1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-10-27 08:27:19 +00:00

Compare commits

..

5 Commits

18 changed files with 192 additions and 81 deletions

View File

@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
<Authors>JKorf</Authors>
<Description>Protobuf support for CryptoExchange.Net</Description>
<PackageVersion>9.8.0</PackageVersion>
<AssemblyVersion>9.8.0</AssemblyVersion>
<FileVersion>9.8.0</FileVersion>
<PackageVersion>9.9.0</PackageVersion>
<AssemblyVersion>9.9.0</AssemblyVersion>
<FileVersion>9.9.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
<RepositoryType>git</RepositoryType>
@ -41,7 +41,7 @@
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CryptoExchange.Net" Version="9.8.0" />
<PackageReference Include="CryptoExchange.Net" Version="9.9.0" />
<PackageReference Include="protobuf-net" Version="3.2.56" />
</ItemGroup>
</Project>

View File

@ -5,6 +5,9 @@
Protobuf support for CryptoExchange.Net.
## Release notes
* Version 9.9.0 - 06 Oct 2025
* Updated CryptoExchange.Net version to 9.9.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
* Version 9.8.0 - 30 Sep 2025
* Updated CryptoExchange.Net version to 9.8.0, see https://github.com/JKorf/CryptoExchange.Net/releases/

View File

@ -202,7 +202,7 @@ namespace CryptoExchange.Net.UnitTests
await sub;
// assert
ClassicAssert.IsFalse(client.SubClient.TestSubscription.Confirmed);
ClassicAssert.IsTrue(client.SubClient.TestSubscription.Status != SubscriptionStatus.Subscribed);
}
[TestCase()]
@ -225,7 +225,7 @@ namespace CryptoExchange.Net.UnitTests
await sub;
// assert
Assert.That(client.SubClient.TestSubscription.Confirmed);
Assert.That(client.SubClient.TestSubscription.Status == SubscriptionStatus.Subscribed);
}
}
}

View File

@ -269,6 +269,7 @@ namespace CryptoExchange.Net.Clients
return new CallResult<UpdateSubscription>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused")));
}
subscription.Status = SubscriptionStatus.Subscribing;
var waitEvent = new AsyncResetEvent(false);
var subQuery = subscription.CreateSubscriptionQuery(socketConnection);
if (subQuery != null)
@ -279,7 +280,7 @@ namespace CryptoExchange.Net.Clients
{
waitEvent?.Set();
var isTimeout = subResult.Error is CancellationRequestedError;
if (isTimeout && subscription.Confirmed)
if (isTimeout && subscription.Status == SubscriptionStatus.Subscribed)
{
// No response received, but the subscription did receive updates. We'll assume success
}
@ -287,6 +288,7 @@ namespace CryptoExchange.Net.Clients
{
_logger.FailedToSubscribe(socketConnection.SocketId, subResult.Error?.ToString());
// If this was a timeout we still need to send an unsubscribe to prevent messages coming in later
subscription.Status = SubscriptionStatus.Pending;
await socketConnection.CloseAsync(subscription).ConfigureAwait(false);
return new CallResult<UpdateSubscription>(subResult.Error!);
}
@ -295,7 +297,7 @@ namespace CryptoExchange.Net.Clients
subscription.HandleSubQueryResponse(subQuery.Response!);
}
subscription.Confirmed = true;
subscription.Status = SubscriptionStatus.Subscribed;
if (ct != default)
{
subscription.CancellationTokenRegistration = ct.Register(async () =>
@ -847,7 +849,7 @@ namespace CryptoExchange.Net.Clients
cs.SubscriptionStates.ForEach(subState =>
{
sb.AppendLine($"\t\t\tId: {subState.Id}");
sb.AppendLine($"\t\t\tConfirmed: {subState.Confirmed}");
sb.AppendLine($"\t\t\tStatus: {subState.Status}");
sb.AppendLine($"\t\t\tInvocations: {subState.Invocations}");
sb.AppendLine($"\t\t\tIdentifiers: [{subState.ListenMatcher.ToString()}]");
});

View File

@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId>
<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>
<PackageVersion>9.8.0</PackageVersion>
<AssemblyVersion>9.8.0</AssemblyVersion>
<FileVersion>9.8.0</FileVersion>
<PackageVersion>9.9.0</PackageVersion>
<AssemblyVersion>9.9.0</AssemblyVersion>
<FileVersion>9.9.0</FileVersion>
<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>
<RepositoryType>git</RepositoryType>

View File

@ -267,4 +267,31 @@ namespace CryptoExchange.Net.Objects
Succeed
}
/// <summary>
/// Subscription status
/// </summary>
public enum SubscriptionStatus
{
/// <summary>
/// Pending, waiting before (re)subscription can be started
/// </summary>
Pending,
/// <summary>
/// Currently (re)subscribing, will start producing updates soon if subscription is successful
/// </summary>
Subscribing,
/// <summary>
/// Subscribed and listening to updates
/// </summary>
Subscribed,
/// <summary>
/// Subscription is being closed and will stop producing updates
/// </summary>
Closing,
/// <summary>
/// Subscription is closed and will no long produce updates
/// </summary>
Closed
}
}

View File

@ -15,6 +15,7 @@ namespace CryptoExchange.Net.Objects.Sockets
private readonly Subscription _listener;
private object _eventLock = new object();
private bool _connectionEventsSubscribed = true;
private List<Action> _connectionClosedEventHandlers = new List<Action>();
private List<Action> _connectionLostEventHandlers = new List<Action>();
private List<Action<Error>> _resubscribeFailedEventHandlers = new List<Action<Error>>();
@ -22,6 +23,11 @@ namespace CryptoExchange.Net.Objects.Sockets
private List<Action> _activityPausedEventHandlers = new List<Action>();
private List<Action> _activityUnpausedEventHandlers = new List<Action>();
/// <summary>
/// Event when the status of the subscription changes
/// </summary>
public event Action<SubscriptionStatus>? SubscriptionStatusChanged;
/// <summary>
/// Event when the connection is lost. The socket will automatically reconnect when possible.
/// </summary>
@ -113,21 +119,34 @@ namespace CryptoExchange.Net.Objects.Sockets
_connection.ActivityUnpaused += HandleUnpausedEvent;
_listener = subscription;
_listener.Unsubscribed += HandleUnsubscribed;
_listener.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x);
}
private void HandleUnsubscribed()
private void UnsubscribeConnectionEvents()
{
lock (_eventLock)
{
if (!_connectionEventsSubscribed)
return;
_connection.ConnectionClosed -= HandleConnectionClosedEvent;
_connection.ConnectionLost -= HandleConnectionLostEvent;
_connection.ConnectionRestored -= HandleConnectionRestoredEvent;
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
_connection.ActivityPaused -= HandlePausedEvent;
_connection.ActivityUnpaused -= HandleUnpausedEvent;
_connectionEventsSubscribed = false;
}
}
private void HandleConnectionClosedEvent()
{
UnsubscribeConnectionEvents();
// If we're not the subscription closing this connection don't bother emitting
if (!_listener.IsClosingConnection)
return;
List<Action> handlers;
lock (_eventLock)
handlers = _connectionClosedEventHandlers.ToList();
@ -138,6 +157,12 @@ namespace CryptoExchange.Net.Objects.Sockets
private void HandleConnectionLostEvent()
{
if (!_listener.Active)
{
UnsubscribeConnectionEvents();
return;
}
List<Action> handlers;
lock (_eventLock)
handlers = _connectionLostEventHandlers.ToList();
@ -148,6 +173,12 @@ namespace CryptoExchange.Net.Objects.Sockets
private void HandleConnectionRestoredEvent(TimeSpan period)
{
if (!_listener.Active)
{
UnsubscribeConnectionEvents();
return;
}
List<Action<TimeSpan>> handlers;
lock (_eventLock)
handlers = _connectionRestoredEventHandlers.ToList();
@ -158,6 +189,12 @@ namespace CryptoExchange.Net.Objects.Sockets
private void HandleResubscribeFailedEvent(Error error)
{
if (!_listener.Active)
{
UnsubscribeConnectionEvents();
return;
}
List<Action<Error>> handlers;
lock (_eventLock)
handlers = _resubscribeFailedEventHandlers.ToList();
@ -168,6 +205,12 @@ namespace CryptoExchange.Net.Objects.Sockets
private void HandlePausedEvent()
{
if (!_listener.Active)
{
UnsubscribeConnectionEvents();
return;
}
List<Action> handlers;
lock (_eventLock)
handlers = _activityPausedEventHandlers.ToList();
@ -178,6 +221,12 @@ namespace CryptoExchange.Net.Objects.Sockets
private void HandleUnpausedEvent()
{
if (!_listener.Active)
{
UnsubscribeConnectionEvents();
return;
}
List<Action> handlers;
lock (_eventLock)
handlers = _activityUnpausedEventHandlers.ToList();

View File

@ -296,7 +296,7 @@ namespace CryptoExchange.Net.Sockets
lock (_listenersLock)
{
foreach (var subscription in _listeners.OfType<Subscription>().Where(l => l.UserSubscription))
foreach (var subscription in _listeners.OfType<Subscription>().Where(l => l.UserSubscription && !l.IsClosingConnection))
subscription.Reset();
foreach (var query in _listeners.OfType<Query>().ToList())
@ -527,10 +527,10 @@ namespace CryptoExchange.Net.Sockets
continue;
}
if (processor is Subscription subscriptionProcessor && !subscriptionProcessor.Confirmed)
if (processor is Subscription subscriptionProcessor && subscriptionProcessor.Status == SubscriptionStatus.Subscribing)
{
// If this message is for this listener then it is automatically confirmed, even if the subscription is not (yet) confirmed
subscriptionProcessor.Confirmed = true;
subscriptionProcessor.Status = SubscriptionStatus.Subscribed;
if (subscriptionProcessor.SubscriptionQuery?.TimeoutBehavior == TimeoutBehavior.Succeed)
// If this subscription has a query waiting for a timeout (success if there is no error response)
// then time it out now as the data is being received, so we assume it's successful
@ -657,13 +657,16 @@ namespace CryptoExchange.Net.Sockets
public async Task CloseAsync(Subscription subscription)
{
// If we are resubscribing this subscription at this moment we'll want to wait for a bit until it is finished to avoid concurrency issues
while (subscription.IsResubscribing)
while (subscription.Status == SubscriptionStatus.Subscribing)
await Task.Delay(50).ConfigureAwait(false);
subscription.Closed = true;
subscription.Status = SubscriptionStatus.Closing;
if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed)
{
subscription.Status = SubscriptionStatus.Closed;
return;
}
_logger.ClosingSubscription(SocketId, subscription.Id);
if (subscription.CancellationTokenRegistration.HasValue)
@ -675,7 +678,7 @@ namespace CryptoExchange.Net.Sockets
bool shouldCloseConnection;
lock (_listenersLock)
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Status == SubscriptionStatus.Closing || r.Status == SubscriptionStatus.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
if (!anyDuplicateSubscription)
{
@ -693,6 +696,7 @@ namespace CryptoExchange.Net.Sockets
if (Status == SocketStatus.Closing)
{
subscription.Status = SubscriptionStatus.Closed;
_logger.AlreadyClosing(SocketId);
return;
}
@ -700,6 +704,7 @@ namespace CryptoExchange.Net.Sockets
if (shouldCloseConnection)
{
Status = SocketStatus.Closing;
subscription.IsClosingConnection = true;
_logger.ClosingNoMoreSubscriptions(SocketId);
await CloseAsync().ConfigureAwait(false);
}
@ -707,7 +712,7 @@ namespace CryptoExchange.Net.Sockets
lock (_listenersLock)
_listeners.Remove(subscription);
subscription.InvokeUnsubscribedHandler();
subscription.Status = SubscriptionStatus.Closed;
}
/// <summary>
@ -991,7 +996,7 @@ namespace CryptoExchange.Net.Sockets
List<Subscription> subList;
lock (_listenersLock)
subList = _listeners.OfType<Subscription>().Where(x => !x.Closed).Skip(batch * batchSize).Take(batchSize).ToList();
subList = _listeners.OfType<Subscription>().Where(x => x.Active).Skip(batch * batchSize).Take(batchSize).ToList();
if (subList.Count == 0)
break;
@ -1000,34 +1005,32 @@ namespace CryptoExchange.Net.Sockets
foreach (var subscription in subList)
{
subscription.ConnectionInvocations = 0;
if (subscription.Closed)
if (!subscription.Active)
// Can be closed during resubscribing
continue;
subscription.IsResubscribing = true;
subscription.Status = SubscriptionStatus.Subscribing;
var result = await ApiClient.RevitalizeRequestAsync(subscription).ConfigureAwait(false);
if (!result)
{
_logger.FailedRequestRevitalization(SocketId, result.Error?.ToString());
subscription.IsResubscribing = false;
subscription.Status = SubscriptionStatus.Pending;
return result;
}
var subQuery = subscription.CreateSubscriptionQuery(this);
if (subQuery == null)
{
subscription.IsResubscribing = false;
subscription.Status = SubscriptionStatus.Subscribed;
continue;
}
var waitEvent = new AsyncResetEvent(false);
taskList.Add(SendAndWaitQueryAsync(subQuery, waitEvent).ContinueWith((r) =>
{
subscription.IsResubscribing = false;
subscription.Status = r.Result.Success ? SubscriptionStatus.Subscribed: SubscriptionStatus.Pending;
subscription.HandleSubQueryResponse(subQuery.Response!);
waitEvent.Set();
if (r.Result.Success)
subscription.Confirmed = true;
return r.Result;
}));
}

View File

@ -35,20 +35,32 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public bool UserSubscription { get; set; }
private SubscriptionStatus _status;
/// <summary>
/// Has the subscription been confirmed
/// Current subscription status
/// </summary>
public bool Confirmed { get; set; }
public SubscriptionStatus Status
{
get => _status;
set
{
if (_status == value)
return;
_status = value;
Task.Run(() => StatusChanged?.Invoke(value));
}
}
/// <summary>
/// Is the subscription closed
/// Whether the subscription is active
/// </summary>
public bool Closed { get; set; }
public bool Active => Status != SubscriptionStatus.Closing && Status != SubscriptionStatus.Closed;
/// <summary>
/// Is the subscription currently resubscribing
/// Whether the unsubscribing of this subscription lead to the closing of the connection
/// </summary>
public bool IsResubscribing { get; set; }
public bool IsClosingConnection { get; set; }
/// <summary>
/// Logger
@ -77,7 +89,7 @@ namespace CryptoExchange.Net.Sockets
/// <summary>
/// Listener unsubscribed event
/// </summary>
public event Action? Unsubscribed;
public event Action<SubscriptionStatus>? StatusChanged;
/// <summary>
/// Subscription topic
@ -167,7 +179,7 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public void Reset()
{
Confirmed = false;
Status = SubscriptionStatus.Pending;
DoHandleReset();
}
@ -185,24 +197,16 @@ namespace CryptoExchange.Net.Sockets
Exception?.Invoke(e);
}
/// <summary>
/// Invoke the unsubscribed event
/// </summary>
public void InvokeUnsubscribedHandler()
{
Unsubscribed?.Invoke();
}
/// <summary>
/// State of this subscription
/// </summary>
/// <param name="Id">The id of the subscription</param>
/// <param name="Confirmed">True when the subscription query is handled (either accepted or rejected)</param>
/// <param name="Status">Subscription status</param>
/// <param name="Invocations">Number of times this subscription got a message</param>
/// <param name="ListenMatcher">Matcher for this subscription</param>
public record SubscriptionState(
int Id,
bool Confirmed,
SubscriptionStatus Status,
int Invocations,
MessageMatcher ListenMatcher
);
@ -213,7 +217,7 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns>
public SubscriptionState GetState()
{
return new SubscriptionState(Id, Confirmed, TotalInvocations, MessageMatcher);
return new SubscriptionState(Id, Status, TotalInvocations, MessageMatcher);
}
}

View File

@ -18,7 +18,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="authenticated"></param>
public SystemSubscription(ILogger logger, bool authenticated = false) : base(logger, authenticated, false)
{
Confirmed = true;
Status = SubscriptionStatus.Subscribed;
}
/// <inheritdoc />

View File

@ -5,30 +5,31 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="11.7.1" />
<PackageReference Include="Bitfinex.Net" Version="9.7.0" />
<PackageReference Include="BitMart.Net" Version="2.8.0" />
<PackageReference Include="BloFin.Net" Version="1.0.0" />
<PackageReference Include="Bybit.Net" Version="5.8.0" />
<PackageReference Include="CoinEx.Net" Version="9.7.0" />
<PackageReference Include="CoinW.Net" Version="1.4.0" />
<PackageReference Include="CryptoCom.Net" Version="2.8.0" />
<PackageReference Include="DeepCoin.Net" Version="2.7.0" />
<PackageReference Include="GateIo.Net" Version="2.8.1" />
<PackageReference Include="HyperLiquid.Net" Version="2.12.0" />
<PackageReference Include="JK.BingX.Net" Version="2.7.0" />
<PackageReference Include="JK.Bitget.Net" Version="2.7.1" />
<PackageReference Include="JK.Mexc.Net" Version="3.8.0" />
<PackageReference Include="JK.OKX.Net" Version="3.7.1" />
<PackageReference Include="JKorf.BitMEX.Net" Version="2.7.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="2.7.0" />
<PackageReference Include="JKorf.HTX.Net" Version="7.7.0" />
<PackageReference Include="KrakenExchange.Net" Version="6.7.0" />
<PackageReference Include="Kucoin.Net" Version="7.7.1" />
<PackageReference Include="Binance.Net" Version="11.8.0" />
<PackageReference Include="Bitfinex.Net" Version="9.8.0" />
<PackageReference Include="BitMart.Net" Version="2.9.0" />
<PackageReference Include="BloFin.Net" Version="1.1.0" />
<PackageReference Include="Bybit.Net" Version="5.9.0" />
<PackageReference Include="CoinEx.Net" Version="9.8.0" />
<PackageReference Include="CoinW.Net" Version="1.5.0" />
<PackageReference Include="CryptoCom.Net" Version="2.9.0" />
<PackageReference Include="DeepCoin.Net" Version="2.8.0" />
<PackageReference Include="GateIo.Net" Version="2.9.1" />
<PackageReference Include="HyperLiquid.Net" Version="2.13.0" />
<PackageReference Include="JK.BingX.Net" Version="2.8.0" />
<PackageReference Include="JK.Bitget.Net" Version="2.8.0" />
<PackageReference Include="JK.Mexc.Net" Version="3.9.0" />
<PackageReference Include="JK.OKX.Net" Version="3.8.0" />
<PackageReference Include="Jkorf.Aster.Net" Version="1.0.0" />
<PackageReference Include="JKorf.BitMEX.Net" Version="2.8.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="2.8.0" />
<PackageReference Include="JKorf.HTX.Net" Version="7.8.0" />
<PackageReference Include="KrakenExchange.Net" Version="6.8.0" />
<PackageReference Include="Kucoin.Net" Version="7.8.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Toobit.Net" Version="1.6.0" />
<PackageReference Include="WhiteBit.Net" Version="2.8.0" />
<PackageReference Include="XT.Net" Version="2.7.0" />
<PackageReference Include="Toobit.Net" Version="1.7.0" />
<PackageReference Include="WhiteBit.Net" Version="2.9.0" />
<PackageReference Include="XT.Net" Version="2.8.0" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,5 @@
@page "/"
@inject IAsterRestClient asterClient
@inject IBinanceRestClient binanceClient
@inject IBingXRestClient bingXClient
@inject IBitfinexRestClient bitfinexClient
@ -34,6 +35,7 @@
protected override async Task OnInitializedAsync()
{
var asterTask = asterClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
var binanceTask = binanceClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
var bingXTask = bingXClient.SpotApi.ExchangeData.GetTickersAsync("BTC-USDT");
var bitfinexTask = bitfinexClient.SpotApi.ExchangeData.GetTickerAsync("tBTCUSD");
@ -49,7 +51,7 @@
var deepCoinTask = deepCoinClient.ExchangeApi.ExchangeData.GetTickersAsync(DeepCoin.Net.Enums.SymbolType.Spot);
var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
var htxTask = htxClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt");
var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync(); // HyperLiquid does not have BTC spot trading
var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync();
var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD");
var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
var mexcTask = mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
@ -58,7 +60,10 @@
var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync();
var xtTask = xtClient.SpotApi.ExchangeData.GetTickersAsync("btc_usdt");
await Task.WhenAll(binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bybitTask, coinexTask, deepCoinTask, gateioTask, htxTask, krakenTask, kucoinTask, mexcTask, okxTask);
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bitmexTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask, htxTask, krakenTask, kucoinTask, mexcTask, okxTask);
if (asterTask.Result.Success)
_prices.Add("Aster", asterTask.Result.Data.LastPrice);
if (binanceTask.Result.Success)
_prices.Add("Binance", binanceTask.Result.Data.LastPrice);

View File

@ -1,4 +1,5 @@
@page "/LiveData"
@inject IAsterSocketClient asterSocketClient
@inject IBinanceSocketClient binanceSocketClient
@inject IBingXSocketClient bingXSocketClient
@inject IBitfinexSocketClient bitfinexSocketClient
@ -43,6 +44,8 @@
{
var tasks = new Task<CallResult<UpdateSubscription>>[]
{
// Aster doesn't support the ETH/BTC pair
//asterSocketClient.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Aster", data.Data.LastPrice)),
binanceSocketClient.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Binance", data.Data.LastPrice)),
bingXSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("BingX", data.Data.LastPrice)),
bitfinexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),

View File

@ -1,6 +1,7 @@
@page "/OrderBooks"
@using System.Collections.Concurrent
@using System.Timers
@using Aster.Net.Interfaces
@using Binance.Net.Interfaces
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@ -27,6 +28,7 @@
@using Toobit.Net.Interfaces;
@using WhiteBit.Net.Interfaces
@using XT.Net.Interfaces
@inject IAsterOrderBookFactory asterFactory
@inject IBinanceOrderBookFactory binanceFactory
@inject IBingXOrderBookFactory bingXFactory
@inject IBitfinexOrderBookFactory bitfinexFactory
@ -83,6 +85,7 @@
_books = new Dictionary<string, ISymbolOrderBook>
{
{ "Aster", binanceFactory.CreateSpot("ETHUSDT") },
{ "Binance", binanceFactory.CreateSpot("ETHBTC") },
{ "BingX", bingXFactory.CreateSpot("ETH-BTC") },
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },

View File

@ -1,6 +1,7 @@
@page "/Trackers"
@using System.Collections.Concurrent
@using System.Timers
@using Aster.Net.Interfaces
@using Binance.Net.Interfaces
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@ -28,6 +29,7 @@
@using Toobit.Net.Interfaces;
@using WhiteBit.Net.Interfaces
@using XT.Net.Interfaces
@inject IAsterTrackerFactory asterFactory
@inject IBinanceTrackerFactory binanceFactory
@inject IBingXTrackerFactory bingXFactory
@inject IBitfinexTrackerFactory bitfinexFactory
@ -79,6 +81,7 @@
_trackers = new List<ITradeTracker>
{
{ asterFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
{ binanceFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
{ bingXFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
{ bitfinexFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },

View File

@ -33,6 +33,7 @@ namespace BlazorClient
restOptions.ApiCredentials = new ApiCredentials("KEY", "SECRET");
});
services.AddAster();
services.AddBingX();
services.AddBitfinex();
services.AddBitget();

View File

@ -8,6 +8,7 @@
@using Microsoft.JSInterop
@using BlazorClient
@using BlazorClient.Shared
@using Aster.Net.Interfaces.Clients;
@using Binance.Net.Interfaces.Clients;
@using BingX.Net.Interfaces.Clients;
@using Bitfinex.Net.Interfaces.Clients;

View File

@ -12,6 +12,7 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
||Exchange|Type|Repository|Nuget|Referral Link|Referral Fee Discount|
|--|--|--|--|--|--|--|
|![Aster](https://raw.githubusercontent.com/JKorf/Aster.Net/refs/heads/main/Aster.Net/Icon/icon.png)|Aster|DEX|[JKorf/Aster.Net](https://github.com/JKorf/Aster.Net)|[![Nuget version](https://img.shields.io/nuget/v/JKorf.Aster.net.svg?style=flat-square)](https://www.nuget.org/packages/JKorf.Aster.Net)|[Link](https://www.asterdex.com/en/referral/FD2E11)|4%|
|![Binance](https://raw.githubusercontent.com/JKorf/Binance.Net/refs/heads/master/Binance.Net/Icon/icon.png)|Binance|CEX|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[![Nuget version](https://img.shields.io/nuget/v/Binance.net.svg?style=flat-square)](https://www.nuget.org/packages/Binance.Net)|[Link](https://accounts.binance.com/register?ref=X5K3F2ZG)|20%|
|![BingX](https://raw.githubusercontent.com/JKorf/BingX.Net/refs/heads/main/BingX.Net/Icon/BingX.png)|BingX|CEX|[JKorf/BingX.Net](https://github.com/JKorf/BingX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.BingX.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.BingX.Net)|[Link](https://bingx.com/invite/FFHRJKWG/)|20%|
|![Bitfinex](https://raw.githubusercontent.com/JKorf/Bitfinex.Net/refs/heads/master/Bitfinex.Net/Icon/icon.png)|Bitfinex|CEX|[JKorf/Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitfinex.net.svg?style=flat-square)](https://www.nuget.org/packages/Bitfinex.Net)|-|-|
@ -64,6 +65,11 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
## Release notes
* Version 9.9.0 - 06 Oct 2025
* Updated socket Subscription status handling
* Added SubscriptionStatusChanged event to UpdateSubscription (SubscribeAsync methods reponse)
* Fixed timing issue for connection events in UpdateSubscription
* Version 9.8.0 - 30 Sep 2025
* Added ContractAddress to SharedAsset model
* Added ITrackerFactory interface