1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-10-14 23:21:48 +00:00

Compare commits

..

53 Commits

Author SHA1 Message Date
Jkorf 911e09be61 wip 2026-09-10 15:03:29 +02:00
Jkorf 876c2d27f7 wip 2026-09-10 13:26:33 +02:00
Jkorf dc4a6af695 wip 2026-09-10 12:53:11 +02:00
Jkorf c56e31abab wip 2026-09-10 12:02:57 +02:00
Jkorf 08972d3d63 wip 2026-09-08 15:15:08 +02:00
Jkorf 357809fd0c wip 2026-09-08 15:08:34 +02:00
Jkorf 7629455dec wip 2026-09-07 15:23:08 +02:00
JKorf 9df960361a wip 2026-09-06 20:27:37 +02:00
JKorf f215b729ed wip 2026-09-05 20:28:11 +02:00
Jkorf 431ef93611 wip 2026-09-04 16:21:13 +02:00
Jkorf a545efbb45 wip 2026-09-04 13:33:50 +02:00
Jkorf 67bf7d08cb wip 2026-09-04 12:40:54 +02:00
JKorf 2704693600 wip 2026-09-02 17:49:53 +02:00
Jkorf 1643487e73 wip 2026-09-02 09:07:49 +02:00
JKorf 1a0101a45f wip 2026-09-01 21:22:30 +02:00
Jkorf 20977e28b7 wip 2026-09-01 16:56:15 +02:00
Jkorf d0f5a8d6cc wip 2026-09-01 15:57:19 +02:00
Jkorf 2fff7a307f Merge branch 'master' into feature/shared-apis-v2 2026-09-01 09:58:01 +02:00
Jkorf 4a4930d658 Updated to version 12.5.1 2026-09-01 09:03:50 +02:00
Jkorf ca2bf414a7 wip 2026-09-01 08:58:03 +02:00
nils2525 ca245959a2 Fix subscription query completion for topic-filtered routes (#288) 2026-08-31 21:07:33 +02:00
Jkorf 5f101e3b88 wip 2026-08-31 11:23:53 +02:00
Jkorf 32e74e9bc4 wip 2026-08-27 15:57:28 +02:00
Jkorf d93d6b7a2e wip 2026-08-26 14:26:24 +02:00
Jkorf d0ce2563de wip 2026-08-25 16:09:15 +02:00
Jkorf 407509e9cd wip 2026-08-25 09:12:39 +02:00
Jkorf 3dd68dcad4 wip 2026-08-24 16:13:22 +02:00
Jkorf ff43d202d4 Add Tapbit to examples 2026-08-24 11:52:37 +02:00
Jkorf d1947e643b Added Tapbit reference 2026-08-24 10:55:38 +02:00
JKorf 6fec65cd6a Fixed failed ISymbolOrderBook start potentially leaving a pending background task 2026-08-23 20:36:47 +02:00
JKorf 053bcf6ecc Fixed caching issue for auth requests 2026-08-23 20:22:10 +02:00
JKorf 6957ba44f5 Fixed KlineTracker reporting incorrect High/Low price on GetStats result 2026-08-23 20:21:54 +02:00
Jkorf c1af97dd4a Updated to version 12.5.0 2026-08-21 10:21:54 +02:00
Jkorf ecfe3cf392 Added ISpotOrderManagementSocketClient and IFuturesOrderManagementSocketClient for placing/canceling orders via websocket 2026-08-21 09:56:56 +02:00
Jkorf ac1e9cf2ab Added Description property to EndpointOptions classes. Updated client info string representation, added additional validation for Spot endpoints 2026-08-21 09:55:53 +02:00
Jkorf 4987e6be70 Added WithCalculatedQuantities to SharedQuantity to retrieve a copy with calculated quantities 2026-08-21 09:52:54 +02:00
Jkorf 9100c0d7f3 Added UpperPriceLimitPerecentage, LowerPriceLimitPercentage, MakerFeePercentage and TakerFeePercentage to SharedSpotSymbol model 2026-08-21 09:52:29 +02:00
Jkorf 5b1ae30e4b Updated SharedId value to be nullable 2026-08-21 09:52:00 +02:00
Jkorf 36a06e8a9a Added UpperFundingCap, LowerFundingCap to SharedFuturesSymbol model 2026-08-21 09:51:43 +02:00
Jkorf e7eb559074 Updated remaining quantity/volumes to SharedOrderQuantity model in Shared APIs, added QuantityType to SharedOrderBook, added auto calculated quote quantity where for SharedOrderQuantity where it makes sense 2026-08-21 09:51:17 +02:00
Jkorf 79e2efec70 Added IQueryResult interface to QueryResult, moved OriginalData to the base class 2026-08-21 09:48:57 +02:00
Jkorf c8c6d14329 Split UserClientProvider into base class with RestClient and derived class also containing SocketClient 2026-08-21 09:47:19 +02:00
Jkorf b8bd30a09d Added UsePublicConnectionForAuth property to SocketApiClient 2026-08-21 09:44:30 +02:00
Jkorf c2b58ab19a Removed unnecessary log from token manager 2026-08-21 09:43:57 +02:00
Jkorf 90a031c595 Added support for order polling where Shared order implementation doesn't support time filtering to UserDataTracker implementations 2026-08-17 14:17:43 +02:00
Jkorf 860d753ad6 Update README.md 2026-08-07 09:09:51 +02:00
Jkorf e16c91792c Added LBank reference 2026-08-06 10:29:02 +02:00
Jkorf a3d95da9fa Updated examples 2026-08-06 10:26:39 +02:00
JKorf 907399b878 Added ManualUpdateSubscription and UpdateSubscription additional constructor to allow producing websocket events without actual connection 2026-08-05 21:31:59 +02:00
JKorf 9eab0d967e Merge branch 'master' of https://github.com/JKorf/CryptoExchange.Net 2026-08-05 17:53:51 +02:00
JKorf 0dee68e8ae Fixed timing issue in test 2026-08-05 17:53:43 +02:00
Jkorf d838d3377f Updated logging unhandled websocket message 2026-08-05 13:13:48 +02:00
Jkorf 2141ad9061 Fixed incorrect check test output 2026-07-29 13:05:42 +02:00
498 changed files with 12819 additions and 3626 deletions
+5 -1
View File
@@ -42,6 +42,10 @@ When implementing an exchange library, use `LibraryHelpers.IsStableCoin`, `IsCom
In 12.4.0, use `SharedOrderQuantity`-valued `Volumes` on shared spot/futures tickers and klines, and `Quantities` on shared trades. The scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete. In 12.4.0, use `SharedOrderQuantity`-valued `Volumes` on shared spot/futures tickers and klines, and `Quantities` on shared trades. The scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete.
## WebSocket order commands
In 12.5.0, `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient` optionally provide place/cancel order commands over WebSocket. Their methods return `QueryResult<SharedId>`, not `WebSocketResult<UpdateSubscription>`. Check whether the exchange implements the interface.
## Result pattern ## Result pattern
REST methods return `HttpResult<T>` and websocket subscription methods return `WebSocketResult<UpdateSubscription>`. Always check `.Success`. `.Exchange` property identifies which exchange responded — useful for logging. REST methods return `HttpResult<T>` and websocket subscription methods return `WebSocketResult<UpdateSubscription>`. Always check `.Success`. `.Exchange` property identifies which exchange responded — useful for logging.
@@ -49,7 +53,7 @@ REST methods return `HttpResult<T>` and websocket subscription methods return `W
## Available shared interfaces ## Available shared interfaces
- REST tickers/symbols/orderbook/klines/trades, orders (spot/futures, regular/trigger/TP-SL), balances, positions, fees, deposits/withdrawals, transfers - REST tickers/symbols/orderbook/klines/trades, orders (spot/futures, regular/trigger/TP-SL), balances, positions, fees, deposits/withdrawals, transfers
- WebSocket tickers, book tickers, order book, trades, klines, user data - WebSocket tickers, book tickers, order book, trades, klines, user data, and optional spot/futures order management
Each exchange documents which it implements. Not every exchange supports every operation. Each exchange documents which it implements. Not every exchange supports every operation.
+5 -1
View File
@@ -34,6 +34,10 @@ CryptoExchange.Net 12.2.0 classifies the base and quote sides of `SharedSpotSymb
CryptoExchange.Net 12.4.0 uses `SharedOrderQuantity` for market-data quantities. Prefer `Volumes` on `SharedSpotTicker`, `SharedFuturesTicker`, and `SharedKline`, and `Quantities` on `SharedTrade`; the scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete. CryptoExchange.Net 12.4.0 uses `SharedOrderQuantity` for market-data quantities. Prefer `Volumes` on `SharedSpotTicker`, `SharedFuturesTicker`, and `SharedKline`, and `Quantities` on `SharedTrade`; the scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete.
## WebSocket order commands
CryptoExchange.Net 12.5.0 adds optional `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient` interfaces for placing and canceling orders over WebSocket. These command methods return `QueryResult<SharedId>` rather than a subscription result. Check exchange support before relying on either interface.
## Single-exchange code uses the exchange's own client ## Single-exchange code uses the exchange's own client
For Binance-only code, use `BinanceRestClient` directly (see Binance.Net repo `AGENTS.md`). SharedApis is for portability — use it when you need that. For Binance-only code, use `BinanceRestClient` directly (see Binance.Net repo `AGENTS.md`). SharedApis is for portability — use it when you need that.
@@ -45,7 +49,7 @@ REST methods return `HttpResult<T>` and websocket subscription methods return `W
## Available shared interfaces ## Available shared interfaces
REST: tickers, symbols, orderbook, klines, trades, orders (spot/futures, trigger, TP-SL), balances, positions, fees, deposits/withdrawals, transfers. REST: tickers, symbols, orderbook, klines, trades, orders (spot/futures, trigger, TP-SL), balances, positions, fees, deposits/withdrawals, transfers.
WebSocket: tickers, book tickers, orderbook, trades, klines, user data. WebSocket: tickers, book tickers, orderbook, trades, klines, user data, and optional spot/futures order management.
Each exchange library implements a subset. Check exchange docs for support matrix. Each exchange library implements a subset. Check exchange docs for support matrix.
+5
View File
@@ -89,6 +89,10 @@ For exchange-library implementations, `LibraryHelpers.IsStableCoin`, `IsCommodit
Since CryptoExchange.Net 12.4.0, shared market-data models use `SharedOrderQuantity` so base-asset, quote-asset, and contract quantities remain explicit. Read `SharedSpotTicker.Volumes`, `SharedFuturesTicker.Volumes`, and `SharedKline.Volumes`; read `SharedTrade.Quantities`. The former scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete. Since CryptoExchange.Net 12.4.0, shared market-data models use `SharedOrderQuantity` so base-asset, quote-asset, and contract quantities remain explicit. Read `SharedSpotTicker.Volumes`, `SharedFuturesTicker.Volumes`, and `SharedKline.Volumes`; read `SharedTrade.Quantities`. The former scalar `Volume`, `QuoteVolume`, and `Quantity` members are obsolete.
## WebSocket Order Management
Since CryptoExchange.Net 12.5.0, exchanges can implement `ISpotOrderManagementSocketClient` and `IFuturesOrderManagementSocketClient` to place and cancel orders over WebSocket. These are command interfaces, not subscription interfaces: `Place*OrderAsync` and `Cancel*OrderAsync` return `QueryResult<SharedId>`. Check exchange support before using them.
## Available Shared Interfaces ## Available Shared Interfaces
**REST:** **REST:**
@@ -102,6 +106,7 @@ Since CryptoExchange.Net 12.4.0, shared market-data models use `SharedOrderQuant
- `ITickerSocketClient`, `IBookTickerSocketClient` - `ITickerSocketClient`, `IBookTickerSocketClient`
- `IOrderBookSocketClient`, `ITradeSocketClient`, `IKlineSocketClient` - `IOrderBookSocketClient`, `ITradeSocketClient`, `IKlineSocketClient`
- `IUserTradeSocketClient`, `ISpotOrderSocketClient`, `IFuturesOrderSocketClient`, `IPositionSocketClient`, `IBalanceSocketClient` - `IUserTradeSocketClient`, `ISpotOrderSocketClient`, `IFuturesOrderSocketClient`, `IPositionSocketClient`, `IBalanceSocketClient`
- Order commands: `ISpotOrderManagementSocketClient`, `IFuturesOrderManagementSocketClient`
Each exchange documents which interfaces it implements (some exchanges don't support every operation). Each exchange documents which interfaces it implements (some exchanges don't support every operation).
@@ -91,28 +91,25 @@ namespace CryptoExchange.Net.UnitTests
var evnt = new AsyncResetEvent(false, true); var evnt = new AsyncResetEvent(false, true);
var waiters = new List<Task<bool>>(); var waiters = new List<Task<bool>>();
for(var i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
waiters.Add(evnt.WaitAsync()); waiters.Add(evnt.WaitAsync());
} }
List<bool>? results = null; var remaining = waiters.ToList();
var resultsWaiter = Task.Run(async () =>
{
await Task.WhenAll(waiters);
results = waiters.Select(w => w.Result).ToList();
});
for(var i = 1; i <= 10; i++) for (var i = 0; i < 10; i++)
{ {
evnt.Set(); evnt.Set();
await Task.Delay(1); // Wait for the continuation.
Assert.That(10 - i == waiters.Count(w => w.Status != TaskStatus.RanToCompletion)); var completed = await Task.WhenAny(remaining);
Assert.That(await completed, Is.True);
remaining.Remove(completed);
Assert.That(remaining.Count(w => w.IsCompleted), Is.Zero);
} }
await resultsWaiter; Assert.That(remaining, Is.Empty);
Assert.That(10 == results?.Count(r => r));
} }
[Test] [Test]
@@ -0,0 +1,136 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Sockets.Default;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
public class ManualUpdateSubscriptionTests
{
[Test]
public void Constructor_Should_CreateSubscribedVirtualSubscription()
{
var controller = new ManualUpdateSubscription(socketId: 12);
Assert.That(controller.Subscription.SocketId, Is.EqualTo(12));
Assert.That(controller.Subscription.Id, Is.GreaterThan(0));
Assert.That(controller.Subscription.SocketStatus, Is.EqualTo(SocketStatus.Connected));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Subscribed));
Assert.That(controller.Subscription.LastReceiveTime, Is.Null);
}
[Test]
public void StateChanges_Should_BeVisibleOnSubscription()
{
var controller = new ManualUpdateSubscription();
var timestamp = new DateTime(2026, 8, 5, 12, 0, 0, DateTimeKind.Utc);
var statuses = new List<SubscriptionStatus>();
controller.Subscription.SubscriptionStatusChanged += statuses.Add;
controller.SetLastReceiveTime(timestamp);
controller.SetSocketStatus(SocketStatus.Reconnecting);
controller.SetSubscriptionStatus(SubscriptionStatus.Subscribing);
controller.SetSubscriptionStatus(SubscriptionStatus.Subscribed);
Assert.That(controller.Subscription.LastReceiveTime, Is.EqualTo(timestamp));
Assert.That(controller.Subscription.SocketStatus, Is.EqualTo(SocketStatus.Reconnecting));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Subscribed));
Assert.That(statuses, Is.EqualTo(new[]
{
SubscriptionStatus.Subscribing,
SubscriptionStatus.Subscribed
}));
}
[Test]
public void LifecycleMethods_Should_InvokeSubscriptionEvents()
{
var controller = new ManualUpdateSubscription();
var error = new ServerError("Test error", ErrorInfo.Unknown);
var exception = new InvalidOperationException("Test exception");
var disconnectedPeriod = TimeSpan.FromMinutes(2);
var lost = 0;
var restored = TimeSpan.Zero;
Error? resubscribeError = null;
var paused = 0;
var unpaused = 0;
Exception? receivedException = null;
controller.Subscription.ConnectionLost += () => lost++;
controller.Subscription.ConnectionRestored += x => restored = x;
controller.Subscription.ResubscribingFailed += x => resubscribeError = x;
controller.Subscription.ActivityPaused += () => paused++;
controller.Subscription.ActivityUnpaused += () => unpaused++;
controller.Subscription.Exception += x => receivedException = x;
controller.InvokeConnectionLost();
controller.InvokeConnectionRestored(disconnectedPeriod);
controller.InvokeResubscribingFailed(error);
controller.InvokeActivityPaused();
controller.InvokeActivityUnpaused();
controller.InvokeException(exception);
Assert.That(lost, Is.EqualTo(1));
Assert.That(restored, Is.EqualTo(disconnectedPeriod));
Assert.That(resubscribeError, Is.SameAs(error));
Assert.That(paused, Is.EqualTo(1));
Assert.That(unpaused, Is.EqualTo(1));
Assert.That(receivedException, Is.SameAs(exception));
}
[Test]
public void InvokeConnectionClosed_Should_CloseAndOnlyInvokeOnce()
{
var controller = new ManualUpdateSubscription();
var closed = 0;
controller.Subscription.ConnectionClosed += () => closed++;
controller.InvokeConnectionClosed();
controller.InvokeConnectionClosed();
Assert.That(closed, Is.EqualTo(1));
Assert.That(controller.Subscription.SocketStatus, Is.EqualTo(SocketStatus.Closed));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Closed));
}
[Test]
public async Task SubscriptionOperations_Should_InvokeCallbacks()
{
var closes = 0;
var reconnects = 0;
var resubscribes = 0;
var controller = new ManualUpdateSubscription(
closeAsync: () =>
{
closes++;
return Task.CompletedTask;
},
reconnectAsync: () =>
{
reconnects++;
return Task.CompletedTask;
},
resubscribeAsync: () =>
{
resubscribes++;
return Task.FromResult(CallResult.Ok());
});
await controller.Subscription.ReconnectAsync();
var resubscribeResult = await controller.Subscription.ResubscribeAsync();
await controller.Subscription.CloseAsync();
await controller.Subscription.CloseAsync();
Assert.That(reconnects, Is.EqualTo(1));
Assert.That(resubscribes, Is.EqualTo(1));
Assert.That(resubscribeResult.Success, Is.True);
Assert.That(closes, Is.EqualTo(1));
Assert.That(controller.Subscription.SubscriptionStatus, Is.EqualTo(SubscriptionStatus.Closed));
}
}
}
@@ -0,0 +1,281 @@
using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
using System;
using System.Collections;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
public class RequestCoalescerTests
{
private static readonly TimeSpan _testTimeout = TimeSpan.FromSeconds(5);
[Test]
public async Task ExecuteAsync_WithIdenticalRequests_ShouldExecuteRequestOnce()
{
var coalescer = CreateCoalescer();
var key = CreateKey();
var requestCompletion = CreateCompletionSource<string>();
var requestStarted = CreateCompletionSource<bool>();
var requestCount = 0;
async Task<string> ExecuteRequest(CancellationToken cancellationToken)
{
Interlocked.Increment(ref requestCount);
requestStarted.TrySetResult(true);
return await requestCompletion.Task;
}
var first = coalescer.ExecuteAsync(key, ExecuteRequest, () => "canceled", CancellationToken.None);
await requestStarted.Task.WaitAsync(_testTimeout);
var second = coalescer.ExecuteAsync(key, ExecuteRequest, () => "canceled", CancellationToken.None);
Assert.That(requestCount, Is.EqualTo(1));
requestCompletion.SetResult("result");
Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("result"));
Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("result"));
Assert.That(requestCount, Is.EqualTo(1));
}
[Test]
public async Task ExecuteAsync_WithDifferentKeys_ShouldExecuteEachRequest()
{
var coalescer = CreateCoalescer();
var firstCompletion = CreateCompletionSource<string>();
var secondCompletion = CreateCompletionSource<string>();
var firstStarted = CreateCompletionSource<bool>();
var secondStarted = CreateCompletionSource<bool>();
var requestCount = 0;
var first = coalescer.ExecuteAsync(
CreateKey(parameters: "symbol=ETH"),
async cancellationToken =>
{
Interlocked.Increment(ref requestCount);
firstStarted.TrySetResult(true);
return await firstCompletion.Task;
},
() => "canceled",
CancellationToken.None);
var second = coalescer.ExecuteAsync(
CreateKey(parameters: "symbol=BTC"),
async cancellationToken =>
{
Interlocked.Increment(ref requestCount);
secondStarted.TrySetResult(true);
return await secondCompletion.Task;
},
() => "canceled",
CancellationToken.None);
await Task.WhenAll(firstStarted.Task, secondStarted.Task).WaitAsync(_testTimeout);
Assert.That(requestCount, Is.EqualTo(2));
firstCompletion.SetResult("first");
secondCompletion.SetResult("second");
Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("first"));
Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("second"));
}
[Test]
public async Task ExecuteAsync_WhenOneListenerCancels_ShouldKeepSharedRequestRunning()
{
var coalescer = CreateCoalescer();
var requestCompletion = CreateCompletionSource<string>();
var requestStarted = CreateCompletionSource<CancellationToken>();
var requestCount = 0;
using var firstCancellationSource = new CancellationTokenSource();
async Task<string> ExecuteRequest(CancellationToken cancellationToken)
{
Interlocked.Increment(ref requestCount);
requestStarted.TrySetResult(cancellationToken);
return await requestCompletion.Task;
}
var first = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", firstCancellationSource.Token);
var requestCancellationToken = await requestStarted.Task.WaitAsync(_testTimeout);
var second = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", CancellationToken.None);
firstCancellationSource.Cancel();
Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("canceled"));
Assert.That(requestCancellationToken.IsCancellationRequested, Is.False);
Assert.That(requestCount, Is.EqualTo(1));
requestCompletion.SetResult("result");
Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("result"));
}
[Test]
public async Task ExecuteAsync_WhenAllListenersCancel_ShouldCancelSharedRequest()
{
var coalescer = CreateCoalescer();
var requestStarted = CreateCompletionSource<bool>();
var requestCanceled = CreateCompletionSource<bool>();
using var firstCancellationSource = new CancellationTokenSource();
using var secondCancellationSource = new CancellationTokenSource();
async Task<string> ExecuteRequest(CancellationToken cancellationToken)
{
using (cancellationToken.Register(() => requestCanceled.TrySetResult(true)))
{
requestStarted.TrySetResult(true);
await requestCanceled.Task;
return "request canceled";
}
}
var first = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "listener canceled", firstCancellationSource.Token);
await requestStarted.Task.WaitAsync(_testTimeout);
var second = coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "listener canceled", secondCancellationSource.Token);
firstCancellationSource.Cancel();
Assert.That(await first.WaitAsync(_testTimeout), Is.EqualTo("listener canceled"));
Assert.That(requestCanceled.Task.IsCompleted, Is.False);
secondCancellationSource.Cancel();
Assert.That(await second.WaitAsync(_testTimeout), Is.EqualTo("listener canceled"));
Assert.That(await requestCanceled.Task.WaitAsync(_testTimeout), Is.True);
}
[Test]
public async Task ExecuteAsync_WithAlreadyCanceledListener_ShouldNotStartRequest()
{
var coalescer = CreateCoalescer();
var requestCount = 0;
using var cancellationSource = new CancellationTokenSource();
cancellationSource.Cancel();
var result = await coalescer.ExecuteAsync(
CreateKey(),
cancellationToken =>
{
Interlocked.Increment(ref requestCount);
return Task.FromResult("result");
},
() => "canceled",
cancellationSource.Token);
Assert.That(result, Is.EqualTo("canceled"));
Assert.That(requestCount, Is.Zero);
}
[Test]
public async Task ExecuteAsync_AfterRequestCompletes_ShouldStartNewRequest()
{
var coalescer = CreateCoalescer();
var requestCount = 0;
Task<int> ExecuteRequest(CancellationToken cancellationToken)
=> Task.FromResult(Interlocked.Increment(ref requestCount));
var first = await coalescer.ExecuteAsync(CreateKey(typeof(int)), ExecuteRequest, () => -1, CancellationToken.None);
var second = await coalescer.ExecuteAsync(CreateKey(typeof(int)), ExecuteRequest, () => -1, CancellationToken.None);
Assert.That(first, Is.EqualTo(1));
Assert.That(second, Is.EqualTo(2));
Assert.That(requestCount, Is.EqualTo(2));
}
[Test]
public void ExecuteAsync_AfterRequestFails_ShouldStartNewRequest()
{
var coalescer = CreateCoalescer();
var requestCount = 0;
Task<string> ExecuteRequest(CancellationToken cancellationToken)
{
if (Interlocked.Increment(ref requestCount) == 1)
return Task.FromException<string>(new InvalidOperationException("Request failed"));
return Task.FromResult("result");
}
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", CancellationToken.None));
Assert.That(
coalescer.ExecuteAsync(CreateKey(), ExecuteRequest, () => "canceled", CancellationToken.None).GetAwaiter().GetResult(),
Is.EqualTo("result"));
Assert.That(requestCount, Is.EqualTo(2));
}
[Test]
public async Task ExecuteAsync_WhenAbandonedRequestCompletes_ShouldNotRemoveReplacementRequest()
{
var coalescer = CreateCoalescer();
var key = CreateKey();
var oldRequestCompletion = CreateCompletionSource<string>();
var oldRequestStarted = CreateCompletionSource<bool>();
var replacementCompletion = CreateCompletionSource<string>();
var replacementStarted = CreateCompletionSource<bool>();
var requestCount = 0;
using var cancellationSource = new CancellationTokenSource();
async Task<string> ExecuteOldRequest(CancellationToken cancellationToken)
{
Interlocked.Increment(ref requestCount);
oldRequestStarted.TrySetResult(true);
return await oldRequestCompletion.Task;
}
async Task<string> ExecuteReplacementRequest(CancellationToken cancellationToken)
{
Interlocked.Increment(ref requestCount);
replacementStarted.TrySetResult(true);
return await replacementCompletion.Task;
}
var abandonedListener = coalescer.ExecuteAsync(key, ExecuteOldRequest, () => "canceled", cancellationSource.Token);
await oldRequestStarted.Task.WaitAsync(_testTimeout);
var oldPhysicalRequest = GetPhysicalRequest(coalescer, key);
cancellationSource.Cancel();
Assert.That(await abandonedListener.WaitAsync(_testTimeout), Is.EqualTo("canceled"));
var replacementListener = coalescer.ExecuteAsync(key, ExecuteReplacementRequest, () => "canceled", CancellationToken.None);
await replacementStarted.Task.WaitAsync(_testTimeout);
oldRequestCompletion.SetResult("old result");
await oldPhysicalRequest.WaitAsync(_testTimeout);
var joinedReplacementListener = coalescer.ExecuteAsync(key, ExecuteReplacementRequest, () => "canceled", CancellationToken.None);
Assert.That(requestCount, Is.EqualTo(2));
replacementCompletion.SetResult("replacement result");
Assert.That(await replacementListener.WaitAsync(_testTimeout), Is.EqualTo("replacement result"));
Assert.That(await joinedReplacementListener.WaitAsync(_testTimeout), Is.EqualTo("replacement result"));
Assert.That(requestCount, Is.EqualTo(2));
}
private static RequestCoalescer CreateCoalescer()
=> new RequestCoalescer(NullLogger.Instance);
private static RequestCoalescingKey CreateKey(Type? responseType = null, string? parameters = null)
=> new RequestCoalescingKey("GET", "https://localhost/test", parameters, responseType ?? typeof(string));
private static TaskCompletionSource<T> CreateCompletionSource<T>()
=> new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
private static Task GetPhysicalRequest(RequestCoalescer coalescer, RequestCoalescingKey key)
{
var requestsField = typeof(RequestCoalescer).GetField("_requests", BindingFlags.Instance | BindingFlags.NonPublic)!;
var requests = (IDictionary)requestsField.GetValue(coalescer)!;
var entry = requests[key]!;
var requestProperty = entry.GetType().GetProperty("Request")!;
return ((Lazy<Task<object>>)requestProperty.GetValue(entry)!).Value;
}
}
}
@@ -0,0 +1,170 @@
using CryptoExchange.Net.SharedApis;
using NUnit.Framework;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
internal class SharedApiClientTests
{
[Test]
public void GetCapability_ShouldUseCapabilityTradingModes()
{
var sharedApi = new TestSharedApi(
[TradingMode.Spot, TradingMode.PerpetualLinear],
[TradingMode.Spot]);
var client = new TestSharedApiClient(sharedApi);
Assert.Multiple(() =>
{
Assert.That(client.GetCapability<ITestCapability>(TradingMode.Spot), Is.SameAs(sharedApi));
Assert.That(client.GetCapability<ITestCapability>(TradingMode.PerpetualLinear), Is.Null);
Assert.That(client.GetCapabilities<ITestCapability>(TradingMode.Spot), Has.Count.EqualTo(1));
Assert.That(client.GetCapabilities<ITestCapability>(TradingMode.PerpetualLinear), Is.Empty);
});
}
[Test]
public void CapabilityTradingModes_ShouldDefaultToSharedApiTradingModes()
{
var sharedApi = new TestSharedApi(
[TradingMode.Spot, TradingMode.PerpetualLinear],
null);
var client = new TestSharedApiClient(sharedApi);
Assert.Multiple(() =>
{
Assert.That(client.GetCapability<ITestCapability>(TradingMode.Spot), Is.SameAs(sharedApi));
Assert.That(client.GetCapability<ITestCapability>(TradingMode.PerpetualLinear), Is.SameAs(sharedApi));
});
}
[Test]
public void CapabilityTradingModes_ShouldIntersectApiApplicableAndOverrideModes()
{
var sharedApi = new TestSharedApi(
[TradingMode.Spot, TradingMode.PerpetualLinear, TradingMode.DeliveryLinear],
[TradingMode.Spot, TradingMode.PerpetualLinear],
[TradingMode.PerpetualLinear, TradingMode.DeliveryLinear]);
Assert.That(
sharedApi.CapabilityOptions.SupportedTradingModes,
Is.EqualTo(new[] { TradingMode.PerpetualLinear }));
}
[Test]
public void CapabilityOptions_ShouldApplyIntrinsicTradingModes()
{
var apiModes = new[]
{
TradingMode.Spot,
TradingMode.PerpetualLinear,
TradingMode.DeliveryLinear,
TradingMode.PerpetualInverse,
TradingMode.DeliveryInverse
};
var spotOptions = new PlaceSpotOrderOptions("TestExchange");
var futuresOptions = new GetPositionsOptions("TestExchange", false);
var perpetualOptions = new GetFundingRateHistoryOptions(
"TestExchange",
true,
true,
true,
100,
false);
var unrestrictedOptions = new GetTickerOptions("TestExchange");
_ = new TestOptionsHost(apiModes, spotOptions);
_ = new TestOptionsHost(apiModes, futuresOptions);
_ = new TestOptionsHost(apiModes, perpetualOptions);
_ = new TestOptionsHost(apiModes, unrestrictedOptions);
Assert.Multiple(() =>
{
Assert.That(spotOptions.SupportedTradingModes, Is.EqualTo(new[] { TradingMode.Spot }));
Assert.That(futuresOptions.SupportedTradingModes, Is.EqualTo(new[]
{
TradingMode.PerpetualLinear,
TradingMode.DeliveryLinear,
TradingMode.PerpetualInverse,
TradingMode.DeliveryInverse
}));
Assert.That(perpetualOptions.SupportedTradingModes, Is.EqualTo(new[]
{
TradingMode.PerpetualLinear,
TradingMode.PerpetualInverse
}));
Assert.That(unrestrictedOptions.SupportedTradingModes, Is.EqualTo(apiModes));
});
}
private interface ITestCapability : ISharedApiCapability
{
}
private sealed class TestCapabilityOptions : CapabilityOptions<SharedRequest, ITestCapability>
{
public override string Description => "Test capability";
public TestCapabilityOptions(TradingMode[]? applicableTradingModes)
: base("TestExchange", false, "TestOperation", [], applicableTradingModes)
{
}
}
private sealed class TestSharedApi : SharedApiBase, ITestCapability
{
public TestCapabilityOptions CapabilityOptions { get; }
public TestSharedApi(TradingMode[] apiTradingModes, TradingMode[]? capabilityTradingModes)
: this(apiTradingModes, capabilityTradingModes, null)
{
}
public TestSharedApi(
TradingMode[] apiTradingModes,
TradingMode[]? capabilityTradingModes,
TradingMode[]? applicableTradingModes)
: base(
SharedTransport.Rest,
"TestExchange",
apiTradingModes,
() => false,
(baseAsset, quoteAsset, tradingMode, deliverDate) => $"{baseAsset}{quoteAsset}")
{
var options = new TestCapabilityOptions(applicableTradingModes)
{
SupportedTradingModeOverrides = capabilityTradingModes
};
SetCapabilities(options);
CapabilityOptions = options;
}
public override SharedClientInfo Discover() => new();
}
private sealed class TestOptionsHost : SharedApiBase
{
public TestOptionsHost(TradingMode[] apiTradingModes, CapabilityOptions capabilityOptions)
: base(
SharedTransport.Rest,
"TestExchange",
apiTradingModes,
() => false,
(baseAsset, quoteAsset, tradingMode, deliverDate) => $"{baseAsset}{quoteAsset}")
{
SetCapabilities(capabilityOptions);
}
public override SharedClientInfo Discover() => new();
}
private sealed class TestSharedApiClient : SharedApiClientBase
{
public TestSharedApiClient(params ISharedApiCapability[] sharedApis)
: base([SharedTransport.Rest, SharedTransport.Socket], sharedApis)
{
}
}
}
}
@@ -0,0 +1,89 @@
using CryptoExchange.Net.SharedApis;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
internal class SharedCapabilitiesTests
{
[Test]
public void AllSharedApiCapabilities_ShouldBeListedInSharedCapabilities()
{
var markerTypes = new[]
{
typeof(ISharedApiCapability),
typeof(ISharedRest),
typeof(ISharedSocket),
typeof(ISharedSubscription)
};
var expectedCapabilities = typeof(ISharedApiCapability).Assembly
.GetTypes()
.Where(x => x.IsInterface
&& typeof(ISharedApiCapability).IsAssignableFrom(x)
&& !markerTypes.Contains(x))
.OrderBy(x => x.FullName)
.ToArray();
var listedCapabilities = GetTypeAndNestedTypes(typeof(SharedCapabilities))
.SelectMany(x => x.GetProperties(
BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly))
.SelectMany(x => x.PropertyType.GetGenericArguments())
.Where(x => x.IsInterface
&& typeof(ISharedApiCapability).IsAssignableFrom(x))
.ToArray();
var missingCapabilities = expectedCapabilities
.Except(listedCapabilities)
.Select(x => x.Name)
.ToArray();
var unknownCapabilities = listedCapabilities
.Except(expectedCapabilities)
.Select(x => x.Name)
.ToArray();
var duplicateCapabilities = listedCapabilities
.GroupBy(x => x)
.Where(x => x.Count() > 1)
.Select(x => x.Key.Name)
.ToArray();
Assert.Multiple(() =>
{
Assert.That(
missingCapabilities,
Is.Empty,
$"Capabilities missing from SharedCapabilities: " +
string.Join(", ", missingCapabilities));
Assert.That(
unknownCapabilities,
Is.Empty,
$"Unknown capabilities listed in SharedCapabilities: " +
string.Join(", ", unknownCapabilities));
Assert.That(
duplicateCapabilities,
Is.Empty,
$"Capabilities listed multiple times in SharedCapabilities: " +
string.Join(", ", duplicateCapabilities));
});
}
private static IEnumerable<Type> GetTypeAndNestedTypes(Type type)
{
yield return type;
foreach (var nestedType in type.GetNestedTypes(BindingFlags.Public))
{
foreach (var result in GetTypeAndNestedTypes(nestedType))
yield return result;
}
}
}
}
@@ -0,0 +1,180 @@
using NUnit.Framework;
using CryptoExchange.Net.SharedApis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace CryptoExchange.Net.UnitTests
{
[TestFixture]
internal class SharedParametersTests
{
[Test]
public void AllSharedAPIOptions_ShouldListAllDefaultParametersInRequestParameterRules()
{
var failures = new List<string>();
var optionsTypes = typeof(CapabilityOptions).Assembly
.GetTypes()
.Where(x => x.IsClass
&& !x.IsAbstract
&& typeof(CapabilityOptions).IsAssignableFrom(x))
.OrderBy(x => x.FullName)
.ToArray();
foreach (var optionsType in optionsTypes)
{
var genericOptionsType = GetGenericOptionsType(optionsType);
if (genericOptionsType == null)
{
if (optionsType == typeof(ClosePositionOptions))
continue;
failures.Add($"{optionsType.Name}: unable to determine request type");
continue;
}
var requestType = genericOptionsType.GetGenericArguments()[0];
var defaultRulesField = GetDefaultParameterRulesField(optionsType);
if (defaultRulesField == null)
{
failures.Add($"{optionsType.Name}: no _defaultParameterRules field found");
continue;
}
var rules = (RequestParameterDescription[]?)defaultRulesField.GetValue(null);
if (rules == null)
{
failures.Add($"{optionsType.Name}: _defaultParameterRules is null");
continue;
}
var constructorParameterNames = requestType
.GetConstructors(BindingFlags.Instance | BindingFlags.Public)
.SelectMany(x => x.GetParameters())
.Where(x => !string.Equals(
x.Name,
"exchangeParameters",
StringComparison.OrdinalIgnoreCase))
.Select(x => x.Name!)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var parameterProperties = requestType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.DeclaringType == requestType
|| constructorParameterNames.Contains(x.Name))
.Where(x => x.Name != nameof(SharedRequest.ExchangeParameters))
.Select(x => x.Name)
.OrderBy(x => x)
.ToArray();
var ruleNames = rules
.Select(x => x.Name)
.ToHashSet(StringComparer.Ordinal);
foreach (var parameterProperty in parameterProperties)
{
if (!ruleNames.Contains(parameterProperty))
{
failures.Add(
$"{optionsType.Name}: request property " +
$"{requestType.Name}.{parameterProperty} has no default request parameter rule");
}
}
}
Assert.That(
failures,
Is.Empty,
$"Missing default request parameter rules:{Environment.NewLine}" +
string.Join(Environment.NewLine, failures));
}
[Test]
public void RequiredExchangeParameter_ShouldAcceptNameOrAnyAlias()
{
const string exchange = "TestExchange";
var options = new GetTickerOptions(exchange)
{
ExchangeParameterRules =
[
ExchangeParameterRule.Required(
"Parameter",
"Test parameter",
1,
"Alias1",
"Alias2")
]
};
var missingResult = options.ValidateRequest(null, null, [TradingMode.Spot]);
var nameResult = options.ValidateRequest(
new ExchangeParameters(new ExchangeParameter(exchange, "Parameter", 1)),
null,
[TradingMode.Spot]);
var firstAliasResult = options.ValidateRequest(
new ExchangeParameters(new ExchangeParameter(exchange, "Alias1", 1)),
null,
[TradingMode.Spot]);
var secondAliasResult = options.ValidateRequest(
new ExchangeParameters(new ExchangeParameter(exchange, "Alias2", 1)),
null,
[TradingMode.Spot]);
Assert.Multiple(() =>
{
Assert.That(missingResult, Is.Not.Null);
Assert.That(nameResult, Is.Null);
Assert.That(firstAliasResult, Is.Null);
Assert.That(secondAliasResult, Is.Null);
});
}
[Test]
public void RequiredExchangeParameterWithoutAliases_ShouldBeRequired()
{
const string exchange = "TestExchange";
var options = new GetTickerOptions(exchange)
{
ExchangeParameterRules =
[
ExchangeParameterRule.Required(
"Parameter",
"Test parameter",
1)
]
};
var result = options.ValidateRequest(null, null, [TradingMode.Spot]);
Assert.That(result, Is.Not.Null);
}
private static Type? GetGenericOptionsType(Type optionsType)
{
for (var type = optionsType; type != null; type = type.BaseType)
{
if (type.IsGenericType
&& type.GetGenericTypeDefinition() == typeof(CapabilityOptions<,>))
return type;
}
return null;
}
private static FieldInfo? GetDefaultParameterRulesField(Type optionsType)
{
for (var type = optionsType; type != null; type = type.BaseType)
{
var field = type.GetField(
"_defaultParameterRules",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
if (field != null)
return field;
}
return null;
}
}
}
@@ -0,0 +1,130 @@
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Sockets;
using CryptoExchange.Net.Sockets.Default;
using CryptoExchange.Net.Sockets.Default.Routing;
using CryptoExchange.Net.UnitTests.Implementations;
using Microsoft.Extensions.Logging.Abstractions;
using NUnit.Framework;
using System;
namespace CryptoExchange.Net.UnitTests.SocketRoutingTests
{
[TestFixture]
public class SubscriptionTests
{
[Test]
public void Handle_Should_OnlyCompleteSubscriptionQuery_ForMatchingTopic()
{
// arrange
var topicASubscription = new TopicSubscription("topic-a");
var topicBSubscription = new TopicSubscription("topic-b");
var topicAQuery = topicASubscription.CreateSubscriptionQuery(null!)!;
var topicBQuery = topicBSubscription.CreateSubscriptionQuery(null!)!;
// act
var topicAHandled = topicASubscription.Handle("type", "topic-a", null!, DateTime.UtcNow, "original", "data");
var topicBHandled = topicBSubscription.Handle("type", "topic-a", null!, DateTime.UtcNow, "original", "data");
// assert
Assert.That(topicAHandled, Is.True);
Assert.That(topicBHandled, Is.False);
Assert.That(topicAQuery.Completed, Is.True);
Assert.That(topicAQuery.Success, Is.True);
Assert.That(topicBQuery.Completed, Is.False);
}
[Test]
public void Handle_Should_CompleteSubscriptionQuery_BeforeInvokingMatchingHandler()
{
// arrange
Query? query = null;
var queryCompletedWhenHandlerInvoked = false;
var subscription = new TopicSubscription("topic", () => queryCompletedWhenHandlerInvoked = query!.Completed);
query = subscription.CreateSubscriptionQuery(null!)!;
// act
subscription.Handle("type", "topic", null!, DateTime.UtcNow, "original", "data");
// assert
Assert.That(queryCompletedWhenHandlerInvoked, Is.True);
}
[TestCase(null)]
[TestCase("topic")]
public void Handle_Should_CompleteSubscriptionQuery_ForUnfilteredRoute(string? topicFilter)
{
// arrange
var subscription = new TopicSubscription(topic: null);
var query = subscription.CreateSubscriptionQuery(null!)!;
// act
var handled = subscription.Handle("type", topicFilter, null!, DateTime.UtcNow, "original", "data");
// assert
Assert.That(handled, Is.True);
Assert.That(query.Completed, Is.True);
Assert.That(query.Success, Is.True);
}
[Test]
public void Handle_Should_TreatEmptyTopicFilterAsUnfilteredRoute()
{
// arrange
var subscription = new TopicSubscription(string.Empty);
var query = subscription.CreateSubscriptionQuery(null!)!;
// act
var handled = subscription.Handle("type", "topic", null!, DateTime.UtcNow, "original", "data");
// assert
Assert.That(handled, Is.True);
Assert.That(query.Completed, Is.True);
Assert.That(query.Success, Is.True);
}
[Test]
public void Handle_Should_CompleteSubscriptionQuery_ForAnyMatchingTopic()
{
// arrange
var subscription = new TopicSubscription(["topic-a", "topic-b"]);
var query = subscription.CreateSubscriptionQuery(null!)!;
// act
var handled = subscription.Handle("type", "topic-b", null!, DateTime.UtcNow, "original", "data");
// assert
Assert.That(handled, Is.True);
Assert.That(query.Completed, Is.True);
Assert.That(query.Success, Is.True);
}
private sealed class TopicSubscription : Subscription
{
public TopicSubscription(string? topic, Action? handler = null)
: base(NullLogger.Instance, false)
{
MessageRouter = MessageRouter.CreateForEvent<string>("type", topic, (_, _, _, _) =>
{
handler?.Invoke();
return CallResult.Ok();
});
}
public TopicSubscription(string[] topics)
: base(NullLogger.Instance, false)
{
MessageRouter = MessageRouter.CreateForEvent<string>("type", topics, (_, _, _, _) => CallResult.Ok());
}
protected override Query? GetSubQuery(SocketConnection connection)
{
return new TestQuery(new TestSocketMessage { Id = 1, Data = "Sub" }, false)
{
TimeoutBehavior = TimeoutBehavior.Succeed
};
}
protected override Query? GetUnsubQuery(SocketConnection connection) => null;
}
}
}
+106 -3
View File
@@ -98,6 +98,10 @@ namespace CryptoExchange.Net.Clients
/// Configured environment name /// Configured environment name
/// </summary> /// </summary>
public abstract string EnvironmentName { get; } public abstract string EnvironmentName { get; }
/// <summary>
/// Request coalescer
/// </summary>
protected RequestCoalescer RequestCoalescer { get; }
/// <summary> /// <summary>
/// ctor /// ctor
@@ -123,6 +127,7 @@ namespace CryptoExchange.Net.Clients
{ {
TimeOffsetManager.RegisterRestApi(ClientName); TimeOffsetManager.RegisterRestApi(ClientName);
RequestCoalescer = new RequestCoalescer(_logger);
RequestFactory.Configure(options, httpClient); RequestFactory.Configure(options, httpClient);
} }
@@ -177,8 +182,7 @@ namespace CryptoExchange.Net.Clients
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param> /// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param> /// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
/// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param> /// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
/// <returns></returns> protected virtual Task<HttpResult<T>> SendAsync<T>(
protected virtual async Task<HttpResult<T>> SendAsync<T>(
RequestDefinition definition, RequestDefinition definition,
Parameters? uriParameters, Parameters? uriParameters,
Parameters? bodyParameters, Parameters? bodyParameters,
@@ -187,6 +191,61 @@ namespace CryptoExchange.Net.Clients
int? weight = null, int? weight = null,
int? weightSingleLimiter = null, int? weightSingleLimiter = null,
string? rateLimitKeySuffix = null) string? rateLimitKeySuffix = null)
{
if (!ShouldCoalesce(definition, additionalHeaders))
{
return SendCoreAsync<T>(
definition,
uriParameters,
bodyParameters,
cancellationToken,
additionalHeaders,
weight,
weightSingleLimiter,
rateLimitKeySuffix);
}
var key = GetRequestCoalescingKey<T>(
definition,
uriParameters);
return RequestCoalescer.ExecuteAsync(
key,
requestCancellationToken => SendCoreAsync<T>(
definition,
uriParameters,
bodyParameters,
requestCancellationToken,
additionalHeaders,
weight,
weightSingleLimiter,
rateLimitKeySuffix),
() => HttpResult.Fail<T>(
Exchange,
new CancellationRequestedError()),
cancellationToken);
}
/// <summary>
/// Send a request to the base address based on the request definition
/// </summary>
/// <typeparam name="T">Response type</typeparam>
/// <param name="definition">Request definition</param>
/// <param name="uriParameters">Request query parameters</param>
/// <param name="bodyParameters">Request body parameters</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="additionalHeaders">Additional headers for this request</param>
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
/// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
protected virtual async Task<HttpResult<T>> SendCoreAsync<T>(RequestDefinition definition,
Parameters? uriParameters,
Parameters? bodyParameters,
CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null,
int? weight = null,
int? weightSingleLimiter = null,
string? rateLimitKeySuffix = null)
{ {
var requestId = ExchangeHelpers.NextId(); var requestId = ExchangeHelpers.NextId();
if (definition.Authenticated && GetAuthenticationProvider() == null) if (definition.Authenticated && GetAuthenticationProvider() == null)
@@ -198,7 +257,7 @@ namespace CryptoExchange.Net.Clients
string? cacheKey = null; string? cacheKey = null;
if (ShouldCache(definition)) if (ShouldCache(definition))
{ {
cacheKey = definition.FullUrl + definition + uriParameters?.ToFormData(); cacheKey = GetCacheKey(definition, uriParameters);
_logger.CheckingCache(cacheKey); _logger.CheckingCache(cacheKey);
var cachedValue = _cache.Get(cacheKey, ClientOptions.CachingMaxAge); var cachedValue = _cache.Get(cacheKey, ClientOptions.CachingMaxAge);
if (cachedValue != null) if (cachedValue != null)
@@ -688,7 +747,10 @@ namespace CryptoExchange.Net.Clients
stringData = stringSerializer.Serialize(parameters.BodyValue); stringData = stringSerializer.Serialize(parameters.BodyValue);
} }
else else
{
stringData = stringSerializer.Serialize(parameters); stringData = stringSerializer.Serialize(parameters);
}
request.SetContent(stringData, RequestBodyContentEncoding, contentType); request.SetContent(stringData, RequestBodyContentEncoding, contentType);
} }
else if (contentType == Constants.FormContentHeader) else if (contentType == Constants.FormContentHeader)
@@ -800,6 +862,41 @@ namespace CryptoExchange.Net.Clients
&& definition.Method == HttpMethod.Get && definition.Method == HttpMethod.Get
&& !definition.PreventCaching; && !definition.PreventCaching;
/// <summary>
/// Produce a unique cache key for the request based on the request definition and parameters.
/// </summary>
protected virtual string GetCacheKey(RequestDefinition definition, Parameters? parameters)
{
return definition.FullUrl + definition + parameters?.ToFormData();
}
/// <summary>
/// Whether a request can be coalesced or not
/// </summary>
protected virtual bool ShouldCoalesce(
RequestDefinition definition,
Dictionary<string, string>? additionalHeaders)
{
return ClientOptions.RequestCoalescingEnabled // Enabled in client options
&& !definition.PreventRequestCoalescing // Enabled in request definition
&& definition.Method == HttpMethod.Get // Is a GET request
&& !definition.Authenticated // Not an authenticated request
&& (additionalHeaders == null || additionalHeaders.Count == 0); // No additional headers that might change the response
}
/// <summary>
/// Get a unique key for coalescing requests based on the request definition, parameters, and the type of the expected response.
/// </summary>
protected virtual RequestCoalescingKey GetRequestCoalescingKey<T>(
RequestDefinition definition,
Parameters? uriParameters)
{
return new RequestCoalescingKey(
definition.Method.Method,
definition.FullUrl,
uriParameters?.ToFormData(),
typeof(T));
}
/// <inheritdoc /> /// <inheritdoc />
public virtual void SetOptions(UpdateOptions options) public virtual void SetOptions(UpdateOptions options)
@@ -889,6 +986,12 @@ namespace CryptoExchange.Net.Clients
ApiCredentials = (TApiCredentials?)options.ApiCredentials?.Copy() ?? ApiCredentials; ApiCredentials = (TApiCredentials?)options.ApiCredentials?.Copy() ?? ApiCredentials;
} }
/// <inheritdoc />
protected override string GetCacheKey(RequestDefinition definition, Parameters? parameters)
{
return definition.FullUrl + definition + parameters?.ToFormData() + GetAuthenticationProvider()?.Key;
}
} }
/// <inheritdoc /> /// <inheritdoc />
+10 -1
View File
@@ -97,6 +97,11 @@ namespace CryptoExchange.Net.Clients
/// </summary> /// </summary>
protected bool AllowTopicsOnTheSameConnection { get; set; } = true; protected bool AllowTopicsOnTheSameConnection { get; set; } = true;
/// <summary>
/// Whether to allow usage of a current public WebSocket connection for an authenticated request
/// </summary>
protected bool UsePublicConnectionForAuth { get; set; } = false;
/// <inheritdoc /> /// <inheritdoc />
public double IncomingKbps public double IncomingKbps
{ {
@@ -688,7 +693,7 @@ namespace CryptoExchange.Net.Clients
_logger.WaitedForReconnectingSocket((long)(DateTime.UtcNow - delayStart).TotalMilliseconds); _logger.WaitedForReconnectingSocket((long)(DateTime.UtcNow - delayStart).TotalMilliseconds);
socketQuery = socketQuery.Where(s => (s.Status == SocketStatus.None || s.Status == SocketStatus.Connected) socketQuery = socketQuery.Where(s => (s.Status == SocketStatus.None || s.Status == SocketStatus.Connected)
&& (s.Authenticated == authenticated || !authenticated) && (s.Authenticated == authenticated || !authenticated || UsePublicConnectionForAuth)
&& s.Connected).ToList(); && s.Connected).ToList();
bool maxConnectionsReached = _socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections); bool maxConnectionsReached = _socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections);
@@ -702,12 +707,14 @@ namespace CryptoExchange.Net.Clients
} }
if (connection == null) if (connection == null)
{
// Use an eligible non-dedicated connection for subscriptions, or as fallback when no dedicated request connection is available // Use an eligible non-dedicated connection for subscriptions, or as fallback when no dedicated request connection is available
connection = socketQuery connection = socketQuery
.Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection) .Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection)
.Where(s => IsConnectionEligible(s, individualSubscriptionCount, maxConnectionsReached)) .Where(s => IsConnectionEligible(s, individualSubscriptionCount, maxConnectionsReached))
.OrderBy(s => s.UserSubscriptionCount) .OrderBy(s => s.UserSubscriptionCount)
.FirstOrDefault(); .FirstOrDefault();
}
if (connection != null) if (connection != null)
return CallResult.Ok(connection); return CallResult.Ok(connection);
@@ -786,7 +793,9 @@ namespace CryptoExchange.Net.Clients
if ((!lessThanBatchSubCombineTarget || !lessThanIndividualSubCombineTarget) if ((!lessThanBatchSubCombineTarget || !lessThanIndividualSubCombineTarget)
&& !maxConnectionsReached) && !maxConnectionsReached)
{
return false; return false;
}
return MaxIndividualSubscriptionsPerConnection == null return MaxIndividualSubscriptionsPerConnection == null
|| currentIndividualSubscriptionCount + individualSubscriptionCount <= MaxIndividualSubscriptionsPerConnection; || currentIndividualSubscriptionCount + individualSubscriptionCount <= MaxIndividualSubscriptionsPerConnection;
+100 -60
View File
@@ -14,21 +14,19 @@ using System.Text;
namespace CryptoExchange.Net.Clients namespace CryptoExchange.Net.Clients
{ {
/// <inheritdoc /> /// <inheritdoc />
public abstract class UserClientProvider<TRestClient, TSocketClient, TRestOptions, TSocketOptions, TCredentials, TEnvironment> public abstract class UserClientProvider<TRestClient, TRestOptions, TCredentials, TEnvironment>
where TRestClient : IRestClient<TCredentials> where TRestClient : IRestClient<TCredentials>
where TSocketClient : ISocketClient<TCredentials>
where TRestOptions : RestExchangeOptions<TEnvironment, TCredentials>, new() where TRestOptions : RestExchangeOptions<TEnvironment, TCredentials>, new()
where TSocketOptions : SocketExchangeOptions<TEnvironment, TCredentials>, new()
where TCredentials : ApiCredentials where TCredentials : ApiCredentials
where TEnvironment : TradeEnvironment where TEnvironment : TradeEnvironment
{ {
private ConcurrentDictionary<string, TRestClient> _restClients = new ConcurrentDictionary<string, TRestClient>(); private ConcurrentDictionary<string, TRestClient> _restClients = new ConcurrentDictionary<string, TRestClient>();
private ConcurrentDictionary<string, TSocketClient> _socketClients = new ConcurrentDictionary<string, TSocketClient>();
private readonly IOptions<TRestOptions> _restOptions; private readonly IOptions<TRestOptions> _restOptions;
private readonly IOptions<TSocketOptions> _socketOptions;
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ILoggerFactory? _loggerFactory; /// <summary>
/// Logger factory
/// </summary>
protected readonly ILoggerFactory? _loggerFactory;
/// <inheritdoc /> /// <inheritdoc />
public abstract string ExchangeName { get; } public abstract string ExchangeName { get; }
@@ -39,17 +37,14 @@ namespace CryptoExchange.Net.Clients
public UserClientProvider( public UserClientProvider(
HttpClient? httpClient, HttpClient? httpClient,
ILoggerFactory? loggerFactory, ILoggerFactory? loggerFactory,
IOptions<TRestOptions> restOptions, IOptions<TRestOptions> restOptions)
IOptions<TSocketOptions> socketOptions)
{ {
_httpClient = httpClient ?? new HttpClient(); _httpClient = httpClient ?? new HttpClient();
_httpClient.Timeout = restOptions.Value.RequestTimeout; _httpClient.Timeout = restOptions.Value.RequestTimeout;
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_restOptions = restOptions; _restOptions = restOptions;
_socketOptions = socketOptions;
} }
private IOptions<TRestOptions> SetRestEnvironment(IOptions<TRestOptions> options, TEnvironment? environment) private IOptions<TRestOptions> SetRestEnvironment(IOptions<TRestOptions> options, TEnvironment? environment)
{ {
if (environment == null) if (environment == null)
@@ -61,22 +56,10 @@ namespace CryptoExchange.Net.Clients
return Options.Create(newRestClientOptions); return Options.Create(newRestClientOptions);
} }
private IOptions<TSocketOptions> SetSocketEnvironment(IOptions<TSocketOptions> options, TEnvironment? environment)
{
if (environment == null)
return options;
var newSocketClientOptions = new TSocketOptions();
options.Value.Set(newSocketClientOptions);
newSocketClientOptions.Environment = environment;
return Options.Create(newSocketClientOptions);
}
/// <inheritdoc /> /// <inheritdoc />
public void InitializeUserClient(string userIdentifier, TCredentials credentials, TEnvironment? environment = null) public virtual void InitializeUserClient(string userIdentifier, TCredentials credentials, TEnvironment? environment = null)
{ {
CreateRestClient(userIdentifier, credentials, environment); CreateRestClient(userIdentifier, credentials, environment);
CreateSocketClient(userIdentifier, credentials, environment);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -88,15 +71,6 @@ namespace CryptoExchange.Net.Clients
return client; return client;
} }
/// <inheritdoc />
public TSocketClient GetSocketClient(string userIdentifier, TCredentials? credentials = null, TEnvironment? environment = null)
{
if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateSocketClient(userIdentifier, credentials, environment);
return client;
}
private TRestClient CreateRestClient(string userIdentifier, TCredentials? credentials, TEnvironment? environment) private TRestClient CreateRestClient(string userIdentifier, TCredentials? credentials, TEnvironment? environment)
{ {
var clientRestOptions = SetRestEnvironment(_restOptions, environment); var clientRestOptions = SetRestEnvironment(_restOptions, environment);
@@ -109,6 +83,94 @@ namespace CryptoExchange.Net.Clients
return client; return client;
} }
/// <summary>
/// Constructs a new instance of the rest client
/// </summary>
protected abstract TRestClient ConstructRestClient(
HttpClient client,
ILoggerFactory? loggerFactory,
IOptions<TRestOptions> options);
/// <inheritdoc />
public virtual void ClearUserClients(string userIdentifier)
{
_restClients.TryRemove(userIdentifier, out var restClient);
restClient?.Dispose();
}
/// <inheritdoc />
public virtual void Clear()
{
foreach (var client in _restClients.Values)
client.Dispose();
_restClients.Clear();
}
/// <summary>
/// Applies the provided options delegate to a new instance of the specified type.
/// </summary>
protected static T ApplyOptionsDelegate<T>(Action<T>? del) where T : new()
{
var opts = new T();
del?.Invoke(opts);
return opts;
}
}
/// <inheritdoc />
public abstract class UserClientProvider<TRestClient, TSocketClient, TRestOptions, TSocketOptions, TCredentials, TEnvironment>
: UserClientProvider<TRestClient, TRestOptions, TCredentials, TEnvironment>
where TRestClient : IRestClient<TCredentials>
where TSocketClient : ISocketClient<TCredentials>
where TRestOptions : RestExchangeOptions<TEnvironment, TCredentials>, new()
where TSocketOptions : SocketExchangeOptions<TEnvironment, TCredentials>, new()
where TCredentials : ApiCredentials
where TEnvironment : TradeEnvironment
{
private ConcurrentDictionary<string, TSocketClient> _socketClients = new ConcurrentDictionary<string, TSocketClient>();
private readonly IOptions<TSocketOptions> _socketOptions;
/// <summary>
/// ctor
/// </summary>
public UserClientProvider(
HttpClient? httpClient,
ILoggerFactory? loggerFactory,
IOptions<TRestOptions> restOptions,
IOptions<TSocketOptions> socketOptions)
: base(httpClient, loggerFactory, restOptions)
{
_socketOptions = socketOptions;
}
private IOptions<TSocketOptions> SetSocketEnvironment(IOptions<TSocketOptions> options, TEnvironment? environment)
{
if (environment == null)
return options;
var newSocketClientOptions = new TSocketOptions();
options.Value.Set(newSocketClientOptions);
newSocketClientOptions.Environment = environment;
return Options.Create(newSocketClientOptions);
}
/// <inheritdoc />
public override void InitializeUserClient(string userIdentifier, TCredentials credentials, TEnvironment? environment = null)
{
base.InitializeUserClient(userIdentifier, credentials, environment);
CreateSocketClient(userIdentifier, credentials, environment);
}
/// <inheritdoc />
public TSocketClient GetSocketClient(string userIdentifier, TCredentials? credentials = null, TEnvironment? environment = null)
{
if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed)
client = CreateSocketClient(userIdentifier, credentials, environment);
return client;
}
private TSocketClient CreateSocketClient(string userIdentifier, TCredentials? credentials, TEnvironment? environment) private TSocketClient CreateSocketClient(string userIdentifier, TCredentials? credentials, TEnvironment? environment)
{ {
var clientSocketOptions = SetSocketEnvironment(_socketOptions, environment); var clientSocketOptions = SetSocketEnvironment(_socketOptions, environment);
@@ -121,15 +183,6 @@ namespace CryptoExchange.Net.Clients
return client; return client;
} }
/// <summary>
/// Constructs a new instance of the rest client
/// </summary>
protected abstract TRestClient ConstructRestClient(
HttpClient client,
ILoggerFactory? loggerFactory,
IOptions<TRestOptions> options);
/// <summary> /// <summary>
/// Constructs a new instance of the socket client /// Constructs a new instance of the socket client
/// </summary> /// </summary>
@@ -137,36 +190,23 @@ namespace CryptoExchange.Net.Clients
ILoggerFactory? loggerFactory, ILoggerFactory? loggerFactory,
IOptions<TSocketOptions> options); IOptions<TSocketOptions> options);
/// <inheritdoc /> /// <inheritdoc />
public void ClearUserClients(string userIdentifier) public override void ClearUserClients(string userIdentifier)
{ {
_restClients.TryRemove(userIdentifier, out var restClient); base.ClearUserClients(userIdentifier);
_socketClients.TryRemove(userIdentifier, out var socketClient); _socketClients.TryRemove(userIdentifier, out var socketClient);
restClient?.Dispose();
socketClient?.Dispose(); socketClient?.Dispose();
} }
/// <inheritdoc /> /// <inheritdoc />
public void Clear() public override void Clear()
{ {
foreach (var client in _restClients.Values) base.Clear();
client.Dispose();
_restClients.Clear();
foreach (var client in _socketClients.Values) foreach (var client in _socketClients.Values)
client.Dispose(); client.Dispose();
_socketClients.Clear(); _socketClients.Clear();
} }
/// <summary>
/// Applies the provided options delegate to a new instance of the specified type.
/// </summary>
protected static T ApplyOptionsDelegate<T>(Action<T>? del) where T : new()
{
var opts = new T();
del?.Invoke(opts);
return opts;
}
} }
} }
+3 -3
View File
@@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description> <Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>12.4.0</PackageVersion> <PackageVersion>12.5.1</PackageVersion>
<AssemblyVersion>12.4.0</AssemblyVersion> <AssemblyVersion>12.5.1</AssemblyVersion>
<FileVersion>12.4.0</FileVersion> <FileVersion>12.5.1</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags> <PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
+46 -1
View File
@@ -4,6 +4,7 @@ using CryptoExchange.Net.SharedApis;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
@@ -395,7 +396,6 @@ namespace CryptoExchange.Net
services.AddTransient(x => (IBookTickerRestClient)client(x)!); services.AddTransient(x => (IBookTickerRestClient)client(x)!);
if (typeof(ITransferRestClient).IsAssignableFrom(typeof(T))) if (typeof(ITransferRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITransferRestClient)client(x)!); services.AddTransient(x => (ITransferRestClient)client(x)!);
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderRestClient)client(x)!); services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotSymbolRestClient).IsAssignableFrom(typeof(T)))
@@ -461,11 +461,56 @@ namespace CryptoExchange.Net
if (typeof(ISpotOrderSocketClient).IsAssignableFrom(typeof(T))) if (typeof(ISpotOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderSocketClient)client(x)!); services.AddTransient(x => (ISpotOrderSocketClient)client(x)!);
if (typeof(ISpotOrderManagementSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderManagementSocketClient)client(x)!);
if (typeof(IFuturesOrderSocketClient).IsAssignableFrom(typeof(T))) if (typeof(IFuturesOrderSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!); services.AddTransient(x => (IFuturesOrderSocketClient)client(x)!);
if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T))) if (typeof(IPositionSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IPositionSocketClient)client(x)!); services.AddTransient(x => (IPositionSocketClient)client(x)!);
if (typeof(IFuturesOrderManagementSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFuturesOrderManagementSocketClient)client(x)!);
return services;
}
/// <summary>
/// Registers a strict Shared API aggregate and all capability interfaces implemented by it.
/// </summary>
public static IServiceCollection RegisterSharedApi<
#if NET5_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]
#endif
TSharedApi>(
this IServiceCollection services,
Func<IServiceProvider, TSharedApi> factory)
where TSharedApi : class, ISharedApi
{
services.AddTransient<TSharedApi>(factory);
var markerInterfaces = new[]
{
typeof(ISharedApiCapability),
typeof(ISharedRest),
typeof(ISharedSocket),
typeof(ISharedSubscription)
};
var capabilityInterfaces = typeof(TSharedApi)
.GetInterfaces()
.Where(x =>
typeof(ISharedApiCapability).IsAssignableFrom(x)
&& !markerInterfaces.Contains(x))
.Distinct()
.ToArray();
foreach (var capabilityInterface in capabilityInterfaces)
{
services.AddTransient(
capabilityInterface,
serviceProvider =>
serviceProvider.GetRequiredService<TSharedApi>());
}
return services; return services;
} }
@@ -22,6 +22,7 @@ namespace CryptoExchange.Net.Logging.Extensions
private static readonly Action<ILogger, string, Exception?> _restApiCacheHit; private static readonly Action<ILogger, string, Exception?> _restApiCacheHit;
private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit; private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit;
private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested; private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested;
private static readonly Action<ILogger, string, string, Exception?> _restApiRequestsJoined;
static RestApiClientLoggingExtensions() static RestApiClientLoggingExtensions()
{ {
@@ -90,6 +91,10 @@ namespace CryptoExchange.Net.Logging.Extensions
new EventId(4012, "RestApiCancellationRequested"), new EventId(4012, "RestApiCancellationRequested"),
"[Req {RequestId}] request cancelled by user"); "[Req {RequestId}] request cancelled by user");
_restApiRequestsJoined = LoggerMessage.Define<string, string>(
LogLevel.Debug,
new EventId(4013, "RestApiRequestsJoined"),
"Request for {Method} {Path} joined with in flight request");
} }
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception) public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error, string? originalData, Exception? exception)
@@ -151,9 +156,15 @@ namespace CryptoExchange.Net.Logging.Extensions
{ {
_restApiCacheNotHit(logger, key, null); _restApiCacheNotHit(logger, key, null);
} }
public static void RestApiCancellationRequested(this ILogger logger, int? requestId) public static void RestApiCancellationRequested(this ILogger logger, int? requestId)
{ {
_restApiCancellationRequested(logger, requestId, null); _restApiCancellationRequested(logger, requestId, null);
} }
public static void RestApiRequestsJoined(this ILogger logger, string method, string path)
{
_restApiRequestsJoined(logger, method, path, null);
}
} }
} }
@@ -180,7 +180,7 @@ namespace CryptoExchange.Net.Logging.Extensions
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string, string>( _receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string, string>(
LogLevel.Warning, LogLevel.Warning,
new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"), new EventId(2029, "ReceivedMessageNotMatchedToAnyListener"),
"[Sckt {SocketId}] received message not matched to any listener. TypeIdentifier: {TypeIdentifier}, ListenId: {ListenId}, current listeners: [{ListenIds}]"); "[Sckt {SocketId}] received message not matched to any listener. TypeIdentifier: {TypeIdentifier}, TopicFilter: {ListenId}, registered TopicFilters for type: [{TopicFilters}]");
_failedToParse = LoggerMessage.Define<int, string>( _failedToParse = LoggerMessage.Define<int, string>(
LogLevel.Warning, LogLevel.Warning,
@@ -326,9 +326,9 @@ namespace CryptoExchange.Net.Logging.Extensions
_sendingData(logger, socketId, requestId, data, null); _sendingData(logger, socketId, requestId, data, null);
} }
public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string typeIdentifier, string listenId, string listenIds) public static void ReceivedMessageNotMatchedToAnyListener(this ILogger logger, int socketId, string typeIdentifier, string topicFilter, string topicFilters)
{ {
_receivedMessageNotMatchedToAnyListener(logger, socketId, typeIdentifier, listenId, listenIds, null); _receivedMessageNotMatchedToAnyListener(logger, socketId, typeIdentifier, topicFilter, topicFilters, null);
} }
public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length) public static void SendingByteData(this ILogger logger, int socketId, int requestId, int length)
@@ -34,6 +34,11 @@ namespace CryptoExchange.Net.Objects.Options
= new Version(1, 1); = new Version(1, 1);
#endif #endif
/// <summary>
/// Whether concurrent identical REST requests may share the same in-flight request when allowed by the request definition.
/// </summary>
public bool RequestCoalescingEnabled { get; set; } = true;
/// <summary> /// <summary>
/// Http client keep alive interval for keeping connections open. Only applied when using dotnet8.0 or higher and dependency injection /// Http client keep alive interval for keeping connections open. Only applied when using dotnet8.0 or higher and dependency injection
/// </summary> /// </summary>
@@ -73,6 +78,7 @@ namespace CryptoExchange.Net.Objects.Options
item.CachingEnabled = CachingEnabled; item.CachingEnabled = CachingEnabled;
item.CachingMaxAge = CachingMaxAge; item.CachingMaxAge = CachingMaxAge;
item.HttpVersion = HttpVersion; item.HttpVersion = HttpVersion;
item.RequestCoalescingEnabled = RequestCoalescingEnabled;
item.HttpKeepAliveInterval = HttpKeepAliveInterval; item.HttpKeepAliveInterval = HttpKeepAliveInterval;
#if NET5_0_OR_GREATER #if NET5_0_OR_GREATER
item.HttpMaxConnectionsPerServer = HttpMaxConnectionsPerServer; item.HttpMaxConnectionsPerServer = HttpMaxConnectionsPerServer;
@@ -0,0 +1,277 @@
using CryptoExchange.Net.Logging.Extensions;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects
{
/// <summary>
/// Request coalescer, used to detect multiple identical requests and wait for and return only a single actual request result
/// </summary>
public class RequestCoalescer
{
private sealed class RequestEntry
{
public RequestCoalescingKey Key { get; }
public CancellationTokenSource CancellationSource { get; } = new();
public Lazy<Task<object>> Request { get; set; } = null!;
public int ListenerCount { get; set; }
public bool Completed { get; set; }
public bool CancellationInProgress { get; set; }
public bool Disposed { get; set; }
public RequestEntry(RequestCoalescingKey key)
{
Key = key;
}
}
private readonly object _sync = new();
private readonly ILogger _logger;
private readonly Dictionary<RequestCoalescingKey, RequestEntry> _requests = new();
/// <summary>
/// ctor
/// </summary>
public RequestCoalescer(ILogger logger)
{
_logger = logger;
}
/// <summary>
/// Execute a request, coalescing identical requests into a single request
/// </summary>
public Task<TResult> ExecuteAsync<TResult>(
RequestCoalescingKey key,
Func<CancellationToken, Task<TResult>> requestFactory,
Func<TResult> cancellationResultFactory,
CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromResult(cancellationResultFactory());
RequestEntry entry;
bool joined;
lock (_sync)
{
joined = _requests.TryGetValue(key, out entry!);
if (!joined)
{
entry = CreateEntry(key, requestFactory);
_requests.Add(key, entry);
}
entry.ListenerCount++;
}
if (joined)
_logger.RestApiRequestsJoined(key.Method, key.Url);
return WaitAsync(
entry,
cancellationResultFactory,
cancellationToken);
}
private RequestEntry CreateEntry<TResult>(
RequestCoalescingKey key,
Func<CancellationToken, Task<TResult>> requestFactory)
{
var entry = new RequestEntry(key);
entry.Request = new Lazy<Task<object>>(
() => ExecuteCoreAsync(
entry,
async requestCancellationToken =>
(await requestFactory(requestCancellationToken).ConfigureAwait(false))!),
LazyThreadSafetyMode.ExecutionAndPublication);
return entry;
}
private async Task<object> ExecuteCoreAsync(
RequestEntry entry,
Func<CancellationToken, Task<object>> requestFactory)
{
try
{
return await requestFactory(entry.CancellationSource.Token).ConfigureAwait(false);
}
finally
{
CompleteRequest(entry);
}
}
private async Task<TResult> WaitAsync<TResult>(
RequestEntry entry,
Func<TResult> cancellationResultFactory,
CancellationToken cancellationToken)
{
try
{
var requestTask = entry.Request.Value;
if (!cancellationToken.CanBeCanceled)
return (TResult)await requestTask.ConfigureAwait(false);
var cancellationCompletion = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
using (cancellationToken.Register(
state => ((TaskCompletionSource<bool>)state!).TrySetResult(true),
cancellationCompletion))
{
var completedTask = await Task.WhenAny(requestTask, cancellationCompletion.Task).ConfigureAwait(false);
if (completedTask != requestTask)
return cancellationResultFactory();
}
return (TResult)await requestTask.ConfigureAwait(false);
}
finally
{
ReleaseListener(entry);
}
}
private void ReleaseListener(RequestEntry entry)
{
var cancelRequest = false;
var disposeCancellationSource = false;
lock (_sync)
{
entry.ListenerCount--;
if (entry.ListenerCount == 0)
{
if (!entry.Completed)
{
// Remove the entry before canceling it. A caller arriving
// after this point must create a new physical request
// instead of joining one that is being canceled.
RemoveEntry(entry);
entry.CancellationInProgress = true;
cancelRequest = true;
}
else if (!entry.Disposed)
{
entry.Disposed = true;
disposeCancellationSource = true;
}
}
}
if (cancelRequest)
{
try
{
entry.CancellationSource.Cancel();
}
catch (Exception exception)
{
// Cancellation callbacks are external to the coalescer and
// must not cause an exception to escape the library.
_logger.LogWarning(
exception,
"Error canceling in-flight request {Method} {Url}",
entry.Key.Method,
entry.Key.Url);
}
finally
{
CompleteCancellation(entry);
}
}
else if (disposeCancellationSource)
{
entry.CancellationSource.Dispose();
}
}
private void CompleteCancellation(RequestEntry entry)
{
var disposeCancellationSource = false;
lock (_sync)
{
entry.CancellationInProgress = false;
if (entry.Completed
&& entry.ListenerCount == 0
&& !entry.Disposed)
{
entry.Disposed = true;
disposeCancellationSource = true;
}
}
if (disposeCancellationSource)
entry.CancellationSource.Dispose();
}
private void CompleteRequest(RequestEntry entry)
{
var disposeCancellationSource = false;
lock (_sync)
{
entry.Completed = true;
// Only remove this specific entry. If all listeners canceled,
// a replacement request may already have been created for the
// same key.
RemoveEntry(entry);
if (entry.ListenerCount == 0
&& !entry.CancellationInProgress
&& !entry.Disposed)
{
entry.Disposed = true;
disposeCancellationSource = true;
}
}
if (disposeCancellationSource)
entry.CancellationSource.Dispose();
}
/// <summary>
/// Remove an entry when it is still the current entry for its key.
/// Must only be called while holding <see cref="_sync"/>.
/// </summary>
private void RemoveEntry(RequestEntry entry)
{
if (_requests.TryGetValue(entry.Key, out var current)
&& ReferenceEquals(current, entry))
{
_requests.Remove(entry.Key);
}
}
}
/// <summary>
/// Key used to identify identical requests for coalescing
/// </summary>
/// <param name="Method">Request method</param>
/// <param name="Url">Request URL</param>
/// <param name="Parameters">Request parameter string</param>
/// <param name="ResponseType">Response type</param>
public sealed record RequestCoalescingKey(
string Method,
string Url,
string? Parameters,
Type ResponseType);
}
@@ -101,6 +101,11 @@ namespace CryptoExchange.Net.Objects
} }
} }
/// <summary>
/// Whether concurrent identical requests should be prevented from sharing the same request/response
/// </summary>
public bool PreventRequestCoalescing { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@@ -53,6 +53,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param> /// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <param name="forcePathEndWithSlash">Force trailing `/`</param> /// <param name="forcePathEndWithSlash">Force trailing `/`</param>
/// <param name="identifier">Optional request identifier override</param> /// <param name="identifier">Optional request identifier override</param>
/// <param name="preventRequestCoalescing">Whether concurrent identical requests should be prevented from sharing the same request/response</param>
/// <returns></returns> /// <returns></returns>
public RequestDefinition GetOrCreate( public RequestDefinition GetOrCreate(
HttpMethod method, HttpMethod method,
@@ -68,7 +69,8 @@ namespace CryptoExchange.Net.Objects
bool? preventCaching = null, bool? preventCaching = null,
bool? tryParseOnNonSuccess = null, bool? tryParseOnNonSuccess = null,
bool? forcePathEndWithSlash = null, bool? forcePathEndWithSlash = null,
string? identifier = null) string? identifier = null,
bool? preventRequestCoalescing = null)
{ {
var identifierToUse = identifier ?? $"{path}{method.Method}{baseAddress}"; var identifierToUse = identifier ?? $"{path}{method.Method}{baseAddress}";
if (!_definitions.TryGetValue(identifierToUse, out var def)) if (!_definitions.TryGetValue(identifierToUse, out var def))
@@ -85,6 +87,7 @@ namespace CryptoExchange.Net.Objects
PreventCaching = preventCaching ?? false, PreventCaching = preventCaching ?? false,
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false, TryParseOnNonSuccess = tryParseOnNonSuccess ?? false,
ForcePathEndWithSlash = forcePathEndWithSlash ?? false, ForcePathEndWithSlash = forcePathEndWithSlash ?? false,
PreventRequestCoalescing = preventRequestCoalescing ?? false,
}; };
_definitions.TryAdd(identifierToUse, def); _definitions.TryAdd(identifierToUse, def);
} }
@@ -187,7 +187,7 @@ public record WebSocketResult<T> : WebSocketResult, IWebSocketResult<T>
} }
/// <inheritdoc /> /// <inheritdoc />
public record QueryResult : WebSocketResult public record QueryResult : WebSocketResult, IQueryResult
{ {
/// <summary> /// <summary>
/// ctor /// ctor
@@ -212,6 +212,8 @@ public record QueryResult : WebSocketResult
Url = result.Url, Url = result.Url,
RequestId = result.RequestId, RequestId = result.RequestId,
ResponseTime = result.ResponseTime, ResponseTime = result.ResponseTime,
OriginalData = result.OriginalData,
RequestBody = result.RequestBody
}; };
/// <summary> /// <summary>
@@ -292,6 +294,9 @@ public record QueryResult : WebSocketResult
/// <inheritdoc /> /// <inheritdoc />
public string? RequestBody { get; init; } public string? RequestBody { get; init; }
/// <inheritdoc />
public string? OriginalData { get; init; }
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -318,7 +323,4 @@ public record QueryResult<T> : QueryResult, IQueryResult<T>
public new bool Success => Error == null; public new bool Success => Error == null;
/// <inheritdoc /> /// <inheritdoc />
public T? Data { get; set; } public T? Data { get; set; }
/// <inheritdoc />
public string? OriginalData { get; init; }
} }
@@ -165,7 +165,10 @@ namespace CryptoExchange.Net.Objects.Sockets
{ {
StreamId = StreamId, StreamId = StreamId,
UpdateType = UpdateType, UpdateType = UpdateType,
Symbol = Symbol Symbol = Symbol,
DataTime = DataTime,
DataTimeLocal = DataTimeLocal,
SequenceNumber = SequenceNumber
}; };
} }
@@ -0,0 +1,209 @@
using CryptoExchange.Net.Sockets;
using CryptoExchange.Net.Sockets.Default;
using CryptoExchange.Net.Sockets.Default.Routing;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Objects.Sockets
{
/// <summary>
/// Controller for an update subscription which isn't backed by a websocket connection. Can be used for testing.
/// </summary>
public class ManualUpdateSubscription
{
private readonly Func<Task> _closeAsync;
private readonly Func<Task> _reconnectAsync;
private readonly Func<Task<CallResult>> _resubscribeAsync;
private readonly ManualSubscription _manualSubscription;
private int _closedEventInvoked;
/// <summary>
/// The update subscription
/// </summary>
public UpdateSubscription Subscription { get; }
/// <summary>
/// The virtual socket id
/// </summary>
public int SocketId { get; }
/// <summary>
/// The last timestamp anything was received by the subscription
/// </summary>
public DateTime? LastReceiveTime { get; private set; }
/// <summary>
/// The current virtual websocket status
/// </summary>
public SocketStatus SocketStatus { get; private set; }
/// <summary>
/// Create a manually controlled update subscription
/// </summary>
/// <param name="socketId">The virtual socket id</param>
/// <param name="closeAsync">Callback when the subscription is closed</param>
/// <param name="reconnectAsync">Callback when a reconnect is requested</param>
/// <param name="resubscribeAsync">Callback when a resubscribe is requested</param>
public ManualUpdateSubscription(
int socketId = 0,
Func<Task>? closeAsync = null,
Func<Task>? reconnectAsync = null,
Func<Task<CallResult>>? resubscribeAsync = null)
{
SocketId = socketId;
SocketStatus = SocketStatus.Connected;
_closeAsync = closeAsync ?? (() => Task.CompletedTask);
_reconnectAsync = reconnectAsync ?? (() => Task.CompletedTask);
_resubscribeAsync = resubscribeAsync ?? (() => Task.FromResult(CallResult.Ok()));
_manualSubscription = new ManualSubscription();
_manualSubscription.Status = SubscriptionStatus.Subscribed;
Subscription = new UpdateSubscription(this, _manualSubscription);
}
/// <summary>
/// Set the last timestamp anything was received by the subscription
/// </summary>
/// <param name="timestamp">The receive timestamp</param>
public void SetLastReceiveTime(DateTime? timestamp)
{
LastReceiveTime = timestamp;
}
/// <summary>
/// Set the virtual websocket status
/// </summary>
/// <param name="status">The status</param>
public void SetSocketStatus(SocketStatus status)
{
SocketStatus = status;
}
/// <summary>
/// Set the subscription status
/// </summary>
/// <param name="status">The status</param>
public void SetSubscriptionStatus(SubscriptionStatus status)
{
_manualSubscription.Status = status;
}
/// <summary>
/// Invoke the connection lost event
/// </summary>
public void InvokeConnectionLost()
{
Subscription.HandleConnectionLostEvent();
}
/// <summary>
/// Invoke the connection restored event
/// </summary>
/// <param name="disconnectedPeriod">The period the connection was disconnected</param>
public void InvokeConnectionRestored(TimeSpan disconnectedPeriod)
{
Subscription.HandleConnectionRestoredEvent(disconnectedPeriod);
}
/// <summary>
/// Invoke the connection closed event
/// </summary>
public void InvokeConnectionClosed()
{
if (Interlocked.Exchange(ref _closedEventInvoked, 1) != 0)
return;
SocketStatus = SocketStatus.Closed;
_manualSubscription.Status = SubscriptionStatus.Closed;
Subscription.HandleConnectionClosedEvent();
}
/// <summary>
/// Invoke the resubscribing failed event
/// </summary>
/// <param name="error">The resubscribe error</param>
public void InvokeResubscribingFailed(Error error)
{
if (error == null)
throw new ArgumentNullException(nameof(error));
Subscription.HandleResubscribeFailedEvent(error);
}
/// <summary>
/// Invoke the activity paused event
/// </summary>
public void InvokeActivityPaused()
{
Subscription.HandlePausedEvent();
}
/// <summary>
/// Invoke the activity unpaused event
/// </summary>
public void InvokeActivityUnpaused()
{
Subscription.HandleUnpausedEvent();
}
/// <summary>
/// Invoke the exception event
/// </summary>
/// <param name="exception">The exception</param>
public void InvokeException(Exception exception)
{
if (exception == null)
throw new ArgumentNullException(nameof(exception));
_manualSubscription.InvokeExceptionHandler(exception);
}
internal async Task CloseAsync()
{
if (_manualSubscription.Status == SubscriptionStatus.Closed
|| _manualSubscription.Status == SubscriptionStatus.Closing)
return;
_manualSubscription.Status = SubscriptionStatus.Closing;
try
{
await _closeAsync().ConfigureAwait(false);
}
finally
{
_manualSubscription.Status = SubscriptionStatus.Closed;
}
}
internal Task ReconnectAsync()
{
return _reconnectAsync();
}
internal Task<CallResult> ResubscribeAsync()
{
return _resubscribeAsync();
}
private class ManualSubscription : Subscription
{
public ManualSubscription()
: base(NullLogger.Instance, false)
{
MessageRouter = MessageRouter.Create();
}
protected override Query? GetSubQuery(SocketConnection connection)
{
return null;
}
protected override Query? GetUnsubQuery(SocketConnection connection)
{
return null;
}
}
}
}
@@ -12,7 +12,8 @@ namespace CryptoExchange.Net.Objects.Sockets
/// </summary> /// </summary>
public class UpdateSubscription public class UpdateSubscription
{ {
private readonly SocketConnection _connection; private readonly SocketConnection? _connection;
private readonly ManualUpdateSubscription? _manualSubscription;
internal readonly Subscription _subscription; internal readonly Subscription _subscription;
#if NET9_0_OR_GREATER #if NET9_0_OR_GREATER
@@ -102,7 +103,7 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <summary> /// <summary>
/// The id of the socket /// The id of the socket
/// </summary> /// </summary>
public int SocketId => _connection.SocketId; public int SocketId => _connection?.SocketId ?? _manualSubscription!.SocketId;
/// <summary> /// <summary>
/// The id of the subscription /// The id of the subscription
@@ -112,12 +113,12 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <summary> /// <summary>
/// The last timestamp anything was received from the server /// The last timestamp anything was received from the server
/// </summary> /// </summary>
public DateTime? LastReceiveTime => _connection.LastReceiveTime; public DateTime? LastReceiveTime => _connection?.LastReceiveTime ?? _manualSubscription!.LastReceiveTime;
/// <summary> /// <summary>
/// The current websocket status /// The current websocket status
/// </summary> /// </summary>
public SocketStatus SocketStatus => _connection.Status; public SocketStatus SocketStatus => _connection?.Status ?? _manualSubscription!.SocketStatus;
/// <summary> /// <summary>
/// The current subscription status /// The current subscription status
@@ -143,6 +144,18 @@ namespace CryptoExchange.Net.Objects.Sockets
_subscription.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x); _subscription.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x);
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="manualSubscription">The manual subscription for controlling events and data</param>
/// <param name="subscription">The subscription</param>
internal UpdateSubscription(ManualUpdateSubscription manualSubscription, Subscription subscription)
{
_manualSubscription = manualSubscription;
_subscription = subscription;
_subscription.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x);
}
private void UnsubscribeConnectionEvents() private void UnsubscribeConnectionEvents()
{ {
lock (_eventLock) lock (_eventLock)
@@ -150,22 +163,26 @@ namespace CryptoExchange.Net.Objects.Sockets
if (!_connectionEventsSubscribed) if (!_connectionEventsSubscribed)
return; return;
_connection.ConnectionClosed -= HandleConnectionClosedEvent; if (_connection != null)
_connection.ConnectionLost -= HandleConnectionLostEvent; {
_connection.ConnectionRestored -= HandleConnectionRestoredEvent; _connection.ConnectionClosed -= HandleConnectionClosedEvent;
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent; _connection.ConnectionLost -= HandleConnectionLostEvent;
_connection.ActivityPaused -= HandlePausedEvent; _connection.ConnectionRestored -= HandleConnectionRestoredEvent;
_connection.ActivityUnpaused -= HandleUnpausedEvent; _connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
_connection.ActivityPaused -= HandlePausedEvent;
_connection.ActivityUnpaused -= HandleUnpausedEvent;
}
_connectionEventsSubscribed = false; _connectionEventsSubscribed = false;
} }
} }
private void HandleConnectionClosedEvent() internal void HandleConnectionClosedEvent()
{ {
UnsubscribeConnectionEvents(); UnsubscribeConnectionEvents();
// If we're not the subscription closing this connection don't bother emitting // If we're not the subscription closing this connection don't bother emitting
if (!_subscription.IsClosingConnection) if (_connection != null && !_subscription.IsClosingConnection)
return; return;
List<Action> handlers; List<Action> handlers;
@@ -176,7 +193,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(); callback();
} }
private void HandleConnectionLostEvent() internal void HandleConnectionLostEvent()
{ {
if (!_subscription.Active) if (!_subscription.Active)
{ {
@@ -192,7 +209,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(); callback();
} }
private void HandleConnectionRestoredEvent(TimeSpan period) internal void HandleConnectionRestoredEvent(TimeSpan period)
{ {
if (!_subscription.Active) if (!_subscription.Active)
{ {
@@ -208,7 +225,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(period); callback(period);
} }
private void HandleResubscribeFailedEvent(Error error) internal void HandleResubscribeFailedEvent(Error error)
{ {
if (!_subscription.Active) if (!_subscription.Active)
{ {
@@ -224,7 +241,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(error); callback(error);
} }
private void HandlePausedEvent() internal void HandlePausedEvent()
{ {
if (!_subscription.Active) if (!_subscription.Active)
{ {
@@ -240,7 +257,7 @@ namespace CryptoExchange.Net.Objects.Sockets
callback(); callback();
} }
private void HandleUnpausedEvent() internal void HandleUnpausedEvent()
{ {
if (!_subscription.Active) if (!_subscription.Active)
{ {
@@ -262,7 +279,10 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns> /// <returns></returns>
public Task CloseAsync() public Task CloseAsync()
{ {
return _connection.CloseAsync(_subscription); if (_connection != null)
return _connection.CloseAsync(_subscription);
return _manualSubscription!.CloseAsync();
} }
/// <summary> /// <summary>
@@ -271,7 +291,10 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns> /// <returns></returns>
public Task ReconnectAsync() public Task ReconnectAsync()
{ {
return _connection.TriggerReconnectAsync(); if (_connection != null)
return _connection.TriggerReconnectAsync();
return _manualSubscription!.ReconnectAsync();
} }
/// <summary> /// <summary>
@@ -280,7 +303,13 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns> /// <returns></returns>
internal async Task UnsubscribeAsync() internal async Task UnsubscribeAsync()
{ {
await _connection.UnsubscribeAsync(_subscription).ConfigureAwait(false); if (_connection != null)
{
await _connection.UnsubscribeAsync(_subscription).ConfigureAwait(false);
return;
}
await _manualSubscription!.CloseAsync().ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -289,7 +318,10 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns> /// <returns></returns>
internal async Task<CallResult> ResubscribeAsync() internal async Task<CallResult> ResubscribeAsync()
{ {
return await _connection.ResubscribeAsync(_subscription).ConfigureAwait(false); if (_connection != null)
return await _connection.ResubscribeAsync(_subscription).ConfigureAwait(false);
return await _manualSubscription!.ResubscribeAsync().ConfigureAwait(false);
} }
} }
} }
@@ -289,6 +289,7 @@ namespace CryptoExchange.Net.OrderBook
if (!startResult.Success) if (!startResult.Success)
{ {
Status = OrderBookStatus.Disconnected; Status = OrderBookStatus.Disconnected;
_queueEvent.Set();
return CallResult.Fail(startResult.Error!); return CallResult.Fail(startResult.Error!);
} }
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Catalog of Shared API capability references.
/// </summary>
public static partial class SharedCapabilities
{
/// <summary>
/// Asset capabilities.
/// </summary>
public static class Assets
{
/// <summary>
/// Get asset capability.
/// </summary>
public static SharedRestCapability<IGetAsset, IGetAssetRest> GetAsset { get; } = new();
/// <summary>
/// Get all assets capability.
/// </summary>
public static SharedRestCapability<IGetAllAssets, IGetAllAssetsRest> GetAllAssets { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Balance capabilities.
/// </summary>
public static class Balances
{
/// <summary>
/// Get balances capability.
/// </summary>
public static SharedRestCapability<IGetBalances, IGetBalancesRest> GetBalances { get; } = new();
/// <summary>
/// Subscribe to balance updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeBalancesSocket> SubscribeBalances { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Deposit capabilities.
/// </summary>
public static class Deposits
{
/// <summary>
/// Get deposit addresses capability.
/// </summary>
public static SharedRestCapability<IGetDepositAddresses, IGetDepositAddressesRest> GetDepositAddresses { get; } = new();
/// <summary>
/// Get deposit history capability.
/// </summary>
public static SharedRestCapability<IGetDepositHistory, IGetDepositHistoryRest> GetDepositHistory { get; } = new();
}
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Fee capabilities.
/// </summary>
public static class Fees
{
/// <summary>
/// Get fees capability.
/// </summary>
public static SharedRestCapability<IGetFees, IGetFeesRest> GetFees { get; } = new();
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Funding capabilities.
/// </summary>
public static class Funding
{
/// <summary>
/// Get current funding information capability.
/// </summary>
public static SharedRestCapability<IGetFundingInfo, IGetFundingInfoRest> GetFundingInfo { get; } = new();
/// <summary>
/// Get funding rate history capability.
/// </summary>
public static SharedRestCapability<IGetFundingRateHistory, IGetFundingRateHistoryRest> GetFundingRateHistory { get; } = new();
/// <summary>
/// Get user funding history capability.
/// </summary>
public static SharedRestCapability<IGetUserFundingHistory, IGetUserFundingHistoryRest> GetUserFundingHistory { get; } = new();
}
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Index price capabilities.
/// </summary>
public static class IndexPrices
{
/// <summary>
/// Get an index price capability.
/// </summary>
public static SharedRestCapability<IGetIndexPrice, IGetIndexPriceRest> GetIndexPrice { get; } = new();
/// <summary>
/// Get all index prices capability.
/// </summary>
public static SharedRestCapability<IGetAllIndexPrices, IGetAllIndexPricesRest> GetAllIndexPrices { get; } = new();
/// <summary>
/// Subscribe to index price updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeIndexPriceSocket> SubscribeIndexPrice { get; } = new();
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Kline capabilities.
/// </summary>
public static class Klines
{
/// <summary>
/// Get klines capability.
/// </summary>
public static SharedRestCapability<IGetKlines, IGetKlinesRest> GetKlines { get; } = new();
/// <summary>
/// Get mark price klines capability.
/// </summary>
public static SharedRestCapability<IGetMarkPriceKlines, IGetMarkPriceKlinesRest> GetMarkPriceKlines { get; } = new();
/// <summary>
/// Get index price klines capability.
/// </summary>
public static SharedRestCapability<IGetIndexPriceKlines, IGetIndexPriceKlinesRest> GetIndexPriceKlines { get; } = new();
/// <summary>
/// Subscribe to kline updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeKlinesSocket> SubscribeKlines { get; } = new();
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Ledger capabilities.
/// </summary>
public static class Ledger
{
/// <summary>
/// Get ledger entries capability.
/// </summary>
public static SharedRestCapability<IGetLedger, IGetLedgerRest> GetLedger { get; } = new();
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Leverage capabilities.
/// </summary>
public static class Leverage
{
/// <summary>
/// Get leverage capability.
/// </summary>
public static SharedRestCapability<IGetLeverage, IGetLeverageRest> GetLeverage { get; } = new();
/// <summary>
/// Set leverage capability.
/// </summary>
public static SharedRestCapability<ISetLeverage, ISetLeverageRest> SetLeverage { get; } = new();
/// <summary>
/// Get leverage tiers capability.
/// </summary>
public static SharedRestCapability<IGetLeverageTiers, IGetLeverageTiersRest> GetLeverageTiers { get; } = new();
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Mark price capabilities.
/// </summary>
public static class MarkPrices
{
/// <summary>
/// Get a mark price capability.
/// </summary>
public static SharedRestCapability<IGetMarkPrice, IGetMarkPriceRest> GetMarkPrice { get; } = new();
/// <summary>
/// Get all mark prices capability.
/// </summary>
public static SharedRestCapability<IGetAllMarkPrices, IGetAllMarkPricesRest> GetAllMarkPrices { get; } = new();
/// <summary>
/// Subscribe to mark price updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeMarkPriceSocket> SubscribeMarkPrice { get; } = new();
}
}
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Open interest capabilities.
/// </summary>
public static class OpenInterest
{
/// <summary>
/// Get open interest capability.
/// </summary>
public static SharedRestCapability<IGetOpenInterest, IGetOpenInterestRest> GetOpenInterest { get; } = new();
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Order book capabilities.
/// </summary>
public static class OrderBooks
{
/// <summary>
/// Get an order book capability.
/// </summary>
public static SharedRestCapability<IGetOrderBook, IGetOrderBookRest> GetOrderBook { get; } = new();
/// <summary>
/// Get a book ticker capability.
/// </summary>
public static SharedRestCapability<IGetBookTicker, IGetBookTickerRest> GetBookTicker { get; } = new();
/// <summary>
/// Subscribe to order book updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeOrderBookSocket> SubscribeOrderBook { get; } = new();
/// <summary>
/// Subscribe to incremental order book updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeIncrementalOrderBookSocket> SubscribeIncrementalOrderBook { get; } = new();
}
}
}
@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Order capabilities.
/// </summary>
public static class Orders
{
/// <summary>
/// Spot order capabilities.
/// </summary>
public static class Spot
{
/// <summary>
/// Place a spot order capability.
/// </summary>
public static SharedRestSocketCapability<IPlaceSpotOrder, IPlaceSpotOrderRest, IPlaceSpotOrderSocket> PlaceOrder { get; } = new();
/// <summary>
/// Place multiple spot orders capability.
/// </summary>
public static SharedRestSocketCapability<IPlaceMultipleSpotOrders, IPlaceMultipleSpotOrdersRest, IPlaceMultipleSpotOrdersSocket> PlaceMultipleOrders { get; } = new();
/// <summary>
/// Edit a spot order capability.
/// </summary>
public static SharedRestSocketCapability<IEditSpotOrder, IEditSpotOrderRest, IEditSpotOrderSocket> EditOrder { get; } = new();
/// <summary>
/// Edit a spot order by client order id capability.
/// </summary>
public static SharedRestSocketCapability<IEditSpotOrderByClientOrderId, IEditSpotOrderByClientOrderIdRest, IEditSpotOrderByClientOrderIdSocket> EditOrderByClientOrderId { get; } = new();
/// <summary>
/// Cancel a spot order capability.
/// </summary>
public static SharedRestSocketCapability<ICancelSpotOrder, ICancelSpotOrderRest, ICancelSpotOrderSocket> CancelOrder { get; } = new();
/// <summary>
/// Cancel a spot order by client order id capability.
/// </summary>
public static SharedRestSocketCapability<ICancelSpotOrderByClientOrderId, ICancelSpotOrderByClientOrderIdRest, ICancelSpotOrderByClientOrderIdSocket> CancelOrderByClientOrderId { get; } = new();
/// <summary>
/// Cancel all spot orders capability.
/// </summary>
public static SharedRestSocketCapability<ICancelAllSpotOrders, ICancelAllSpotOrdersRest, ICancelAllSpotOrdersSocket> CancelAllOrders { get; } = new();
/// <summary>
/// Cancel all spot orders for a symbol capability.
/// </summary>
public static SharedRestSocketCapability<ICancelAllSpotSymbolOrders, ICancelAllSpotSymbolOrdersRest, ICancelAllSpotSymbolOrdersSocket> CancelAllSymbolOrders { get; } = new();
/// <summary>
/// Get a spot order capability.
/// </summary>
public static SharedRestCapability<IGetSpotOrder, IGetSpotOrderRest> GetOrder { get; } = new();
/// <summary>
/// Get a spot order by client order id capability.
/// </summary>
public static SharedRestCapability<IGetSpotOrderByClientOrderId, IGetSpotOrderByClientOrderIdRest> GetOrderByClientOrderId { get; } = new();
/// <summary>
/// Get open spot orders capability.
/// </summary>
public static SharedRestCapability<IGetOpenSpotOrders, IGetOpenSpotOrdersRest> GetOpenOrders { get; } = new();
/// <summary>
/// Get closed spot orders capability.
/// </summary>
public static SharedRestCapability<IGetClosedSpotOrders, IGetClosedSpotOrdersRest> GetClosedOrders { get; } = new();
/// <summary>
/// Subscribe to spot order updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeSpotOrdersSocket> SubscribeOrders { get; } = new();
}
/// <summary>
/// Futures order capabilities.
/// </summary>
public static class Futures
{
/// <summary>
/// Place a futures order capability.
/// </summary>
public static SharedRestSocketCapability<IPlaceFuturesOrder, IPlaceFuturesOrderRest, IPlaceFuturesOrderSocket> PlaceOrder { get; } = new();
/// <summary>
/// Place multiple futures orders capability.
/// </summary>
public static SharedRestSocketCapability<IPlaceMultipleFuturesOrders, IPlaceMultipleFuturesOrdersRest, IPlaceMultipleFuturesOrdersSocket> PlaceMultipleOrders { get; } = new();
/// <summary>
/// Edit a futures order capability.
/// </summary>
public static SharedRestSocketCapability<IEditFuturesOrder, IEditFuturesOrderRest, IEditFuturesOrderSocket> EditOrder { get; } = new();
/// <summary>
/// Edit a futures order by client order id capability.
/// </summary>
public static SharedRestSocketCapability<IEditFuturesOrderByClientOrderId, IEditFuturesOrderByClientOrderIdRest, IEditFuturesOrderByClientOrderIdSocket> EditOrderByClientOrderId { get; } = new();
/// <summary>
/// Cancel a futures order capability.
/// </summary>
public static SharedRestSocketCapability<ICancelFuturesOrder, ICancelFuturesOrderRest, ICancelFuturesOrderSocket> CancelOrder { get; } = new();
/// <summary>
/// Cancel a futures order by client order id capability.
/// </summary>
public static SharedRestSocketCapability<ICancelFuturesOrderByClientOrderId, ICancelFuturesOrderByClientOrderIdRest, ICancelFuturesOrderByClientOrderIdSocket> CancelOrderByClientOrderId { get; } = new();
/// <summary>
/// Cancel all futures orders capability.
/// </summary>
public static SharedRestSocketCapability<ICancelAllFuturesOrders, ICancelAllFuturesOrdersRest, ICancelAllFuturesOrdersSocket> CancelAllOrders { get; } = new();
/// <summary>
/// Cancel all futures orders for a symbol capability.
/// </summary>
public static SharedRestSocketCapability<ICancelAllFuturesSymbolOrders, ICancelAllFuturesSymbolOrdersRest, ICancelAllFuturesSymbolOrdersSocket> CancelAllSymbolOrders { get; } = new();
/// <summary>
/// Get a futures order capability.
/// </summary>
public static SharedRestCapability<IGetFuturesOrder, IGetFuturesOrderRest> GetOrder { get; } = new();
/// <summary>
/// Get a futures order by client order id capability.
/// </summary>
public static SharedRestCapability<IGetFuturesOrderByClientOrderId, IGetFuturesOrderByClientOrderIdRest> GetOrderByClientOrderId { get; } = new();
/// <summary>
/// Get open futures orders capability.
/// </summary>
public static SharedRestCapability<IGetOpenFuturesOrders, IGetOpenFuturesOrdersRest> GetOpenOrders { get; } = new();
/// <summary>
/// Get closed futures orders capability.
/// </summary>
public static SharedRestCapability<IGetClosedFuturesOrders, IGetClosedFuturesOrdersRest> GetClosedOrders { get; } = new();
/// <summary>
/// Subscribe to futures order updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeFuturesOrdersSocket> SubscribeOrders { get; } = new();
}
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Position mode capabilities.
/// </summary>
public static class PositionMode
{
/// <summary>
/// Get position mode capability.
/// </summary>
public static SharedRestCapability<IGetPositionMode, IGetPositionModeRest> GetPositionMode { get; } = new();
/// <summary>
/// Set position mode capability.
/// </summary>
public static SharedRestCapability<ISetPositionMode, ISetPositionModeRest> SetPositionMode { get; } = new();
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Position capabilities.
/// </summary>
public static class Positions
{
/// <summary>
/// Get positions capability.
/// </summary>
public static SharedRestCapability<IGetPositions, IGetPositionsRest> GetPositions { get; } = new();
/// <summary>
/// Get position history capability.
/// </summary>
public static SharedRestCapability<IGetPositionHistory, IGetPositionHistoryRest> GetPositionHistory { get; } = new();
/// <summary>
/// Close a full position capability.
/// </summary>
public static SharedRestCapability<ICloseFullPosition, ICloseFullPositionRest> CloseFullPosition { get; } = new();
/// <summary>
/// Subscribe to position updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribePositionsSocket> SubscribePositions { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Symbol capabilities.
/// </summary>
public static class Symbols
{
/// <summary>
/// Get spot symbols capability.
/// </summary>
public static SharedRestCapability<IGetSpotSymbols, IGetSpotSymbolsRest> GetSpotSymbols { get; } = new();
/// <summary>
/// Get futures symbols capability.
/// </summary>
public static SharedRestCapability<IGetFuturesSymbols, IGetFuturesSymbolsRest> GetFuturesSymbols { get; } = new();
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Ticker capabilities.
/// </summary>
public static class Tickers
{
/// <summary>
/// Get a ticker capability.
/// </summary>
public static SharedRestCapability<IGetTicker, IGetTickerRest> GetTicker { get; } = new();
/// <summary>
/// Get all tickers capability.
/// </summary>
public static SharedRestCapability<IGetAllTickers, IGetAllTickersRest> GetAllTickers { get; } = new();
/// <summary>
/// Subscribe to ticker updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeTickerSocket> SubscribeTicker { get; } = new();
/// <summary>
/// Subscribe to all ticker updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeAllTickersSocket> SubscribeAllTickers { get; } = new();
/// <summary>
/// Subscribe to book ticker updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeBookTickerSocket> SubscribeBookTicker { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Take profit and stop loss capabilities.
/// </summary>
public static class TpSl
{
/// <summary>
/// Set futures take profit or stop loss capability.
/// </summary>
public static SharedRestCapability<ISetFuturesTpSl, ISetFuturesTpSlRest> SetFuturesTpSl { get; } = new();
/// <summary>
/// Cancel futures take profit or stop loss capability.
/// </summary>
public static SharedRestCapability<ICancelFuturesTpSl, ICancelFuturesTpSlRest> CancelFuturesTpSl { get; } = new();
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Public trade capabilities.
/// </summary>
public static class Trades
{
/// <summary>
/// Get recent trades capability.
/// </summary>
public static SharedRestCapability<IGetRecentTrades, IGetRecentTradesRest> GetRecentTrades { get; } = new();
/// <summary>
/// Get trade history capability.
/// </summary>
public static SharedRestCapability<IGetTradeHistory, IGetTradeHistoryRest> GetTradeHistory { get; } = new();
/// <summary>
/// Subscribe to trade updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeTradesSocket> SubscribeTrades { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Transfer capabilities.
/// </summary>
public static class Transfers
{
/// <summary>
/// Transfer assets capability.
/// </summary>
public static SharedRestCapability<ITransfer, ITransferRest> Transfer { get; } = new();
/// <summary>
/// Get transfer history capability.
/// </summary>
public static SharedRestCapability<IGetTransferHistory, IGetTransferHistoryRest> GetTransferHistory { get; } = new();
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Trigger order capabilities.
/// </summary>
public static class TriggerOrders
{
/// <summary>
/// Spot trigger order capabilities.
/// </summary>
public static class Spot
{
/// <summary>
/// Place a spot trigger order capability.
/// </summary>
public static SharedRestCapability<IPlaceSpotTriggerOrder, IPlaceSpotTriggerOrderRest> PlaceOrder { get; } = new();
/// <summary>
/// Get a spot trigger order capability.
/// </summary>
public static SharedRestCapability<IGetSpotTriggerOrder, IGetSpotTriggerOrderRest> GetOrder { get; } = new();
/// <summary>
/// Cancel a spot trigger order capability.
/// </summary>
public static SharedRestCapability<ICancelSpotTriggerOrder, ICancelSpotTriggerOrderRest> CancelOrder { get; } = new();
}
/// <summary>
/// Futures trigger order capabilities.
/// </summary>
public static class Futures
{
/// <summary>
/// Place a futures trigger order capability.
/// </summary>
public static SharedRestCapability<IPlaceFuturesTriggerOrder, IPlaceFuturesTriggerOrderRest> PlaceOrder { get; } = new();
/// <summary>
/// Get a futures trigger order capability.
/// </summary>
public static SharedRestCapability<IGetFuturesTriggerOrder, IGetFuturesTriggerOrderRest> GetOrder { get; } = new();
/// <summary>
/// Cancel a futures trigger order capability.
/// </summary>
public static SharedRestCapability<ICancelFuturesTriggerOrder, ICancelFuturesTriggerOrderRest> CancelOrder { get; } = new();
}
}
}
}
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// User trade capabilities.
/// </summary>
public static class UserTrades
{
/// <summary>
/// Get trades for a spot order capability.
/// </summary>
public static SharedRestCapability<IGetSpotOrderTrades, IGetSpotOrderTradesRest> GetSpotOrderTrades { get; } = new();
/// <summary>
/// Get spot user trade history capability.
/// </summary>
public static SharedRestCapability<IGetSpotUserTradeHistory, IGetSpotUserTradeHistoryRest> GetSpotTradeHistory { get; } = new();
/// <summary>
/// Get trades for a futures order capability.
/// </summary>
public static SharedRestCapability<IGetFuturesOrderTrades, IGetFuturesOrderTradesRest> GetFuturesOrderTrades { get; } = new();
/// <summary>
/// Get futures user trade history capability.
/// </summary>
public static SharedRestCapability<IGetFuturesUserTradeHistory, IGetFuturesUserTradeHistoryRest> GetFuturesTradeHistory { get; } = new();
/// <summary>
/// Subscribe to user trade updates capability.
/// </summary>
public static SharedCapabilityReference<ISubscribeUserTradesSocket> SubscribeUserTrades { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
public static partial class SharedCapabilities
{
/// <summary>
/// Withdrawal capabilities.
/// </summary>
public static class Withdrawals
{
/// <summary>
/// Withdraw assets capability.
/// </summary>
public static SharedRestCapability<IWithdraw, IWithdrawRest> Withdraw { get; } = new();
/// <summary>
/// Get withdrawal history capability.
/// </summary>
public static SharedRestCapability<IGetWithdrawalHistory, IGetWithdrawalHistoryRest> GetWithdrawalHistory { get; } = new();
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Strongly typed reference to a Shared API capability.
/// </summary>
public class SharedCapabilityReference<T>
where T : ISharedApiCapability
{
/// <summary>
/// Capability interface type.
/// </summary>
public Type CapabilityType => typeof(T);
internal SharedCapabilityReference()
{
}
/// <inheritdoc />
public override string ToString() => typeof(T).Name;
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Capability lookup result
/// </summary>
/// <typeparam name="T">Capability type</typeparam>
public class SharedCapabilityResolution<T>
where T : ISharedApiCapability
{
/// <summary>
/// The capability
/// </summary>
public T Capability { get; }
/// <summary>
/// The capability options
/// </summary>
public CapabilityOptions Options { get; }
internal SharedCapabilityResolution(
T capability,
CapabilityOptions options)
{
Capability = capability;
Options = options;
}
}
}
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// References for a capability available through REST.
/// </summary>
public sealed class SharedRestCapability<TCapability, TRest>
: SharedCapabilityReference<TCapability>
where TCapability : ISharedApiCapability
where TRest : TCapability, ISharedRest
{
/// <summary>
/// Transport-agnostic capability.
/// </summary>
public SharedCapabilityReference<TCapability> Any { get; } = new();
/// <summary>
/// REST capability.
/// </summary>
public SharedCapabilityReference<TRest> Rest { get; } = new();
internal SharedRestCapability()
{
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// References for a capability available through REST and WebSocket.
/// </summary>
public sealed class SharedRestSocketCapability<TCapability, TRest, TSocket>
: SharedCapabilityReference<TCapability>
where TCapability : ISharedApiCapability
where TRest : TCapability, ISharedRest
where TSocket : TCapability, ISharedSocket
{
/// <summary>
/// REST capability.
/// </summary>
public SharedCapabilityReference<TRest> Rest { get; } = new();
/// <summary>
/// WebSocket capability.
/// </summary>
public SharedCapabilityReference<TSocket> Socket { get; } = new();
internal SharedRestSocketCapability()
{
}
}
}
@@ -0,0 +1,42 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Ledger entry type
/// </summary>
public enum SharedLedgerEntryType
{
/// <summary>
/// Trade execution
/// </summary>
Trade,
/// <summary>
/// Withdrawal
/// </summary>
Withdrawal,
/// <summary>
/// Deposit
/// </summary>
Deposit,
/// <summary>
/// Transfer
/// </summary>
Transfer,
/// <summary>
/// Fee payment
/// </summary>
Fee,
/// <summary>
/// Funding fee payment
/// </summary>
FundingFee,
/// <summary>
/// Rebate
/// </summary>
Rebate,
/// <summary>
/// Other or unknown
/// </summary>
Unknown
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// The type of updates the incremental order book subscription produces
/// </summary>
public enum SharedOrderBookSubscriptionType
{
/// <summary>
/// Subscription produces an initial snapshot update, followed by incremental change updates
/// </summary>
SnapshotThenIncremental,
/// <summary>
/// Subscription produces only incremental change updates
/// </summary>
OnlyIncremental
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// The type of transport used for the shared API
/// </summary>
public record SharedTransport(string Name)
{
/// <summary>
/// REST transport
/// </summary>
public static SharedTransport Rest { get; } = new("REST");
/// <summary>
/// WebSocket transport
/// </summary>
public static SharedTransport Socket { get; } = new("WebSocket");
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// A shared/common client interface
/// </summary>
public interface ISharedClient : ISharedApi
{
}
}
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for canceling a futures order
/// </summary>
public class CancelFuturesOrderSocketOptions : CancelFuturesOrderOptions
{
/// <inheritdoc />
public override string Description => "Cancel a futures order over a socket connection";
/// <summary>
/// ctor
/// </summary>
public CancelFuturesOrderSocketOptions(string exchange, bool authenticated) : base(exchange, authenticated)
{
}
}
}
@@ -0,0 +1,23 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for canceling a spot order
/// </summary>
public class CancelSpotOrderSocketOptions : CancelSpotOrderOptions
{
/// <inheritdoc />
public override string Description => "Cancel a spot order over a socket connection";
/// <summary>
/// ctor
/// </summary>
public CancelSpotOrderSocketOptions(string exchange, bool authenticated) : base(exchange, authenticated)
{
}
}
}
@@ -0,0 +1,82 @@
using CryptoExchange.Net.Objects;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for closing position
/// </summary>
#pragma warning disable CS0618 // Type or member is obsolete
public class ClosePositionOptions : EndpointOptions
#pragma warning restore CS0618 // Type or member is obsolete
{
private static PropertyInfo[] _requestProperties = typeof(ClosePositionRequest).GetProperties();
/// <inheritdoc />
public override string Description => "Close an open futures position";
/// <inheritdoc />
public override Type CapabilityType => typeof(IFuturesOrderRestClient);
private static readonly RequestParameterDescription[] _defaultParameterRules = new[]
{
RequestParameterRule<ClosePositionRequest>.Required(x => x.Symbol, "The symbol of the position to close", new SharedSymbol(TradingMode.PerpetualLinear, "ETH", "USDT")),
RequestParameterRule<ClosePositionRequest>.Required(x => x.PositionMode, "The current position mode of the account", SharedPositionMode.OneWay),
RequestParameterRule<ClosePositionRequest>.Optional(x => x.PositionSide, "The side of the position to close", SharedPositionSide.Long),
RequestParameterRule<ClosePositionRequest>.Optional(x => x.MarginMode, "The margin mode of the position", SharedMarginMode.Cross),
RequestParameterRule<ClosePositionRequest>.Optional(x => x.Quantity, "The quantity of the position to close", 1m),
};
/// <summary>
/// ctor
/// </summary>
public ClosePositionOptions(string exchange, bool authenticated) : base(exchange, nameof(IFuturesOrderRestClient.ClosePositionAsync), true, _defaultParameterRules, SharedTradingModeSets.Futures)
{
}
/// <summary>
/// Validate request
/// </summary>
public Error? ValidateRequest(ClosePositionRequest request, IFuturesOrderRestClient client)
{
if (NeedsAuthentication && !client.Authenticated)
return new NoApiCredentialsError();
foreach (var param in RequestParameterRules)
{
var property = _requestProperties.Single(x => x.Name == param.Name);
var value = property.GetValue(request);
if (param.Support == RequestParameterSupport.Required)
{
if (value == null)
{
return ArgumentError.Invalid(
param.Name,
$"Request parameter `{param.Name}` for exchange `{Exchange}` is required and should be provided. Example: {param.ExampleValue}");
}
}
}
if (request is SharedSymbolRequest symbolsRequest)
{
if (symbolsRequest.Symbols != null)
{
if (!SupportsMultipleSymbols)
return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported");
if (symbolsRequest.Symbols.Length > MaxSymbolCount)
return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed");
}
}
return ValidateRequest(request.ExchangeParameters, request.TradingMode, client.SupportedTradingModes);
}
}
}
@@ -0,0 +1,23 @@
using CryptoExchange.Net.Objects;
using System;
using System.Linq;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for placing a new futures order
/// </summary>
public class PlaceFuturesOrderSocketOptions : PlaceFuturesOrderOptions
{
/// <inheritdoc />
public override string Description => "Place a new futures order over a socket connection";
/// <summary>
/// ctor
/// </summary>
public PlaceFuturesOrderSocketOptions(string exchange, bool supportsTpSl) : base(exchange, supportsTpSl)
{
}
}
}
@@ -0,0 +1,23 @@
using CryptoExchange.Net.Objects;
using System;
using System.Linq;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for placing a new spot order
/// </summary>
public class PlaceSpotOrderSocketOptions : PlaceSpotOrderOptions
{
/// <inheritdoc />
public override string Description => "Place a new spot order over a socket connection";
/// <summary>
/// ctor
/// </summary>
public PlaceSpotOrderSocketOptions(string exchange) : base(exchange)
{
}
}
}
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Funding rate request options.<br /> /// Funding rate request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFundingRateHistoryOptions GetFundingRateHistoryOptions { get; } GetFundingRateHistoryOptions GetFundingRateHistoryOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Futures get order by client order id request options.<br /> /// Futures get order by client order id request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesOrderByClientOrderIdOptions GetFuturesOrderByClientOrderIdOptions { get; } GetFuturesOrderByClientOrderIdOptions GetFuturesOrderByClientOrderIdOptions { get; }
@@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures cancel order by client order id request options.<br /> /// Futures cancel order by client order id request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelFuturesOrderByClientOrderIdOptions CancelFuturesOrderByClientOrderIdOptions { get; } CancelFuturesOrderByClientOrderIdOptions CancelFuturesOrderByClientOrderIdOptions { get; }
@@ -38,7 +38,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures place order request options.<br /> /// Futures place order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
PlaceFuturesOrderOptions PlaceFuturesOrderOptions { get; } PlaceFuturesOrderOptions PlaceFuturesOrderOptions { get; }
@@ -52,7 +52,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures get order request options.<br /> /// Futures get order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesOrderOptions GetFuturesOrderOptions { get; } GetFuturesOrderOptions GetFuturesOrderOptions { get; }
@@ -65,7 +65,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures get open orders request options.<br /> /// Futures get open orders request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetOpenFuturesOrdersOptions GetOpenFuturesOrdersOptions { get; } GetOpenFuturesOrdersOptions GetOpenFuturesOrdersOptions { get; }
@@ -78,7 +78,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot get closed orders request options.<br /> /// Spot get closed orders request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesClosedOrdersOptions GetClosedFuturesOrdersOptions { get; } GetFuturesClosedOrdersOptions GetClosedFuturesOrdersOptions { get; }
@@ -93,7 +93,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures get order trades request options.<br /> /// Futures get order trades request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesOrderTradesOptions GetFuturesOrderTradesOptions { get; } GetFuturesOrderTradesOptions GetFuturesOrderTradesOptions { get; }
@@ -106,10 +106,10 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures user trades request options.<br /> /// Futures user trades request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesUserTradesOptions GetFuturesUserTradesOptions { get; } GetFuturesUserTradeHistoryOptions GetFuturesUserTradesOptions { get; }
/// <summary> /// <summary>
/// Get futures user trade records, see <see cref="GetFuturesUserTradesOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get futures user trade records, see <see cref="GetFuturesUserTradesOptions"/> for request options and exchange specific required/optional parameters. <br />
/// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination. /// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination.
@@ -121,7 +121,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures cancel order request options.<br /> /// Futures cancel order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelFuturesOrderOptions CancelFuturesOrderOptions { get; } CancelFuturesOrderOptions CancelFuturesOrderOptions { get; }
@@ -134,7 +134,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Positions request options.<br /> /// Positions request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetPositionsOptions GetPositionsOptions { get; } GetPositionsOptions GetPositionsOptions { get; }
@@ -147,7 +147,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Close position order request options.<br /> /// Close position order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
ClosePositionOptions ClosePositionOptions { get; } ClosePositionOptions ClosePositionOptions { get; }
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures symbol request options.<br /> /// Futures symbol request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesSymbolsOptions GetFuturesSymbolsOptions { get; } GetFuturesSymbolsOptions GetFuturesSymbolsOptions { get; }
@@ -11,10 +11,10 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Futures get ticker request options.<br /> /// Futures get ticker request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesTickerOptions GetFuturesTickerOptions { get; } GetTickerOptions GetFuturesTickerOptions { get; }
/// <summary> /// <summary>
/// Get ticker info for a specific futures symbol, see <see cref="GetFuturesTickerOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get ticker info for a specific futures symbol, see <see cref="GetFuturesTickerOptions"/> for request options and exchange specific required/optional parameters. <br />
/// </summary> /// </summary>
@@ -24,10 +24,10 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Futures get tickers request options.<br /> /// Futures get tickers request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesTickersOptions GetFuturesTickersOptions { get; } GetAllTickersOptions GetFuturesTickersOptions { get; }
/// <summary> /// <summary>
/// Get ticker info for all futures symbols, see <see cref="GetFuturesTickersOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get ticker info for all futures symbols, see <see cref="GetFuturesTickersOptions"/> for request options and exchange specific required/optional parameters. <br />
/// </summary> /// </summary>
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Set take profit and/or stop loss options.<br /> /// Set take profit and/or stop loss options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
SetFuturesTpSlOptions SetFuturesTpSlOptions { get; } SetFuturesTpSlOptions SetFuturesTpSlOptions { get; }
@@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Cancel a take profit and/or stop loss options.<br /> /// Cancel a take profit and/or stop loss options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelFuturesTpSlOptions CancelFuturesTpSlOptions { get; } CancelFuturesTpSlOptions CancelFuturesTpSlOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Place spot trigger order options.<br /> /// Place spot trigger order options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
PlaceFuturesTriggerOrderOptions PlaceFuturesTriggerOrderOptions { get; } PlaceFuturesTriggerOrderOptions PlaceFuturesTriggerOrderOptions { get; }
@@ -26,7 +26,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Get trigger order request options.<br /> /// Get trigger order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFuturesTriggerOrderOptions GetFuturesTriggerOrderOptions { get; } GetFuturesTriggerOrderOptions GetFuturesTriggerOrderOptions { get; }
@@ -39,7 +39,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Cancel trigger order request options.<br /> /// Cancel trigger order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelFuturesTriggerOrderOptions CancelFuturesTriggerOrderOptions { get; } CancelFuturesTriggerOrderOptions CancelFuturesTriggerOrderOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Index price klines request options.<br /> /// Index price klines request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetIndexPriceKlinesOptions GetIndexPriceKlinesOptions { get; } GetIndexPriceKlinesOptions GetIndexPriceKlinesOptions { get; }
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Leverage request options.<br /> /// Leverage request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetLeverageOptions GetLeverageOptions { get; } GetLeverageOptions GetLeverageOptions { get; }
@@ -29,7 +29,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Leverage set request options.<br /> /// Leverage set request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
SetLeverageOptions SetLeverageOptions { get; } SetLeverageOptions SetLeverageOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Mark price klines request options.<br /> /// Mark price klines request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetMarkPriceKlinesOptions GetMarkPriceKlinesOptions { get; } GetMarkPriceKlinesOptions GetMarkPriceKlinesOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Open interest request options.<br /> /// Open interest request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetOpenInterestOptions GetOpenInterestOptions { get; } GetOpenInterestOptions GetOpenInterestOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Position history request options.<br /> /// Position history request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetPositionHistoryOptions GetPositionHistoryOptions { get; } GetPositionHistoryOptions GetPositionHistoryOptions { get; }
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Position mode request options.<br /> /// Position mode request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetPositionModeOptions GetPositionModeOptions { get; } GetPositionModeOptions GetPositionModeOptions { get; }
@@ -29,7 +29,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Position mode set request options.<br /> /// Position mode set request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
SetPositionModeOptions SetPositionModeOptions { get; } SetPositionModeOptions SetPositionModeOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Asset request options.<br /> /// Asset request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetAssetOptions GetAssetOptions { get; } GetAssetOptions GetAssetOptions { get; }
@@ -25,10 +25,10 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Assets request options.<br /> /// Assets request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetAssetsOptions GetAssetsOptions { get; } GetAllAssetsOptions GetAssetsOptions { get; }
/// <summary> /// <summary>
/// Get info on all assets the exchange supports, see <see cref="GetAssetsOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get info on all assets the exchange supports, see <see cref="GetAssetsOptions"/> for request options and exchange specific required/optional parameters. <br />
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Balances request options.<br /> /// Balances request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetBalancesOptions GetBalancesOptions { get; } GetBalancesOptions GetBalancesOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Book ticker request options.<br /> /// Book ticker request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetBookTickerOptions GetBookTickerOptions { get; } GetBookTickerOptions GetBookTickerOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Deposit addresses request options.<br /> /// Deposit addresses request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetDepositAddressesOptions GetDepositAddressesOptions { get; } GetDepositAddressesOptions GetDepositAddressesOptions { get; }
@@ -26,10 +26,10 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Deposits request options.<br /> /// Deposits request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetDepositsOptions GetDepositsOptions { get; } GetDepositHistoryOptions GetDepositsOptions { get; }
/// <summary> /// <summary>
/// Get deposit records, see <see cref="GetDepositsOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get deposit records, see <see cref="GetDepositsOptions"/> for request options and exchange specific required/optional parameters. <br />
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Fee request options.<br /> /// Fee request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetFeeOptions GetFeeOptions { get; } GetFeeOptions GetFeeOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Kline request options.<br /> /// Kline request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetKlinesOptions GetKlinesOptions { get; } GetKlinesOptions GetKlinesOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Order book request options.<br /> /// Order book request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetOrderBookOptions GetOrderBookOptions { get; } GetOrderBookOptions GetOrderBookOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Recent trades request options.<br /> /// Recent trades request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetRecentTradesOptions GetRecentTradesOptions { get; } GetRecentTradesOptions GetRecentTradesOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Trade history request options.<br /> /// Trade history request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetTradeHistoryOptions GetTradeHistoryOptions { get; } GetTradeHistoryOptions GetTradeHistoryOptions { get; }
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Transfer request options.<br /> /// Transfer request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
TransferOptions TransferOptions { get; } TransferOptions TransferOptions { get; }
@@ -1,17 +1,20 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.SharedApis namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Client for requesting to withdraw funds from the exchange /// Client for withdrawing funds
/// </summary> /// </summary>
public interface IWithdrawRestClient : ISharedClient public interface IWithdrawRestClient : ISharedClient
{ {
/// <summary> /// <summary>
/// Withdraw request options.<br /> /// Withdraw request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
WithdrawOptions WithdrawOptions { get; } WithdrawOptions WithdrawOptions { get; }
@@ -11,10 +11,10 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Withdrawal records request options.<br /> /// Withdrawal records request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetWithdrawalsOptions GetWithdrawalsOptions { get; } GetWithdrawalHistoryOptions GetWithdrawalsOptions { get; }
/// <summary> /// <summary>
/// Get withdrawal records, see <see cref="GetWithdrawalsOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get withdrawal records, see <see cref="GetWithdrawalsOptions"/> for request options and exchange specific required/optional parameters. <br />
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Spot get order by client order id request options.<br /> /// Spot get order by client order id request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotOrderByClientOrderIdOptions GetSpotOrderByClientOrderIdOptions { get; } GetSpotOrderByClientOrderIdOptions GetSpotOrderByClientOrderIdOptions { get; }
@@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot cancel order by client order id request options.<br /> /// Spot cancel order by client order id request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelSpotOrderByClientOrderIdOptions CancelSpotOrderByClientOrderIdOptions { get; } CancelSpotOrderByClientOrderIdOptions CancelSpotOrderByClientOrderIdOptions { get; }
@@ -38,13 +38,12 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot place order request options.<br /> /// Spot place order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
PlaceSpotOrderOptions PlaceSpotOrderOptions { get; } PlaceSpotOrderOptions PlaceSpotOrderOptions { get; }
/// <summary> /// <summary>
/// Place a new spot order, see <see cref="PlaceSpotOrderOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Place a new spot order, see <see cref="PlaceSpotOrderOptions"/> for request options and exchange specific required/optional parameters. <br />
/// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination.
/// </summary> /// </summary>
/// <param name="request">Request info</param> /// <param name="request">Request info</param>
/// <param name="ct">Cancellation token</param> /// <param name="ct">Cancellation token</param>
@@ -52,7 +51,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot get order request options.<br /> /// Spot get order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotOrderOptions GetSpotOrderOptions { get; } GetSpotOrderOptions GetSpotOrderOptions { get; }
@@ -65,7 +64,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot get open orders request options.<br /> /// Spot get open orders request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetOpenSpotOrdersOptions GetOpenSpotOrdersOptions { get; } GetOpenSpotOrdersOptions GetOpenSpotOrdersOptions { get; }
@@ -78,7 +77,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot get closed orders request options.<br /> /// Spot get closed orders request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotClosedOrdersOptions GetClosedSpotOrdersOptions { get; } GetSpotClosedOrdersOptions GetClosedSpotOrdersOptions { get; }
@@ -93,7 +92,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot get order trades request options.<br /> /// Spot get order trades request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotOrderTradesOptions GetSpotOrderTradesOptions { get; } GetSpotOrderTradesOptions GetSpotOrderTradesOptions { get; }
@@ -107,10 +106,10 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot user trades request options.<br /> /// Spot user trades request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotUserTradesOptions GetSpotUserTradesOptions { get; } GetSpotUserTradeHistoryOptions GetSpotUserTradesOptions { get; }
/// <summary> /// <summary>
/// Get spot user trade records, see <see cref="GetSpotUserTradesOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get spot user trade records, see <see cref="GetSpotUserTradesOptions"/> for request options and exchange specific required/optional parameters. <br />
/// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination. /// The result is paginated, if there are more results to be retrieved, the `NextPageRequest` property of the result will contain the pagination request to be used for the next request to continue pagination.
@@ -122,7 +121,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot cancel order request options.<br /> /// Spot cancel order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelSpotOrderOptions CancelSpotOrderOptions { get; } CancelSpotOrderOptions CancelSpotOrderOptions { get; }
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Spot symbols request options.<br /> /// Spot symbols request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotSymbolsOptions GetSpotSymbolsOptions { get; } GetSpotSymbolsOptions GetSpotSymbolsOptions { get; }
@@ -11,10 +11,10 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Spot ticker request options.<br /> /// Spot ticker request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotTickerOptions GetSpotTickerOptions { get; } GetTickerOptions GetSpotTickerOptions { get; }
/// <summary> /// <summary>
/// Get ticker for a specific spot symbol, see <see cref="GetSpotTickerOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get ticker for a specific spot symbol, see <see cref="GetSpotTickerOptions"/> for request options and exchange specific required/optional parameters. <br />
/// </summary> /// </summary>
@@ -23,10 +23,10 @@ namespace CryptoExchange.Net.SharedApis
Task<HttpResult<SharedSpotTicker>> GetSpotTickerAsync(GetTickerRequest request, CancellationToken ct = default); Task<HttpResult<SharedSpotTicker>> GetSpotTickerAsync(GetTickerRequest request, CancellationToken ct = default);
/// <summary> /// <summary>
/// Spot tickers request options.<br /> /// Spot tickers request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotTickersOptions GetSpotTickersOptions { get; } GetAllTickersOptions GetSpotTickersOptions { get; }
/// <summary> /// <summary>
/// Get tickers for all spot symbols, see <see cref="GetSpotTickersOptions"/> for request options and exchange specific required/optional parameters. <br /> /// Get tickers for all spot symbols, see <see cref="GetSpotTickersOptions"/> for request options and exchange specific required/optional parameters. <br />
/// </summary> /// </summary>
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Place spot trigger order options.<br /> /// Place spot trigger order options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
PlaceSpotTriggerOrderOptions PlaceSpotTriggerOrderOptions { get; } PlaceSpotTriggerOrderOptions PlaceSpotTriggerOrderOptions { get; }
@@ -26,7 +26,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Get trigger order request options.<br /> /// Get trigger order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
GetSpotTriggerOrderOptions GetSpotTriggerOrderOptions { get; } GetSpotTriggerOrderOptions GetSpotTriggerOrderOptions { get; }
@@ -39,7 +39,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary> /// <summary>
/// Cancel trigger order request options.<br /> /// Cancel trigger order request options.<br />
/// Use <see cref="EndpointOptions.RequiredExchangeParameters"/> and <see cref="EndpointOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br /> /// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object. /// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary> /// </summary>
CancelSpotTriggerOrderOptions CancelSpotTriggerOrderOptions { get; } CancelSpotTriggerOrderOptions CancelSpotTriggerOrderOptions { get; }
@@ -0,0 +1,66 @@
using CryptoExchange.Net.Objects;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Client for placing and managing futures orders
/// </summary>
public interface IFuturesOrderManagementSocketClient : ISharedClient
{
/// <summary>
/// How the trading fee is deducted
/// </summary>
SharedFeeDeductionType FuturesFeeDeductionType { get; }
/// <summary>
/// How the asset is determined in which the trading fee is paid
/// </summary>
SharedFeeAssetType FuturesFeeAssetType { get; }
/// <summary>
/// Supported order types for futures orders
/// </summary>
SharedOrderType[] FuturesSupportedOrderTypes { get; }
/// <summary>
/// Supported time in force types for futures orders
/// </summary>
SharedTimeInForce[] FuturesSupportedTimeInForce { get; }
/// <summary>
/// Supported quantity types for futures orders
/// </summary>
SharedQuantitySupport FuturesSupportedOrderQuantity { get; }
/// <summary>
/// Generate a new random client order id
/// </summary>
/// <returns></returns>
string GenerateClientOrderId();
/// <summary>
/// Futures place order request options.<br />
/// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary>
PlaceFuturesOrderSocketOptions PlaceFuturesOrderOptions { get; }
/// <summary>
/// Place a new futures order, see <see cref="PlaceFuturesOrderOptions"/> for request options and exchange specific required/optional parameters. <br />
/// </summary>
/// <param name="request">Request info</param>
/// <param name="ct">Cancellation token</param>
Task<QueryResult<SharedId>> PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct = default);
/// <summary>
/// Futures cancel order request options.<br />
/// Use <see cref="CapabilityOptions.RequiredExchangeParameters"/> and <see cref="CapabilityOptions.OptionalExchangeParameters"/> to check for required and optional parameters for the request. <br />
/// Exchange specific parameters can be added to the request via the `ExchangeParameters` property of the request object.
/// </summary>
CancelFuturesOrderSocketOptions CancelFuturesOrderOptions { get; }
/// <summary>
/// Cancel a futures order, see <see cref="CancelFuturesOrderOptions"/> for request options and exchange specific required/optional parameters. <br />
/// </summary>
/// <param name="request">Request info</param>
/// <param name="ct">Cancellation token</param>
Task<QueryResult<SharedId>> CancelFuturesOrderAsync(CancelOrderRequest request, CancellationToken ct = default);
}
}

Some files were not shown because too many files have changed in this diff Show More