Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ description: Use DeepCoin.Net when generating C#/.NET code that interacts with t

If the user asks for DeepCoin API access in C#/.NET, use **DeepCoin.Net**. Do not write raw `HttpClient` calls to DeepCoin endpoints because that bypasses signing, rate limiting, result handling, and WebSocket lifecycle support.

For multi-exchange code, use `CryptoExchange.Net.SharedApis` interfaces from `.ExchangeApi.SharedClient`.
For multi-exchange code, use `CryptoExchange.Net.SharedApis` interfaces from `.ExchangeApi.SharedClient`. Call `.ExchangeApi.SharedClient.Discover()` to inspect supported shared features.

## Installation

Expand Down Expand Up @@ -39,7 +39,7 @@ var publicClient = new DeepCoinRestClient();

## Core Pattern: Result Handling

Every method returns `WebCallResult<T>` (REST) or `CallResult<T>` (WebSocket). Always check `.Success` before accessing `.Data`.
REST methods return `HttpResult<T>` or `HttpResult`; WebSocket subscriptions return `WebSocketResult<UpdateSubscription>`; shared symbol/cache helpers can return `ExchangeCallResult<T>`. Always check `.Success` before accessing `.Data`.

```csharp
using DeepCoin.Net.Clients;
Expand Down Expand Up @@ -188,7 +188,9 @@ await socketClient.ExchangeApi.SubscribeToUserDataUpdatesAsync(
using DeepCoin.Net.Clients;
using CryptoExchange.Net.SharedApis;

ISpotTickerRestClient tickerClient = new DeepCoinRestClient().ExchangeApi.SharedClient;
var restClient = new DeepCoinRestClient();
ISpotTickerRestClient tickerClient = restClient.ExchangeApi.SharedClient;
var info = restClient.ExchangeApi.SharedClient.Discover();
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");

var ticker = await tickerClient.GetSpotTickerAsync(new GetTickerRequest(symbol));
Expand Down
4 changes: 2 additions & 2 deletions DeepCoin.Net.UnitTests/DeepCoinRestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Net.Http;
using DeepCoin.Net.Clients;
using CryptoExchange.Net.Converters.SystemTextJson;
using CryptoExchange.Net.Objects;

namespace DeepCoin.Net.UnitTests
{
Expand All @@ -27,12 +28,11 @@ public void CheckSignatureExample1()
return headers["DC-ACCESS-SIGN"].ToString();
},
"Vf4Agvnq70YtbqrjZVCcJUmbqgK8L6ONwb5ldafaptQ=",
new Dictionary<string, object>
new Parameters(DeepCoinExchange._parameterSerializationSettings)
{
{ "symbol", "LTCBTC" },
},
DateTimeConverter.ParseFromDouble(1499827320559),
true,
false);
}

Expand Down
2 changes: 1 addition & 1 deletion DeepCoin.Net.UnitTests/RestRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public async Task ValidateTradingCalls()
await tester.ValidateAsync(client => client.ExchangeApi.Trading.SetTpSlAsync("123"), "SetTpSl");
}

private bool IsAuthenticated(WebCallResult result)
private bool IsAuthenticated(IHttpResult result)
{
return result.RequestHeaders.Any(x => x.Key == "DC-ACCESS-SIGN");
}
Expand Down
2 changes: 1 addition & 1 deletion DeepCoin.Net.UnitTests/SocketSubscriptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async Task ValidateConcurrentSpotSubscriptions()
OutputOriginalData = true
}), logger);

var tester = new SocketSubscriptionValidator<DeepCoinSocketClient>(client, "Subscriptions/Exchange", "wss://stream.crypto.com");
var tester = new SocketSubscriptionValidator<DeepCoinSocketClient>(client, "Subscriptions/Exchange", "wss://stream.deepcoin.com/public/spotws");
await tester.ValidateConcurrentAsync<DeepCoinKlineUpdate>(
(client, handler) => client.ExchangeApi.SubscribeToKlineUpdatesAsync("ETHUSDT", handler),
(client, handler) => client.ExchangeApi.SubscribeToKlineUpdatesAsync("BTCUSDT", handler),
Expand Down
2 changes: 1 addition & 1 deletion DeepCoin.Net/Clients/DeepCoinRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public DeepCoinRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory,
{
Initialize(options.Value);

ExchangeApi = AddApiClient(new DeepCoinRestClientExchangeApi(_logger, httpClient, options.Value));
ExchangeApi = AddApiClient(new DeepCoinRestClientExchangeApi(loggerFactory, httpClient, options.Value));
}

#endregion
Expand Down
2 changes: 1 addition & 1 deletion DeepCoin.Net/Clients/DeepCoinSocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public DeepCoinSocketClient(IOptions<DeepCoinSocketOptions> options, ILoggerFact
{
Initialize(options.Value);

ExchangeApi = AddApiClient(new DeepCoinSocketClientExchangeApi(_logger, options.Value));
ExchangeApi = AddApiClient(new DeepCoinSocketClientExchangeApi(loggerFactory, options.Value));
}

#endregion
Expand Down
113 changes: 16 additions & 97 deletions DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@
using System;
using System.Collections.Concurrent;
using System.Net.Http;
using CryptoExchange.Net.Clients;

namespace DeepCoin.Net.Clients
{
/// <inheritdoc />
public class DeepCoinUserClientProvider : IDeepCoinUserClientProvider
public class DeepCoinUserClientProvider : UserClientProvider<
IDeepCoinRestClient,
IDeepCoinSocketClient,
DeepCoinRestOptions,
DeepCoinSocketOptions,
DeepCoinCredentials,
DeepCoinEnvironment
>, IDeepCoinUserClientProvider
{
private ConcurrentDictionary<string, IDeepCoinRestClient> _restClients = new ConcurrentDictionary<string, IDeepCoinRestClient>();
private ConcurrentDictionary<string, IDeepCoinSocketClient> _socketClients = new ConcurrentDictionary<string, IDeepCoinSocketClient>();

private readonly IOptions<DeepCoinRestOptions> _restOptions;
private readonly IOptions<DeepCoinSocketOptions> _socketOptions;
private readonly HttpClient _httpClient;
private readonly ILoggerFactory? _loggerFactory;


/// <inheritdoc />
public string ExchangeName => DeepCoinExchange.ExchangeName;
public override string ExchangeName => DeepCoinExchange.ExchangeName;

/// <summary>
/// ctor
Expand All @@ -40,97 +41,15 @@ public DeepCoinUserClientProvider(
ILoggerFactory? loggerFactory,
IOptions<DeepCoinRestOptions> restOptions,
IOptions<DeepCoinSocketOptions> socketOptions)
: base(httpClient, loggerFactory, restOptions, socketOptions)
{
_httpClient = httpClient ?? new HttpClient();
_httpClient.Timeout = restOptions.Value.RequestTimeout;
_loggerFactory = loggerFactory;
_restOptions = restOptions;
_socketOptions = socketOptions;
}

/// <inheritdoc />
public void InitializeUserClient(string userIdentifier, DeepCoinCredentials credentials, DeepCoinEnvironment? environment = null)
{
CreateRestClient(userIdentifier, credentials, environment);
CreateSocketClient(userIdentifier, credentials, environment);
}

/// <inheritdoc />
public void ClearUserClients(string userIdentifier)
{
_restClients.TryRemove(userIdentifier, out _);
_socketClients.TryRemove(userIdentifier, out _);
}

protected override IDeepCoinRestClient ConstructRestClient(HttpClient client, ILoggerFactory? loggerFactory, IOptions<DeepCoinRestOptions> options)
=> new DeepCoinRestClient(client, loggerFactory, options);
/// <inheritdoc />
public IDeepCoinRestClient GetRestClient(string userIdentifier, DeepCoinCredentials? credentials = null, DeepCoinEnvironment? environment = null)
{
if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateRestClient(userIdentifier, credentials, environment);

return client;
}

/// <inheritdoc />
public IDeepCoinSocketClient GetSocketClient(string userIdentifier, DeepCoinCredentials? credentials = null, DeepCoinEnvironment? environment = null)
{
if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateSocketClient(userIdentifier, credentials, environment);

return client;
}

private IDeepCoinRestClient CreateRestClient(string userIdentifier, DeepCoinCredentials? credentials, DeepCoinEnvironment? environment)
{
var clientRestOptions = SetRestEnvironment(environment);
var client = new DeepCoinRestClient(_httpClient, _loggerFactory, clientRestOptions);
if (credentials != null)
{
client.SetApiCredentials(credentials);
_restClients[userIdentifier] = client;
}
return client;
}

private IDeepCoinSocketClient CreateSocketClient(string userIdentifier, DeepCoinCredentials? credentials, DeepCoinEnvironment? environment)
{
var clientSocketOptions = SetSocketEnvironment(environment);
var client = new DeepCoinSocketClient(clientSocketOptions!, _loggerFactory);
if (credentials != null)
{
client.SetApiCredentials(credentials);
_socketClients[userIdentifier] = client;
}
return client;
}

private IOptions<DeepCoinRestOptions> SetRestEnvironment(DeepCoinEnvironment? environment)
{
if (environment == null)
return _restOptions;

var newRestClientOptions = new DeepCoinRestOptions();
var restOptions = _restOptions.Value.Set(newRestClientOptions);
newRestClientOptions.Environment = environment;
return Options.Create(newRestClientOptions);
}

private IOptions<DeepCoinSocketOptions> SetSocketEnvironment(DeepCoinEnvironment? environment)
{
if (environment == null)
return _socketOptions;

var newSocketClientOptions = new DeepCoinSocketOptions();
var restOptions = _socketOptions.Value.Set(newSocketClientOptions);
newSocketClientOptions.Environment = environment;
return Options.Create(newSocketClientOptions);
}

private static T ApplyOptionsDelegate<T>(Action<T>? del) where T : new()
{
var opts = new T();
del?.Invoke(opts);
return opts;
}
protected override IDeepCoinSocketClient ConstructSocketClient(ILoggerFactory? loggerFactory, IOptions<DeepCoinSocketOptions> options)
=> new DeepCoinSocketClient(options, loggerFactory);
}
}
36 changes: 13 additions & 23 deletions DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ internal partial class DeepCoinRestClientExchangeApi : RestApiClient<DeepCoinEnv
#endregion

#region constructor/destructor
internal DeepCoinRestClientExchangeApi(ILogger logger, HttpClient? httpClient, DeepCoinRestOptions options)
: base(logger, httpClient, options.Environment.RestClientAddress, options, options.ExchangeOptions)
internal DeepCoinRestClientExchangeApi(ILoggerFactory? loggerFactory, HttpClient? httpClient, DeepCoinRestOptions options)
: base(loggerFactory, DeepCoinExchange.Metadata.Id, httpClient, options.Environment.RestClientAddress, options, options.ExchangeOptions)
{
Account = new DeepCoinRestClientExchangeApiAccount(this);
ExchangeData = new DeepCoinRestClientExchangeApiExchangeData(logger, this);
Trading = new DeepCoinRestClientExchangeApiTrading(logger, this);
ExchangeData = new DeepCoinRestClientExchangeApiExchangeData(_logger, this);
Trading = new DeepCoinRestClientExchangeApiTrading(_logger, this);
}
#endregion

Expand All @@ -58,35 +58,25 @@ internal DeepCoinRestClientExchangeApi(ILogger logger, HttpClient? httpClient, D
protected override DeepCoinAuthenticationProvider CreateAuthenticationProvider(DeepCoinCredentials credentials)
=> new DeepCoinAuthenticationProvider(credentials);

internal Task<WebCallResult> SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null)
=> SendToAddressAsync(BaseAddress, definition, parameters, cancellationToken, weight);

internal async Task<WebCallResult> SendToAddressAsync(string baseAddress, RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null)
internal async Task<HttpResult> SendAsync(RequestDefinition definition, Parameters? parameters, CancellationToken cancellationToken, int? weight = null)
{
var result = await base.SendAsync(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);

// Optional response checking

return result;
return await base.SendAsync<Unit>(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);
}

internal Task<WebCallResult<T>> SendAsync<T>(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) where T : class
=> SendToAddressAsync<T>(BaseAddress, definition, parameters, cancellationToken, weight);

internal async Task<WebCallResult<T>> SendToAddressAsync<T>(string baseAddress, RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) where T : class
internal async Task<HttpResult<T>> SendAsync<T>(RequestDefinition definition, Parameters? parameters, CancellationToken cancellationToken, int? weight = null)
{
var result = await base.SendAsync<DeepCoinResponse<T>>(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);
if (!result)
return result.As<T>(default);
var result = await base.SendAsync<DeepCoinResponse<T>>(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);
if (!result.Success)
return HttpResult.Fail<T>(result);

if (result.Data.Code != 0)
return result.AsError<T>(new ServerError(result.Data.Code, GetErrorInfo(result.Data.Code, result.Data.Message!)));
return HttpResult.Fail<T>(result, new ServerError(result.Data.Code, GetErrorInfo(result.Data.Code, result.Data.Message!)));

return result.As<T>(result.Data.Data);
return HttpResult.Ok(result, result.Data.Data!);
}

/// <inheritdoc />
protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();
protected override Task<HttpResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();


/// <inheritdoc />
Expand Down
Loading
Loading