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
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Use BitMart.Net when generating C#/.NET code that interacts with Bi

## Quick decision

If the user asks for BitMart API access in C#/.NET, use **BitMart.Net**. Do not write raw `HttpClient` calls to BitMart endpoints. For multi-exchange code, use `CryptoExchange.Net.SharedApis`.
If the user asks for BitMart API access in C#/.NET, use **BitMart.Net**. Do not write raw `HttpClient` calls to BitMart endpoints. For multi-exchange code, use `CryptoExchange.Net.SharedApis` through the `.SharedClient` properties. Use `.SharedClient.Discover()` to inspect supported shared features at runtime.

## Installation

Expand Down Expand Up @@ -40,7 +40,7 @@ var publicClient = new BitMartRestClient();

## Core Pattern: Result Handling

Every REST method returns `WebCallResult<T>` or `WebCallResult`. WebSocket subscriptions return `CallResult<UpdateSubscription>`. Always check `.Success` before accessing `.Data`.
Every REST method returns `HttpResult<T>` or `HttpResult`. WebSocket subscriptions return `WebSocketResult<UpdateSubscription>`. Shared non-I/O symbol/cache helpers return `ExchangeCallResult<T>`. Always check `.Success` before accessing `.Data`.

```csharp
var ticker = await restClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
Expand Down Expand Up @@ -345,6 +345,9 @@ using BitMart.Net.Clients;
using CryptoExchange.Net.SharedApis;

var shared = new BitMartRestClient().SpotApi.SharedClient;
var info = shared.Discover();
Console.WriteLine($"{info.Exchange} supports {info.Features.Count(x => x.Supported)} shared features");

var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
var ticker = await shared.GetSpotTickerAsync(new GetTickerRequest(symbol));
```
Expand Down
3 changes: 1 addition & 2 deletions BitMart.Net.UnitTests/BitMartRestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,11 @@ public void CheckSignatureExample1()
return headers["X-BM-SIGN"].ToString();
},
"1d022acf035f8d7f899dcaad0621fe39d351f7809e30ac8c96939a36548a6502",
new Dictionary<string, object>
new Parameters(BitMartExchange._parameterSerializationSettings)
{
{ "symbol", "LTCBTC" },
},
DateTimeConverter.ParseFromDouble(1499827320559),
true,
false);
}

Expand Down
6 changes: 3 additions & 3 deletions BitMart.Net.UnitTests/RestRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public async Task ValidateSpotSubAccountCalls()
await tester.ValidateAsync(client => client.SpotApi.SubAccount.TransferSubAccountToSubAccountAsync("123", 0.1m, "123", "123", "123"), "TransferSubAccountToSubAccount");
await tester.ValidateAsync(client => client.SpotApi.SubAccount.GetSubAccountTransferHistoryForMainAsync(123), "GetSubAccountTransferHistoryForMain", nestedJsonProperty: "data");
await tester.ValidateAsync(client => client.SpotApi.SubAccount.GetSubAccountTransferHistoryAsync(123), "GetSubAccountTransferHistory", nestedJsonProperty: "data");
await tester.ValidateAsync(client => client.SpotApi.SubAccount.GetSubAcccountBalanceAsync("123"), "GetSubAcccountBalance", nestedJsonProperty: "data.wallet");
await tester.ValidateAsync(client => client.SpotApi.SubAccount.GetSubAccountBalanceAsync("123"), "GetSubAcccountBalance", nestedJsonProperty: "data.wallet");
await tester.ValidateAsync(client => client.SpotApi.SubAccount.GetSubAccountListAsync(), "GetSubAccountList", nestedJsonProperty: "data.subAccountList");
}

Expand Down Expand Up @@ -158,7 +158,7 @@ public async Task ValidateUsdFuturesSubAccountCalls()
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.TransferSubToMainForMainAsync("123", 0.1m, "123", "123"), "TransferSubToMainForMain");
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.TransferMainToSubForMainAsync("123", 0.1m, "123", "123"), "TransferMainToSubForMain");
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.TransferSubToMainForSubAsync("123", 0.1m, "123"), "TransferSubToMainForSub");
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.GetSubAcccountBalanceAsync("123"), "GetSubAcccountBalance", nestedJsonProperty: "data.wallet");
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.GetSubAccountBalanceAsync("123"), "GetSubAcccountBalance", nestedJsonProperty: "data.wallet");
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.GetSubAccountTransferHistoryForMainAsync("123", 123), "GetSubAccountTransferHistoryForMain", nestedJsonProperty: "data");
await tester.ValidateAsync(client => client.UsdFuturesApi.SubAccount.GetSubAccountTransferHistoryAsync(123), "GetSubAccountTransferHistory", nestedJsonProperty: "data");
}
Expand Down Expand Up @@ -192,7 +192,7 @@ public async Task ValidateUsdFuturesTradingCalls()
await tester.ValidateAsync(client => client.UsdFuturesApi.Trading.CancelAllAfterAsync("ETHUSDT", TimeSpan.Zero), "CancelAllAfter", nestedJsonProperty: "data");
}

private bool IsAuthenticated(WebCallResult result)
private bool IsAuthenticated(IHttpResult result)
{
return result.RequestHeaders.Any(x => x.Key == "X-BM-SIGN");
}
Expand Down
4 changes: 2 additions & 2 deletions BitMart.Net.UnitTests/SocketSubscriptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public async Task ValidateConcurrentSpotSubscriptions()
OutputOriginalData = true,
}), logger);

var tester = new SocketSubscriptionValidator<BitMartSocketClient>(client, "Subscriptions/Spot", "wss://ws-manager-compress.bitmart.com", "data");
var tester = new SocketSubscriptionValidator<BitMartSocketClient>(client, "Subscriptions/Spot", "wss://ws-manager-compress.bitmart.com/api?protocol=1.1", "data");
await tester.ValidateConcurrentAsync<BitMartKlineUpdate[]>(
(client, handler) => client.SpotApi.SubscribeToKlineUpdatesAsync("ETH_USDT", KlineStreamInterval.OneDay, handler),
(client, handler) => client.SpotApi.SubscribeToKlineUpdatesAsync("ETH_USDT", KlineStreamInterval.OneHour, handler),
Expand Down Expand Up @@ -67,7 +67,7 @@ public async Task ValidateConcurrentFuturesSubscriptions()
OutputOriginalData = true
}), logger);

var tester = new SocketSubscriptionValidator<BitMartSocketClient>(client, "Subscriptions/Futures", "wss://openapi-ws.bitmart.com", "data");
var tester = new SocketSubscriptionValidator<BitMartSocketClient>(client, "Subscriptions/Futures", "wss://openapi-ws-v2.bitmart.com/api?protocol=1.1", "data");
await tester.ValidateConcurrentAsync<BitMartFuturesKlineUpdate>(
(client, handler) => client.UsdFuturesApi.SubscribeToKlineUpdatesAsync("ETHUSDT", FuturesStreamKlineInterval.OneDay, handler),
(client, handler) => client.UsdFuturesApi.SubscribeToKlineUpdatesAsync("ETHUSDT", FuturesStreamKlineInterval.OneHour, handler),
Expand Down
2 changes: 1 addition & 1 deletion BitMart.Net.UnitTests/Subscriptions/Spot/Order.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"client_order_id":"order4872191",
"create_time":"1609926028000",
"update_time":"1609926044000",
"order_mode":"0",
"order_mode":"spot",
"entrust_type":"normal",
"order_state":"partially_filled"
}
Expand Down
2 changes: 1 addition & 1 deletion BitMart.Net/BitMart.Net.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CryptoExchange.Net" Version="11.2.2" />
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptoExchange.Net" Version="12.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.101">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
4 changes: 2 additions & 2 deletions BitMart.Net/BitMartAuthenticationProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public BitMartAuthenticationProvider(BitMartCredentials credentials) : base(cred

public override void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration request)
{
if (!request.Authenticated)
if (!request.RequestDefinition.Authenticated)
return;

var timestamp = GetMillisecondTimestamp(apiClient);
var queryParams = request.GetQueryString(false);
var bodyParams = GetSerializedBody(_serializer, request.BodyParameters ?? new Dictionary<string, object>());
var bodyParams = GetSerializedBody(_serializer, request.BodyParameters ?? new Parameters(BitMartExchange._parameterSerializationSettings));
var signStr = $"{timestamp}#{Credential.Pass}#{queryParams}{bodyParams}";

request.Headers ??= new Dictionary<string, string>();
Expand Down
20 changes: 16 additions & 4 deletions BitMart.Net/BitMartExchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public static class BitMartExchange
"https://www.bitmart.com",
["https://developer-pro.bitmart.com/#introduction"],
PlatformType.CryptoCurrencyExchange,
CentralizationType.Centralized
CentralizationType.Centralized,
BitMartEnvironment.All
);

/// <summary>
Expand Down Expand Up @@ -62,6 +63,11 @@ public static class BitMartExchange
public static ExchangeType Type { get; } = ExchangeType.CEX;

internal static JsonSerializerContext _serializerContext = JsonSerializerContextCache.GetOrCreate<BitMartSourceGenerationContext>();
internal static ParameterSerializationSettings _parameterSerializationSettings = new ParameterSerializationSettings
{
Decimal = DecimalSerialization.String,
DateTimes = DateTimeSerialization.MillisecondsNumber
};

/// <summary>
/// Aliases for BitMart assets
Expand Down Expand Up @@ -95,7 +101,7 @@ public static string FormatSymbol(string baseAsset, string quoteAsset, TradingMo
/// <summary>
/// Rate limiter configuration for the BitMart API
/// </summary>
public static BitMartRateLimiters RateLimiter { get; } = new BitMartRateLimiters();
public static BitMartRateLimiters RateLimiter { get; set; } = new BitMartRateLimiters();
}

/// <summary>
Expand All @@ -114,13 +120,19 @@ public class BitMartRateLimiters
public event Action<RateLimitUpdateEvent> RateLimitUpdated;

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
internal BitMartRateLimiters()
/// <summary>
/// ctor
/// </summary>
public BitMartRateLimiters()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
Initialize();
}

private void Initialize()
/// <summary>
/// Initialize the rate limits
/// </summary>
protected virtual void Initialize()
{
BitMart = new RateLimitGate("BitMart IP");
SocketLimits = new RateLimitGate("Socket limits")
Expand Down
2 changes: 0 additions & 2 deletions BitMart.Net/BitMartUserDataTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public BitMartUserSpotDataTracker(
SpotUserDataTrackerConfig? config) : base(
logger,
restClient.SpotApi.SharedClient,
null,
restClient.SpotApi.SharedClient,
socketClient.SpotApi.SharedClient,
restClient.SpotApi.SharedClient,
Expand Down Expand Up @@ -48,7 +47,6 @@ public BitMartUserUsdFuturesDataTracker(
string? userIdentifier,
FuturesUserDataTrackerConfig? config) : base(logger,
restClient.UsdFuturesApi.SharedClient,
null,
restClient.UsdFuturesApi.SharedClient,
socketClient.UsdFuturesApi.SharedClient,
restClient.UsdFuturesApi.SharedClient,
Expand Down
4 changes: 2 additions & 2 deletions BitMart.Net/Clients/BitMartRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public BitMartRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory,
{
Initialize(options.Value);

UsdFuturesApi = AddApiClient(new BitMartRestClientUsdFuturesApi(_logger, this, httpClient, options.Value));
SpotApi = AddApiClient(new BitMartRestClientSpotApi(_logger, httpClient, options.Value));
UsdFuturesApi = AddApiClient(new BitMartRestClientUsdFuturesApi(loggerFactory, this, httpClient, options.Value));
SpotApi = AddApiClient(new BitMartRestClientSpotApi(loggerFactory, httpClient, options.Value));
}

#endregion
Expand Down
4 changes: 2 additions & 2 deletions BitMart.Net/Clients/BitMartSocketClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public BitMartSocketClient(IOptions<BitMartSocketOptions> options, ILoggerFactor
{
Initialize(options.Value);

UsdFuturesApi = AddApiClient(new BitMartSocketClientUsdFuturesApi(_logger, options.Value));
SpotApi = AddApiClient(new BitMartSocketClientSpotApi(_logger, options.Value));
UsdFuturesApi = AddApiClient(new BitMartSocketClientUsdFuturesApi(loggerFactory, options.Value));
SpotApi = AddApiClient(new BitMartSocketClientSpotApi(loggerFactory, options.Value));
}
#endregion

Expand Down
112 changes: 15 additions & 97 deletions BitMart.Net/Clients/BitMartUserClientProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using BitMart.Net.Interfaces.Clients;
using BitMart.Net.Objects.Options;
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Clients;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
Expand All @@ -10,18 +11,17 @@
namespace BitMart.Net.Clients
{
/// <inheritdoc />
public class BitMartUserClientProvider : IBitMartUserClientProvider
public class BitMartUserClientProvider : UserClientProvider<
IBitMartRestClient,
IBitMartSocketClient,
BitMartRestOptions,
BitMartSocketOptions,
BitMartCredentials,
BitMartEnvironment
>, IBitMartUserClientProvider
{
private ConcurrentDictionary<string, IBitMartRestClient> _restClients = new ConcurrentDictionary<string, IBitMartRestClient>();
private ConcurrentDictionary<string, IBitMartSocketClient> _socketClients = new ConcurrentDictionary<string, IBitMartSocketClient>();

private readonly IOptions<BitMartRestOptions> _restOptions;
private readonly IOptions<BitMartSocketOptions> _socketOptions;
private readonly HttpClient _httpClient;
private readonly ILoggerFactory? _loggerFactory;

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

/// <summary>
/// ctor
Expand All @@ -40,97 +40,15 @@ public BitMartUserClientProvider(
ILoggerFactory? loggerFactory,
IOptions<BitMartRestOptions> restOptions,
IOptions<BitMartSocketOptions> 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, BitMartCredentials credentials, BitMartEnvironment? 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 IBitMartRestClient ConstructRestClient(HttpClient client, ILoggerFactory? loggerFactory, IOptions<BitMartRestOptions> options)
=> new BitMartRestClient(client, loggerFactory, options);
/// <inheritdoc />
public IBitMartRestClient GetRestClient(string userIdentifier, BitMartCredentials? credentials = null, BitMartEnvironment? environment = null)
{
if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateRestClient(userIdentifier, credentials, environment);

return client;
}

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

return client;
}

private IBitMartRestClient CreateRestClient(string userIdentifier, BitMartCredentials? credentials, BitMartEnvironment? environment)
{
var clientRestOptions = SetRestEnvironment(environment);
var client = new BitMartRestClient(_httpClient, _loggerFactory, clientRestOptions);
if (credentials != null)
{
client.SetApiCredentials(credentials);
_restClients[userIdentifier] = client;
}
return client;
}

private IBitMartSocketClient CreateSocketClient(string userIdentifier, BitMartCredentials? credentials, BitMartEnvironment? environment)
{
var clientSocketOptions = SetSocketEnvironment(environment);
var client = new BitMartSocketClient(clientSocketOptions!, _loggerFactory);
if (credentials != null)
{
client.SetApiCredentials(credentials);
_socketClients[userIdentifier] = client;
}
return client;
}

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

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

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

var newSocketClientOptions = new BitMartSocketOptions();
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 IBitMartSocketClient ConstructSocketClient(ILoggerFactory? loggerFactory, IOptions<BitMartSocketOptions> options)
=> new BitMartSocketClient(options, loggerFactory);
}
}
Loading
Loading