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

## Quick Decision

If the user asks for WhiteBit API access in C#/.NET, use `WhiteBit.Net`. Do not write raw `HttpClient` calls to WhiteBit endpoints. The library handles authentication, request signing, rate limiting, response parsing, WebSocket reconnection, and the `WebCallResult<T>` / `CallResult<T>` error model.
If the user asks for WhiteBit API access in C#/.NET, use `WhiteBit.Net`. Do not write raw `HttpClient` calls to WhiteBit endpoints. The library handles authentication, request signing, rate limiting, response parsing, WebSocket reconnection, and the `HttpResult<T>` / `QueryResult<T>` / `WebSocketResult<UpdateSubscription>` error model.

For multi-exchange code, use `CryptoExchange.Net.SharedApis` through `client.V4Api.SharedClient`.

Expand Down Expand Up @@ -43,7 +43,7 @@ var restClient = new WhiteBitRestClient(options =>

## Core Pattern: Result Handling

REST methods return `WebCallResult<T>` or `WebCallResult`. WebSocket requests and subscriptions return `CallResult<T>`. Always check `.Success` before reading `.Data`.
REST methods return `HttpResult<T>` or `HttpResult`. WebSocket request/response methods return `QueryResult<T>`. WebSocket subscriptions return `WebSocketResult<UpdateSubscription>`. Always check `.Success` before reading `.Data`.

```csharp
var tickers = await publicClient.V4Api.ExchangeData.GetTickersAsync();
Expand Down Expand Up @@ -180,6 +180,8 @@ Console.WriteLine(ticker.Data.LastPrice);

Shared REST interfaces implemented by WhiteBit include spot symbols, spot tickers, recent trades, order book, balances, assets, deposits, withdrawals, spot orders, futures symbols, futures tickers, leverage, open interest, position history, futures orders, fees, trigger orders, TP/SL, book ticker, funding rate, and transfers.

Use `new WhiteBitRestClient().V4Api.SharedClient.Discover()` when code needs runtime metadata about supported shared interfaces and endpoint options.

## Dependency Injection

```csharp
Expand Down
2 changes: 2 additions & 0 deletions Examples/ai-friendly/04-multi-exchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// WhiteBit exposes a `.SharedClient` property on V4Api.
// SharedClient implements interfaces like ISpotTickerRestClient, ISpotOrderRestClient,
// IBalanceRestClient, and more: a common abstraction across exchange libraries.
// Use SharedClient.Discover() when you need runtime capability metadata.

ISpotTickerRestClient whiteBitShared = new WhiteBitRestClient().V4Api.SharedClient;

Expand Down Expand Up @@ -58,6 +59,7 @@ async Task PrintTicker(ISpotTickerRestClient client, SharedSymbol symbol)
// IPositionSocketClient

// ---- WEBSOCKET EXAMPLE: SHARED SUBSCRIPTION ----
// Shared socket subscriptions return WebSocketResult<UpdateSubscription>.
var whiteBitSocket = new WhiteBitSocketClient();
ITickerSocketClient whiteBitTickerSocket = whiteBitSocket.V4Api.SharedClient;

Expand Down
14 changes: 8 additions & 6 deletions Examples/ai-friendly/05-error-handling.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// 05-error-handling.cs
//
// Demonstrates: WebCallResult patterns, retry logic, common WhiteBit routing
// Demonstrates: HttpResult patterns, retry logic, common WhiteBit routing
// and validation scenarios.
//
// Setup: dotnet add package WhiteBit.Net
Expand All @@ -16,7 +16,9 @@
});

// ---- 1. THE BASIC PATTERN ----
// Every REST method returns WebCallResult<T> or WebCallResult.
// Every direct and SharedApis REST method returns HttpResult<T> or HttpResult.
// WebSocket request/response methods return QueryResult<T>.
// WebSocket subscriptions return WebSocketResult<UpdateSubscription>.
// .Success is true/false. .Data is valid only when .Success is true.
// .Error contains structured error info when .Success is false.

Expand All @@ -39,11 +41,11 @@
// Retry only on transient errors: network blips, temporary server errors, rate limits.
// Do not retry validation errors, permission errors, or insufficient balance.

async Task<WebCallResult<T>> WithRetry<T>(
Func<Task<WebCallResult<T>>> call,
async Task<HttpResult<T>> WithRetry<T>(
Func<Task<HttpResult<T>>> call,
int maxAttempts = 3)
{
WebCallResult<T> last = default!;
HttpResult<T> last = default!;
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
last = await call();
Expand Down Expand Up @@ -134,7 +136,7 @@ async Task<WebCallResult<T>> WithRetry<T>(
}

// ---- 5. EXCEPTIONS VS ERROR RESULTS ----
// WhiteBit.Net returns exchange and network errors via WebCallResult.Error.
// WhiteBit.Net returns exchange and network errors via HttpResult.Error.
// Exceptions are usually for misconfiguration, disposal, cancellation, or programming errors.

// Common variations:
Expand Down
2 changes: 1 addition & 1 deletion Examples/ai-friendly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ These examples are optimized for AI coding assistants and quick onboarding. Each
| `02-collateral-futures.cs` | Collateral/futures leverage, market order, open position retrieval, reduce-only close |
| `03-websocket.cs` | Ticker updates, kline updates, private order and balance streams, proper teardown |
| `04-multi-exchange.cs` | `CryptoExchange.Net.SharedApis` pattern for exchange-agnostic code |
| `05-error-handling.cs` | `WebCallResult` patterns, retry, common validation and routing mistakes |
| `05-error-handling.cs` | `HttpResult` patterns, retry, common validation and routing mistakes |

## Running

Expand Down
2 changes: 1 addition & 1 deletion WhiteBit.Net.UnitTests/RestRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public async Task ValidateV4CollateralTradingCalls()
await tester.ValidateAsync(client => client.V4Api.CollateralTrading.CancelOcoOrderAsync("123", 123), "CancelOcoOrder");
}

private bool IsAuthenticated(WebCallResult result)
private bool IsAuthenticated(IHttpResult result)
{
return result.RequestHeaders?.Any(x => x.Key == "X-TXC-SIGNATURE") == true;
}
Expand Down
5 changes: 2 additions & 3 deletions WhiteBit.Net.UnitTests/WhiteBitRestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@ public void CheckSignatureExample1()
return headers["X-TXC-SIGNATURE"].ToString();
},
"e164832d38f40692420d44786f8dbc98a46af816ed6c30dbc5d6178e71e63f491a4b6dbfea93c887f50c570d4b403dd61fe2432908754f49f904d06c51c84680",
new Dictionary<string, object>
new Parameters(WhiteBitExchange._parameterSerializationSettings)
{
{ "market", "ETH_USDT" },
},
DateTimeConverter.ParseFromDouble(1499827320559),
true,
false);
true);
}

[Test]
Expand Down
29 changes: 13 additions & 16 deletions WhiteBit.Net/Clients/V4Api/WhiteBitRestClientV4Api.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ internal partial class WhiteBitRestClientV4Api : RestApiClient<WhiteBitEnvironme
#endregion

#region constructor/destructor
internal WhiteBitRestClientV4Api(ILogger logger, HttpClient? httpClient, WhiteBitRestOptions options)
: base(logger, httpClient, options.Environment.RestClientAddress, options, options.V4Options)
internal WhiteBitRestClientV4Api(ILoggerFactory? loggerFactory, HttpClient? httpClient, WhiteBitRestOptions options)
: base(loggerFactory, WhiteBitExchange.ExchangeName, httpClient, options.Environment.RestClientAddress, options, options.V4Options)
{
Account = new WhiteBitRestClientV4ApiAccount(this);
Convert = new WhiteBitRestClientV4ApiConvert(this);
Codes = new WhiteBitRestClientV4ApiCodes(this);
SubAccount = new WhiteBitRestClientV4ApiSubAccount(this);
ExchangeData = new WhiteBitRestClientV4ApiExchangeData(logger, this);
Trading = new WhiteBitRestClientV4ApiTrading(logger, this);
CollateralTrading = new WhiteBitRestClientV4ApiCollateralTrading(logger, this);
ExchangeData = new WhiteBitRestClientV4ApiExchangeData(_logger, this);
Trading = new WhiteBitRestClientV4ApiTrading(_logger, this);
CollateralTrading = new WhiteBitRestClientV4ApiCollateralTrading(_logger, this);
}
#endregion

Expand All @@ -73,26 +73,23 @@ internal WhiteBitRestClientV4Api(ILogger logger, HttpClient? httpClient, WhiteBi
protected override WhiteBitAuthenticationProvider CreateAuthenticationProvider(WhiteBitCredentials credentials)
=> new WhiteBitAuthenticationProvider(credentials, ClientOptions.NonceProvider ?? new WhiteBitNonceProvider());

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);
var result = await base.SendAsync<Unit>(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);
if (!result.Success && result.Error is DeserializeError)
return HttpResult.Ok(result); // Deserialize error without data expected is not an issue

return result;
}

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) where T : class
{
var result = await base.SendAsync<T>(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);
var result = await base.SendAsync<T>(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false);
return result;
}

/// <inheritdoc />
protected override Task<WebCallResult<DateTime>> GetServerTimestampAsync()
protected override Task<HttpResult<DateTime>> GetServerTimestampAsync()
=> ExchangeData.GetServerTimeAsync();

/// <inheritdoc />
Expand Down
Loading
Loading