1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-12 00:43:03 +00:00

Compare commits

...

23 Commits

Author SHA1 Message Date
Jkorf 74f73dc790 Updated to version 8.7.2 2025-01-27 13:24:08 +01:00
Jkorf 0527a8a76e Some small fixes in the System.Text.Json ArrayConverter, added support for flags in EnumConverter 2025-01-27 11:52:07 +01:00
Jkorf c693eb8c02 Updated to version 8.7.1 2025-01-24 08:42:08 +01:00
Jkorf 3eb28c7fed Added HyperLiquid referral 2025-01-23 09:35:44 +01:00
JKorf 618c4922b9 Added Authenticated property to IBaseApiClient interface 2025-01-22 19:11:38 +01:00
Jkorf c81b15861d Updated examples and docs with HyperLiquid references 2025-01-21 15:24:23 +01:00
Jkorf 4a5832cccd Updated to version 8.7.0 2025-01-21 14:02:42 +01:00
Jkorf 4e47c4cbdf Updated CheckForMissingInterfaces test 2025-01-21 14:00:30 +01:00
Jkorf 2af1520ecc Added PriceSignificationFigures to SharedSpotSymbol model 2025-01-21 14:00:08 +01:00
Jkorf cf397af3ab Added GetMillisecondTimestampLong helper method to AuthenticationProvider 2025-01-21 13:47:05 +01:00
JKorf a1479705e2 Fixed typo 2025-01-13 17:49:38 +01:00
Jkorf 175e23f110 Updated to version 8.6.1 2025-01-09 16:25:11 +01:00
Jkorf 9b7019ded2 Removed websocket Error callback when exception is expected 2025-01-09 16:23:34 +01:00
Jkorf 7904aa9ba7 Fixed websocket connection getting stuck after a ping frame timeout, removed unnecessary type restraints on RestApiClient.SendAsync methods 2025-01-09 16:18:13 +01:00
Jkorf 3fe6db589f Updated to version 8.6.0 2025-01-07 13:25:50 +01:00
Jkorf 625dccbbe4 Added ExchangeType enum, some small improvements 2025-01-07 13:22:16 +01:00
Jkorf e650771d16 Added response headers parameter to RestApiClient.TryParseError method, added check for ServerRateLimitError on the result 2025-01-07 10:19:37 +01:00
Jkorf 3dad28b19d Added IFeeRestClient to service registration 2025-01-07 08:59:51 +01:00
Jkorf 2b9fda985e Add support for passing weight to apply to an individual ratelimit guard 2025-01-07 08:35:06 +01:00
JKorf ff8759409b Use Convert.ToHexString if available 2025-01-06 21:38:42 +01:00
JKorf 0d9627c13f Changed socket no data reconnect message to LogLevel Warning 2024-12-23 20:03:56 +01:00
Jkorf 0179fd7e2a Fixed workflow automated tests 2024-12-23 14:43:23 +01:00
Jkorf b8d0b0cf95 Workflow fix 2024-12-23 14:36:18 +01:00
32 changed files with 496 additions and 90 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 8.0.x
dotnet-version: 9.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
@@ -403,10 +403,14 @@ namespace CryptoExchange.Net.Authentication
/// <returns></returns>
protected static string BytesToHexString(byte[] buff)
{
#if NET9_0_OR_GREATER
return Convert.ToHexString(buff);
#else
var result = string.Empty;
foreach (var t in buff)
result += t.ToString("X2");
return result;
#endif
}
/// <summary>
@@ -439,6 +443,16 @@ namespace CryptoExchange.Net.Authentication
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Get millisecond timestamp as a long including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected long GetMillisecondTimestampLong(RestApiClient apiClient)
{
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
}
/// <summary>
/// Return the serialized request body
/// </summary>
+1 -3
View File
@@ -38,9 +38,7 @@ namespace CryptoExchange.Net.Clients
/// </summary>
public bool OutputOriginalData { get; }
/// <summary>
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
/// </summary>
/// <inheritdoc />
public bool Authenticated => ApiOptions.ApiCredentials != null || ClientOptions.ApiCredentials != null;
/// <summary>
+34 -15
View File
@@ -154,6 +154,7 @@ namespace CryptoExchange.Net.Clients
/// <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>
/// <returns></returns>
protected virtual Task<WebCallResult<T>> SendAsync<T>(
string baseAddress,
@@ -161,7 +162,8 @@ namespace CryptoExchange.Net.Clients
ParameterCollection? parameters,
CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null,
int? weight = null) where T : class
int? weight = null,
int? weightSingleLimiter = null)
{
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
return SendAsync<T>(
@@ -171,7 +173,8 @@ namespace CryptoExchange.Net.Clients
parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
cancellationToken,
additionalHeaders,
weight);
weight,
weightSingleLimiter);
}
/// <summary>
@@ -185,6 +188,7 @@ namespace CryptoExchange.Net.Clients
/// <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>
/// <returns></returns>
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
string baseAddress,
@@ -193,7 +197,8 @@ namespace CryptoExchange.Net.Clients
ParameterCollection? bodyParameters,
CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null,
int? weight = null) where T : class
int? weight = null,
int? weightSingleLimiter = null)
{
string? cacheKey = null;
if (ShouldCache(definition))
@@ -217,7 +222,7 @@ namespace CryptoExchange.Net.Clients
currentTry++;
var requestId = ExchangeHelpers.NextId();
var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight).ConfigureAwait(false);
var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight, weightSingleLimiter).ConfigureAwait(false);
if (!prepareResult)
return new WebCallResult<T>(prepareResult.Error!);
@@ -258,6 +263,7 @@ namespace CryptoExchange.Net.Clients
/// <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</param>
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
protected virtual async Task<CallResult> PrepareAsync(
@@ -266,10 +272,9 @@ namespace CryptoExchange.Net.Clients
RequestDefinition definition,
CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null,
int? weight = null)
int? weight = null,
int? weightSingleLimiter = null)
{
var requestWeight = weight ?? definition.Weight;
// Time sync
if (definition.Authenticated)
{
@@ -295,6 +300,7 @@ namespace CryptoExchange.Net.Clients
}
// Rate limiting
var requestWeight = weight ?? definition.Weight;
if (requestWeight != 0)
{
if (definition.RateLimitGate == null)
@@ -316,7 +322,8 @@ namespace CryptoExchange.Net.Clients
if (ClientOptions.RateLimiterEnabled)
{
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false);
var singleRequestWeight = weightSingleLimiter ?? 1;
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false);
if (!limitResult)
return new CallResult(limitResult.Error!);
}
@@ -617,7 +624,7 @@ namespace CryptoExchange.Net.Clients
paramString = $" with request body '{request.Content}'";
var headers = request.GetHeaders();
if (headers.Any())
if (headers.Count != 0)
paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
TotalRequestsMade++;
@@ -693,10 +700,21 @@ namespace CryptoExchange.Net.Clients
}
// Json response received
var parsedError = TryParseError(accessor);
var parsedError = TryParseError(response.ResponseHeaders, accessor);
if (parsedError != null)
{
if (parsedError is ServerRateLimitError rateError)
{
if (rateError.RetryAfter != null && gate != null && ClientOptions.RateLimiterEnabled)
{
_logger.RestApiRateLimitPauseUntil(request.RequestId, rateError.RetryAfter.Value);
await gate.SetRetryAfterGuardAsync(rateError.RetryAfter.Value).ConfigureAwait(false);
}
}
// Success status code, but TryParseError determined it was an error response
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, parsedError);
}
var deserializeResult = accessor.Deserialize<T>();
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, sw.Elapsed, responseLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, deserializeResult.Data, deserializeResult.Error);
@@ -730,12 +748,13 @@ namespace CryptoExchange.Net.Clients
/// <summary>
/// Can be used to parse an error even though response status indicates success. Some apis always return 200 OK, even though there is an error.
/// When setting manualParseError to true this method will be called for each response to be able to check if the response is an error or not.
/// This method will be called for each response to be able to check if the response is an error or not.
/// If the response is an error this method should return the parsed error, else it should return null
/// </summary>
/// <param name="accessor">Data accessor</param>
/// <param name="responseHeaders">The response headers</param>
/// <returns>Null if not an error, Error otherwise</returns>
protected virtual ServerError? TryParseError(IMessageAccessor accessor) => null;
protected virtual Error? TryParseError(IEnumerable<KeyValuePair<string, IEnumerable<string>>> responseHeaders, IMessageAccessor accessor) => null;
/// <summary>
/// Can be used to indicate that a request should be retried. Defaults to false. Make sure to retry a max number of times (based on the the tries parameter) or the request will retry forever.
@@ -752,7 +771,7 @@ namespace CryptoExchange.Net.Clients
// Only retry once
return false;
if ((int?)callResult.ResponseStatusCode == 429
if (callResult.Error is ServerRateLimitError
&& ClientOptions.RateLimiterEnabled
&& ClientOptions.RateLimitingBehaviour != RateLimitingBehaviour.Fail
&& gate != null)
@@ -889,8 +908,8 @@ namespace CryptoExchange.Net.Clients
{
// Write the parameters as json in the body
string stringData;
if (parameters.Count == 1 && parameters.ContainsKey(Constants.BodyPlaceHolderKey))
stringData = CreateSerializer().Serialize(parameters[Constants.BodyPlaceHolderKey]);
if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
stringData = CreateSerializer().Serialize(value);
else
stringData = CreateSerializer().Serialize(parameters);
request.SetContent(stringData, contentType);
@@ -89,6 +89,10 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{
if (prop.PropertyInfo.PropertyType == typeof(string))
writer.WriteStringValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
else if(prop.PropertyInfo.PropertyType.IsEnum)
writer.WriteStringValue(EnumConverter.GetString(objValue));
else if (prop.PropertyInfo.PropertyType == typeof(bool))
writer.WriteBooleanValue((bool)objValue);
else
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture)!);
}
@@ -187,12 +191,12 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
_converterOptionsCache.TryAdd(attribute.JsonConverterType, newOptions);
}
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, newOptions);
value = JsonDocument.ParseValue(ref reader).Deserialize(attribute.PropertyInfo.PropertyType, newOptions);
}
else if (attribute.DefaultDeserialization)
{
// Use default deserialization
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, options);
value = JsonDocument.ParseValue(ref reader).Deserialize(attribute.PropertyInfo.PropertyType, SerializerOptions.WithConverters);
}
else
{
@@ -172,6 +172,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
return true;
}
if (objectType.IsDefined(typeof(FlagsAttribute)))
{
var intValue = int.Parse(value);
result = Enum.ToObject(objectType, intValue);
return true;
}
try
{
// If no explicit mapping is found try to parse string
+3 -3
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>8.5.0</PackageVersion>
<AssemblyVersion>8.5.0</AssemblyVersion>
<FileVersion>8.5.0</FileVersion>
<PackageVersion>8.7.2</PackageVersion>
<AssemblyVersion>8.7.2</AssemblyVersion>
<FileVersion>8.7.2</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</PackageTags>
<RepositoryType>git</RepositoryType>
+1
View File
@@ -261,6 +261,7 @@ namespace CryptoExchange.Net
if (price != null)
{
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
{
+2
View File
@@ -435,6 +435,8 @@ namespace CryptoExchange.Net
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IFeeRestClient)client(x)!);
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
@@ -16,6 +16,11 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
string BaseAddress { get; }
/// <summary>
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
/// </summary>
bool Authenticated { get; }
/// <summary>
/// Format a base and quote asset to an exchange accepted symbol
/// </summary>
@@ -36,7 +41,7 @@ namespace CryptoExchange.Net.Interfaces
/// <summary>
/// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
/// </summary>
/// <typeparam name="T">Api crentials type</typeparam>
/// <typeparam name="T">Api credentials type</typeparam>
/// <param name="options">Options to set</param>
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
}
@@ -35,6 +35,7 @@ namespace CryptoExchange.Net.Logging.Extensions
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimoutReconnect;
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
static CryptoExchangeWebSocketClientLoggingExtension()
{
@@ -169,7 +170,7 @@ namespace CryptoExchange.Net.Logging.Extensions
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
_noDataReceiveTimoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
LogLevel.Debug,
LogLevel.Warning,
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
@@ -180,9 +181,14 @@ namespace CryptoExchange.Net.Logging.Extensions
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
LogLevel.Trace,
new EventId(1028, "SocketProcessingStateChanged"),
new EventId(1029, "SocketProcessingStateChanged"),
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
_socketPingTimeout = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(1030, "SocketPingTimeout"),
"[Sckt {Id}] ping frame timeout; reconnecting socket");
}
public static void SocketConnecting(
@@ -358,5 +364,11 @@ namespace CryptoExchange.Net.Logging.Extensions
{
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
}
public static void SocketPingTimeout(
this ILogger logger, int socketId)
{
_socketPingTimeout(logger, socketId, null);
}
}
}
@@ -183,7 +183,7 @@ namespace CryptoExchange.Net.Logging.Extensions
_sendingData = LoggerMessage.Define<int, int, string>(
LogLevel.Trace,
new EventId(2028, "SendingData"),
"[Sckt {SocketId}] [Req {RequestId}] sending messsage: {Data}");
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Warning,
+14
View File
@@ -235,4 +235,18 @@
Cache
}
/// <summary>
/// Type of exchange
/// </summary>
public enum ExchangeType
{
/// <summary>
/// Centralized
/// </summary>
CEX,
/// <summary>
/// Decentralized
/// </summary>
DEX
}
}
@@ -58,9 +58,38 @@ namespace CryptoExchange.Net.Objects
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null)
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching);
/// <summary>
/// Get a definition if it is already in the cache or create a new definition and add it to the cache
/// </summary>
/// <param name="identifier">Request identifier</param>
/// <param name="method">The HttpMethod</param>
/// <param name="path">Endpoint path</param>
/// <param name="rateLimitGate">The rate limit gate</param>
/// <param name="limitGuard">The rate limit guard for this specific endpoint</param>
/// <param name="weight">Request weight</param>
/// <param name="authenticated">Endpoint is authenticated</param>
/// <param name="requestBodyFormat">Request body format</param>
/// <param name="parameterPosition">Parameter position</param>
/// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param>
/// <returns></returns>
public RequestDefinition GetOrCreate(
string identifier,
HttpMethod method,
string path,
IRateLimitGate? rateLimitGate,
int weight,
bool authenticated,
IRateLimitGuard? limitGuard = null,
RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null)
{
if (!_definitions.TryGetValue(method + path, out var def))
if (!_definitions.TryGetValue(identifier, out var def))
{
def = new RequestDefinition(path, method)
{
@@ -73,7 +102,7 @@ namespace CryptoExchange.Net.Objects
ParameterPosition = parameterPosition,
PreventCaching = preventCaching ?? false
};
_definitions.TryAdd(method + path, def);
_definitions.TryAdd(identifier, def);
}
return def;
+2 -2
View File
@@ -82,12 +82,12 @@ namespace CryptoExchange.Net.Objects
TimeSyncState.LastSyncTime = DateTime.UtcNow;
if (offset.TotalMilliseconds > 0 && offset.TotalMilliseconds < 500)
{
Logger.Log(LogLevel.Information, $"{TimeSyncState.ApiName} Time offset within limits, set offset to 0ms");
Logger.Log(LogLevel.Information, "{TimeSyncState.ApiName} Time offset within limits, set offset to 0ms", TimeSyncState.ApiName);
TimeSyncState.TimeOffset = TimeSpan.Zero;
}
else
{
Logger.Log(LogLevel.Information, $"{TimeSyncState.ApiName} Time offset set to {Math.Round(offset.TotalMilliseconds)}ms");
Logger.Log(LogLevel.Information, "{TimeSyncState.ApiName} Time offset set to {Offset}ms", TimeSyncState.ApiName, Math.Round(offset.TotalMilliseconds));
TimeSyncState.TimeOffset = offset;
}
}
@@ -68,8 +68,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
/// <param name="baseAddress">The host address</param>
/// <param name="apiKey">The API key</param>
/// <param name="behaviour">Behaviour when rate limit is hit</param>
/// <param name="requestWeight">The weight to apply to the limit guard</param>
/// <param name="ct">Cancelation token</param>
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, RateLimitingBehaviour behaviour, CancellationToken ct);
Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct);
}
}
@@ -69,6 +69,7 @@ namespace CryptoExchange.Net.RateLimiting
RequestDefinition definition,
string host,
string? apiKey,
int requestWeight,
RateLimitingBehaviour rateLimitingBehaviour,
CancellationToken ct)
{
@@ -77,7 +78,7 @@ namespace CryptoExchange.Net.RateLimiting
_waitingCount++;
try
{
return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, 1, rateLimitingBehaviour, ct).ConfigureAwait(false);
return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
@@ -37,7 +37,7 @@ namespace CryptoExchange.Net.Requests
_httpClient = CreateClient(proxy, requestTimeout);
}
private HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
{
var handler = new HttpClientHandler();
try
@@ -46,6 +46,10 @@
/// </summary>
public int? PriceDecimals { get; set; }
/// <summary>
/// The max amount of significant figures to use for price. For example with value of 5 these values are valid: 0.00001, 0.12300, 123.53, 12345, but this is not: 12345.1
/// </summary>
public int? PriceSignificantFigures { get; set; }
/// <summary>
/// Whether the symbol is currently available for trading
/// </summary>
public bool Trading { get; set; }
@@ -587,15 +587,27 @@ namespace CryptoExchange.Net.Sockets
lock (_receivedMessagesLock)
_receivedMessages.Add(new ReceiveItem(DateTime.UtcNow, receiveResult.Count));
}
catch (OperationCanceledException)
catch (OperationCanceledException ex)
{
if (ex.InnerException?.InnerException?.Message.Equals("The WebSocket didn't recieve a Pong frame in response to a Ping frame within the configured KeepAliveTimeout.") == true)
{
// Spefic case that the websocket connection got closed because of a ping frame timeout
// Unfortunately doesn't seem to be a nicer way to catch
_logger.SocketPingTimeout(Id);
}
if (_closeTask?.IsCompleted != false)
_closeTask = CloseInternalAsync();
// canceled
break;
}
catch (Exception wse)
{
// Connection closed unexpectedly
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
if (!_ctsSource.Token.IsCancellationRequested)
// Connection closed unexpectedly
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
if (_closeTask?.IsCompleted != false)
_closeTask = CloseInternalAsync();
break;
@@ -105,7 +105,7 @@ namespace CryptoExchange.Net.Testing.Comparers
int i = 0;
foreach (var item in jObj.Children())
{
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p;
if (arrayProp != null)
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!);
i++;
@@ -264,9 +264,9 @@ namespace CryptoExchange.Net.Testing.Comparers
int i = 0;
foreach (var item in jtoken.Children())
{
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p;
if (arrayProp != null)
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), propertyType, arrayProp.Name, "Array index " + i, ignoreProperties);
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties);
i++;
}
@@ -350,7 +350,7 @@ namespace CryptoExchange.Net.Testing.Comparers
int i = 0;
foreach (var item in jObjs.Children())
{
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p;
if (arrayProp != null)
CheckPropertyValue(method, item, arrayProp.GetValue(propertyValue), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!);
i++;
+14 -8
View File
@@ -150,9 +150,9 @@ namespace CryptoExchange.Net.Testing
/// </summary>
/// <typeparam name="TClient"></typeparam>
/// <exception cref="Exception"></exception>
public static void CheckForMissingRestInterfaces<TClient>()
public static void CheckForMissingRestInterfaces<TClient>(string[]? excludeInterfaces = null)
{
CheckForMissingInterfaces(typeof(TClient), typeof(Task));
CheckForMissingInterfaces(typeof(TClient), typeof(Task), excludeInterfaces);
}
/// <summary>
@@ -160,26 +160,32 @@ namespace CryptoExchange.Net.Testing
/// </summary>
/// <typeparam name="TClient"></typeparam>
/// <exception cref="Exception"></exception>
public static void CheckForMissingSocketInterfaces<TClient>()
public static void CheckForMissingSocketInterfaces<TClient>(string[]? excludeInterfaces = null)
{
CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>));
CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>), excludeInterfaces);
}
private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes)
private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes, string[]? excludeInterfaces = null)
{
var assembly = Assembly.GetAssembly(clientType);
var interfaceType = clientType.GetInterface("I" + clientType.Name);
var clientInterfaces = assembly!.GetTypes().Where(t => t.Name.StartsWith("I" + clientType.Name) && !t.Name.EndsWith("Shared"));
var clientInterfaces = assembly!.GetTypes()
.Where(t => t.Name.StartsWith("I" + clientType.Name)
&& !t.Name.EndsWith("Shared")
&& (excludeInterfaces?.Contains(t.Name) != true));
foreach (var clientInterface in clientInterfaces)
{
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && t != clientInterface);
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && !t.IsInterface && t != clientInterface);
foreach (var implementation in implementations)
{
int methods = 0;
foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType)))
{
var interfaceMethod = clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray()) ?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
var interfaceMethod =
clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray())
?? clientInterface.GetInterfaces().Select(x => x.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray())).FirstOrDefault()
?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
methods++;
}
+18 -17
View File
@@ -5,23 +5,24 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="10.9.0" />
<PackageReference Include="Bitfinex.Net" Version="7.10.0" />
<PackageReference Include="BitMart.Net" Version="1.7.0" />
<PackageReference Include="Bybit.Net" Version="3.16.0" />
<PackageReference Include="CoinEx.Net" Version="7.9.0" />
<PackageReference Include="CryptoCom.Net" Version="1.2.0" />
<PackageReference Include="GateIo.Net" Version="1.12.0" />
<PackageReference Include="JK.BingX.Net" Version="1.14.0" />
<PackageReference Include="JK.Bitget.Net" Version="1.13.0" />
<PackageReference Include="JK.Mexc.Net" Version="1.11.0" />
<PackageReference Include="JK.OKX.Net" Version="2.8.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="1.4.0" />
<PackageReference Include="JKorf.HTX.Net" Version="6.4.0" />
<PackageReference Include="KrakenExchange.Net" Version="5.2.0" />
<PackageReference Include="Kucoin.Net" Version="5.18.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
<PackageReference Include="WhiteBit.Net" Version="1.0.0" />
<PackageReference Include="Binance.Net" Version="10.16.1" />
<PackageReference Include="Bitfinex.Net" Version="7.13.1" />
<PackageReference Include="BitMart.Net" Version="1.12.1" />
<PackageReference Include="Bybit.Net" Version="4.0.2" />
<PackageReference Include="CoinEx.Net" Version="7.13.2" />
<PackageReference Include="CryptoCom.Net" Version="1.5.1" />
<PackageReference Include="GateIo.Net" Version="1.17.1" />
<PackageReference Include="HyperLiquid.Net" Version="1.0.0" />
<PackageReference Include="JK.BingX.Net" Version="1.19.1" />
<PackageReference Include="JK.Bitget.Net" Version="1.19.1" />
<PackageReference Include="JK.Mexc.Net" Version="1.15.1" />
<PackageReference Include="JK.OKX.Net" Version="2.14.1" />
<PackageReference Include="JKorf.Coinbase.Net" Version="1.7.2" />
<PackageReference Include="JKorf.HTX.Net" Version="6.8.1" />
<PackageReference Include="KrakenExchange.Net" Version="5.5.3" />
<PackageReference Include="Kucoin.Net" Version="5.23.4" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="WhiteBit.Net" Version="1.3.2" />
</ItemGroup>
</Project>
+11 -2
View File
@@ -9,7 +9,8 @@
@inject ICoinExRestClient coinexClient
@inject ICryptoComRestClient cryptocomClient
@inject IGateIoRestClient gateioClient
@inject IHTXRestClient huobiClient
@inject IHTXRestClient htxClient
@inject IHyperLiquidRestClient hyperLiquidClient
@inject IKrakenRestClient krakenClient
@inject IKucoinRestClient kucoinClient
@inject IMexcRestClient mexcClient
@@ -37,7 +38,8 @@
var coinexTask = coinexClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
var cryptocomTask = cryptocomClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT");
var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
var htxTask = huobiClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt");
var htxTask = htxClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt");
var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync(); // HyperLiquid does not have BTC spot trading
var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD");
var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
var mexcTask = mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
@@ -79,6 +81,13 @@
if (htxTask.Result.Success)
_prices.Add("HTX", htxTask.Result.Data.ClosePrice ?? 0);
if (hyperLiquidTask.Result.Success)
{
// HyperLiquid API doesn't offer an endpoint to filter for a specific ticker, so we have to filter client side
var tickers = hyperLiquidTask.Result.Data.Tickers;
_prices.Add("HyperLiquid", tickers.Single(x => x.Symbol == "BTC").MidPrice ?? 9);
}
if (krakenTask.Result.Success)
_prices.Add("Kraken", krakenTask.Result.Data.First().Value.LastTrade.Price);
+4 -1
View File
@@ -10,6 +10,7 @@
@inject ICryptoComSocketClient cryptocomSocketClient
@inject IGateIoSocketClient gateioSocketClient
@inject IHTXSocketClient htxSocketClient
@inject IHyperLiquidSocketClient hyperLiquidSocketClient
@inject IKrakenSocketClient krakenSocketClient
@inject IKucoinSocketClient kucoinSocketClient
@inject IMexcSocketClient mexcSocketClient
@@ -42,10 +43,12 @@
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
coinExSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("CoinEx", data.Data.LastPrice)),
coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice)),
coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice ?? 0)),
cryptocomSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CryptoCom", data.Data.LastPrice ?? 0)),
gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("GateIo", data.Data.LastPrice)),
htxSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ethbtc", data => UpdateData("HTX", data.Data.ClosePrice ?? 0)),
// HyperLiquid doesn't support the ETH/BTC pair
//hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("ETH", data => UpdateData("HyperLiquid", data.Data.MidPrice ?? 0)),
krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/XBT", data => UpdateData("Kraken", data.Data.LastPrice)),
kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHBTC", data => UpdateData("Mexc", data.Data.LastPrice)),
@@ -13,6 +13,7 @@
@using CryptoCom.Net.Interfaces
@using GateIo.Net.Interfaces
@using HTX.Net.Interfaces
@using HyperLiquid.Net.Interfaces
@using Kraken.Net.Interfaces
@using Kucoin.Net.Clients
@using Kucoin.Net.Interfaces
@@ -30,6 +31,7 @@
@inject ICryptoComOrderBookFactory cryptocomFactory
@inject IGateIoOrderBookFactory gateioFactory
@inject IHTXOrderBookFactory htxFactory
@inject IHyperLiquidOrderBookFactory hyperLiquidFactory
@inject IKrakenOrderBookFactory krakenFactory
@inject IKucoinOrderBookFactory kucoinFactory
@inject IMexcOrderBookFactory mexcFactory
@@ -79,6 +81,8 @@
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
{ "HTX", htxFactory.CreateSpot("ethbtc") },
// HyperLiquid does not support the ETH/BTC pair
//{ "HyperLiquid", hyperLiquidFactory.Create("ETH/BTC") },
{ "Kraken", krakenFactory.CreateSpot("ETH/BTC") },
{ "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
{ "Mexc", mexcFactory.CreateSpot("ETHBTC") },
+21 -17
View File
@@ -15,6 +15,7 @@
@using CryptoExchange.Net.Trackers.Trades
@using GateIo.Net.Interfaces
@using HTX.Net.Interfaces
@using HyperLiquid.Net.Interfaces
@using Kraken.Net.Interfaces
@using Kucoin.Net.Clients
@using Kucoin.Net.Interfaces
@@ -32,6 +33,7 @@
@inject ICryptoComTrackerFactory cryptocomFactory
@inject IGateIoTrackerFactory gateioFactory
@inject IHTXTrackerFactory htxFactory
@inject IHyperLiquidTrackerFactory hyperLiquidFactory
@inject IKrakenTrackerFactory krakenFactory
@inject IKucoinTrackerFactory kucoinFactory
@inject IMexcTrackerFactory mexcFactory
@@ -59,26 +61,28 @@
protected override async Task OnInitializedAsync()
{
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
var usdtSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
_trackers = new List<ITradeTracker>
{
{ binanceFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bingXFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitfinexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitgetFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitmartFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bybitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ coinbaseFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ coinExFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ cryptocomFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ gateioFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ htxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ krakenFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ kucoinFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ mexcFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ okxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ whitebitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ binanceFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ bingXFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ bitfinexFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ bitgetFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ bitmartFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ bybitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ coinbaseFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ coinExFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ cryptocomFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ gateioFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ htxFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
// HyperLiquid doesn't support spot pair, but does have a futures BTC/USDC pair
{ hyperLiquidFactory.CreateTradeTracker(new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDC"), period: TimeSpan.FromMinutes(5)) },
{ krakenFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ kucoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ mexcFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ okxFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ whitebitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
};
await Task.WhenAll(_trackers.Select(b => b.StartAsync()));
+1
View File
@@ -45,6 +45,7 @@ namespace BlazorClient
services.AddCoinEx();
services.AddCryptoCom();
services.AddGateIo();
services.AddHyperLiquid();
services.AddHTX();
services.AddKraken();
services.AddKucoin();
+1
View File
@@ -19,6 +19,7 @@
@using CryptoCom.Net.Interfaces.Clients;
@using GateIo.Net.Interfaces.Clients;
@using HTX.Net.Interfaces.Clients;
@using HyperLiquid.Net.Interfaces.Clients;
@using Kraken.Net.Interfaces.Clients;
@using Kucoin.Net.Interfaces.Clients;
@using Mexc.Net.Interfaces.Clients;
+26
View File
@@ -24,6 +24,7 @@ The following API's are directly supported. Note that there are 3rd party implem
|Crypto.com|[JKorf/CryptoCom.Net](https://github.com/JKorf/CryptoCom.Net)|[![Nuget version](https://img.shields.io/nuget/v/CryptoCom.net.svg?style=flat-square)](https://www.nuget.org/packages/CryptoCom.Net)|
|Gate.io|[JKorf/GateIo.Net](https://github.com/JKorf/GateIo.Net)|[![Nuget version](https://img.shields.io/nuget/v/GateIo.net.svg?style=flat-square)](https://www.nuget.org/packages/GateIo.Net)|
|HTX|[JKorf/HTX.Net](https://github.com/JKorf/HTX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JKorf.HTX.net.svg?style=flat-square)](https://www.nuget.org/packages/JKorf.HTX.Net)|
|HyperLiquid|[JKorf/HyperLiquid.Net](https://github.com/JKorf/HyperLiquid.Net)|[![Nuget version](https://img.shields.io/nuget/v/HyperLiquid.Net.svg?style=flat-square)](https://www.nuget.org/packages/HyperLiquid.Net)|
|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[![Nuget version](https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=flat-square)](https://www.nuget.org/packages/KrakenExchange.Net)|
|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/Kucoin.net.svg?style=flat-square)](https://www.nuget.org/packages/Kucoin.Net)|
|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.Mexc.Net)|
@@ -50,6 +51,7 @@ When creating an account on new exchanges please consider using a referral link
|CoinEx|[https://www.coinex.com/register?refer_code=hd6gn](https://www.coinex.com/register?refer_code=hd6gn)|
|Crypto.com|[https://crypto.com/exch/26ge92xbkn](https://crypto.com/exch/26ge92xbkn)|
|HTX|[https://www.htx.com/invite/en-us/1f?invite_code=fxp9](https://www.htx.com/invite/en-us/1f?invite_code=fxp9)|
|HyperLiquid|[https://app.hyperliquid.xyz/join/JKORF](https://app.hyperliquid.xyz/join/JKORF)|
|Kucoin|[https://www.kucoin.com/r/rf/QBS4FPED](https://www.kucoin.com/r/rf/QBS4FPED)|
|OKX|[https://okx.com/join/48046699](https://okx.com/join/48046699)|
|WhiteBit|[https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf](https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf)|
@@ -66,6 +68,30 @@ 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 8.7.2 - 27 Jan 2025
* Some small fixes in the System.Text.Json ArrayConverter
* Added support for Flags enum deserialization in System.Text.Json EnumConverter
* Version 8.7.1 - 24 Jan 2025
* Added Authenticated property to IBaseApiClient interface to check if a client was provided API credentials
* Version 8.7.0 - 21 Jan 2025
* Added GetMillisecondTimestampLong helper method to AuthenticationProvider
* Added PriceSignificationFigures to SharedSpotSymbol model
* Version 8.6.1 - 09 Jan 2025
* Fixed websocket connection getting stuck after a ping frame timeout
* Removed websocket Error callback when exception is expected
* Removed unnecessary type restraints on RestApiClient.SendAsync methods
* Version 8.6.0 - 07 Jan 2025
* Added support for passing weight to apply to an individual ratelimit guard
* Added IFeeRestClient to service registration
* Added response headers parameter to RestApiClient.TryParseError method
* Added check for ServerRateLimitError on RestApiClient.TryParseError response
* Added ExchangeType Enum
* Some small improvements
* Version 8.5.0 - 23 Dec 2024
* Added SetOptions method to update client settings
* Added SocketConnection parameter to PeriodicQuery callback
+230 -2
View File
@@ -158,6 +158,7 @@
<tr><td>Crypto.com</td><td><a href="https://github.com/JKorf/CryptoCom.Net">JKorf/CryptoCom.Net</a></td><td><a href="https://www.nuget.org/packages/CryptoCom.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CryptoCom.net.svg?style=flat-square" /></a></td></tr>
<tr><td>Gate.io</td><td><a href="https://github.com/JKorf/GateIo.Net">JKorf/GateIo.Net</a></td><td><a href="https://www.nuget.org/packages/GateIo.Net" target="_blank"><img src="https://img.shields.io/nuget/v/GateIo.net.svg?style=flat-square" /></a></td></tr>
<tr><td>HTX</td><td><a href="https://github.com/JKorf/HTX.Net">JKorf/HTX.Net</a></td><td><a href="https://www.nuget.org/packages/JKorf.HTX.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JKorf.HTX.net.svg?style=flat-square" /></a></td></tr>
<tr><td>HyperLiquid</td><td><a href="https://github.com/JKorf/HyperLiquid.Net">JKorf/HyperLiquid.Net</a></td><td><a href="https://www.nuget.org/packages/HyperLiquid.Net" target="_blank"><img src="https://img.shields.io/nuget/v/HyperLiquid.net.svg?style=flat-square" /></a></td></tr>
<tr><td>Kraken</td><td><a href="https://github.com/JKorf/Kraken.Net">JKorf/Kraken.Net</a></td><td><a href="https://www.nuget.org/packages/KrakenExchange.Net" target="_blank"><img src="https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=flat-square" /></a></td></tr>
<tr><td>Kucoin</td><td><a href="https://github.com/JKorf/Kucoin.Net">JKorf/Kucoin.Net</a></td><td><a href="https://www.nuget.org/packages/Kucoin.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Kucoin.net.svg?style=flat-square" /></a></td></tr>
<tr><td>Mexc</td><td><a href="https://github.com/JKorf/Mexc.Net">JKorf/Mexc.Net</a></td><td><a href="https://www.nuget.org/packages/JK.Mexc.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square" /></a></td></tr>
@@ -197,13 +198,14 @@
<b>Referral</b>
<p>When creating an account on new exchanges please consider using a referral link from below to support development</p>
<table>
<tr><td>Exchange</td><td>Link</td></tr>
<table class="table table-bordered">
<tr><th>Exchange</th><th>Link</th></tr>
<tr><td>Bybit</td><td>https://partner.bybit.com/b/jkorf</td></tr>
<tr><td>Coinbase</td><td>https://advanced.coinbase.com/join/T6H54H8</td></tr>
<tr><td>CoinEx</td><td>https://www.coinex.com/register?refer_code=hd6gn</td></tr>
<tr><td>Crypto.com</td><td>https://crypto.com/exch/26ge92xbkn</td></tr>
<tr><td>HTX</td><td>https://www.htx.com/invite/en-us/1f?invite_code=fxp9</td></tr>
<tr><td>HyperLiquid</td><td>https://app.hyperliquid.xyz/join/JKORF</td></tr>
<tr><td>Kucoin</td><td>https://www.kucoin.com/r/rf/QBS4FPED</td></tr>
<tr><td>OKX</td><td>https://okx.com/join/48046699</td></tr>
<tr><td>WhiteBit</td><td>https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf</td></tr>
@@ -280,6 +282,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="install-htx-tab" data-toggle="tab" href="#install-htx" role="tab" aria-controls="install-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="install-hyperliquid-tab" data-toggle="tab" href="#install-hyperliquid" role="tab" aria-controls="install-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="install-kraken-tab" data-toggle="tab" href="#install-kraken" role="tab" aria-controls="install-kraken" aria-selected="false">Kraken</a>
</li>
@@ -345,6 +350,9 @@
<div class="tab-pane fade" id="install-htx" role="tabpanel" aria-labelledby="install-htx-tab">
<pre><code>dotnet add package JKorf.HTX.Net</code></pre>
</div>
<div class="tab-pane fade" id="install-hyperliquid" role="tabpanel" aria-labelledby="install-hyperliquid-tab">
<pre><code>dotnet add package HyperLiquid.Net</code></pre>
</div>
<div class="tab-pane fade" id="install-kraken" role="tabpanel" aria-labelledby="install-kraken-tab">
<pre><code>dotnet add package KrakenExchange.Net</code></pre>
<img src="assets/images/KrakenInstall.png" />
@@ -420,6 +428,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="di-htx-tab" data-toggle="tab" href="#di-htx" role="tab" aria-controls="di-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="di-hyperliquid-tab" data-toggle="tab" href="#di-hyperliquid" role="tab" aria-controls="di-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="di-kraken-tab" data-toggle="tab" href="#di-kraken" role="tab" aria-controls="di-kraken" aria-selected="false">Kraken</a>
</li>
@@ -478,6 +489,9 @@
</div>
<div class="tab-pane fade" id="di-htx" role="tabpanel" aria-labelledby="di-htx-tab">
<pre><code>builder.Services.AddHTX();</code></pre>
</div>
<div class="tab-pane fade" id="di-hyperliquid" role="tabpanel" aria-labelledby="di-hyperliquid-tab">
<pre><code>builder.Services.AddHyperLiquid();</code></pre>
</div>
<div class="tab-pane fade" id="di-kraken" role="tabpanel" aria-labelledby="di-kraken-tab">
<pre><code>builder.Services.AddKraken();</code></pre>
@@ -542,6 +556,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="interfaces-htx-tab" data-toggle="tab" href="#interfaces-htx" role="tab" aria-controls="interfaces-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="interfaces-hyperliquid-tab" data-toggle="tab" href="#interfaces-hyperliquid" role="tab" aria-controls="interfaces-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="interfaces-kraken-tab" data-toggle="tab" href="#interfaces-kraken" role="tab" aria-controls="interfaces-kraken" aria-selected="false">Kraken</a>
</li>
@@ -975,6 +992,39 @@
</tr>
</table>
</div>
<div class="tab-pane fade" id="interfaces-hyperliquid" role="tabpanel" aria-labelledby="interfaces-hyperliquid-tab">
<table class="table table-bordered">
<tr><th>Interface</th><th>Description</th></tr>
<tr>
<td><code>IHyperLiquidRestClient</code></td>
<td>The client for accessing the HyperLiquid REST API</td>
</tr>
<tr>
<td><code>IHyperLiquidSocketClient</code></td>
<td>The client for accessing the HyperLiquid Websocket API</td>
</tr>
<tr>
<td><code>IHyperLiquidOrderBookFactory</code></td>
<td>A factory for creating SymbolOrderBook instances for the HyperLiquid API</td>
</tr>
<tr>
<td><code>IHyperLiquidTrackerFactory</code></td>
<td>A factory for creating kline and trade Tracker instances for the HyperLiquid API</td>
</tr>
<tr>
<td><code>ICryptoRestClient</code></td>
<td>An aggregating client from which multiple different library REST clients can be accessed</td>
</tr>
<tr>
<td><code>ICryptoSocketClient</code></td>
<td>An aggregating client from which multiple different library Websocket clients can be accessed</td>
</tr>
<tr>
<td><code>ISharedClient</code></td>
<td>Various interfaces deriving from ISharedClient which can be used for common functionality</td>
</tr>
</table>
</div>
<div class="tab-pane fade" id="interfaces-kraken" role="tabpanel" aria-labelledby="interfaces-kraken-tab">
<table class="table table-bordered">
<tr><th>Interface</th><th>Description</th></tr>
@@ -1237,6 +1287,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="rest-htx-tab" data-toggle="tab" href="#rest-htx" role="tab" aria-controls="rest-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="rest-hyperliquid-tab" data-toggle="tab" href="#rest-hyperliquid" role="tab" aria-controls="rest-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="rest-kraken-tab" data-toggle="tab" href="#rest-kraken" role="tab" aria-controls="rest-kraken" aria-selected="false">Kraken</a>
</li>
@@ -1409,6 +1462,18 @@ if (!tickersResult.Success)
// Handle error, tickersResult.Error contains more information
}
else
{
// Handle data, tickersResult.Data will contain the actual data
}</code></pre>
</div>
<div class="tab-pane fade" id="rest-hyperliquid" role="tabpanel" aria-labelledby="rest-hyperliquid-tab">
<pre><code>var client = new HyperLiquidRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetExchangeInfoAndTickersAsync();
if (!tickersResult.Success)
{
// Handle error, tickersResult.Error contains more information
}
else
{
// Handle data, tickersResult.Data will contain the actual data
}</code></pre>
@@ -1595,6 +1660,9 @@ else
<li class="nav-item" role="presentation">
<a class="nav-link" id="socket-htx-tab" data-toggle="tab" href="#socket-htx" role="tab" aria-controls="socket-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="socket-hyperliquid-tab" data-toggle="tab" href="#socket-hyperliquid" role="tab" aria-controls="socket-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="socket-kraken-tab" data-toggle="tab" href="#socket-kraken" role="tab" aria-controls="socket-kraken" aria-selected="false">Kraken</a>
</li>
@@ -1745,6 +1813,17 @@ if (!subscribeResult.Success)
{
// Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
</div>
<div class="tab-pane fade" id="socket-hyperliquid" role="tabpanel" aria-labelledby="socket-hyperliquid-tab">
<pre><code>var client = new HyperLiquidSocketClient();
var subscribeResult = await client.SpotApi.SubscribeToSymbolUpdatesAsync("HYPE/USDC", update => {
// Handle the data update, update.Data will contain the actual data
});
if (!subscribeResult.Success)
{
// Handle error, subscribeResult.Error contains more information on why the subscription failed
}
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
</div>
<div class="tab-pane fade" id="socket-kraken" role="tabpanel" aria-labelledby="socket-kraken-tab">
@@ -1999,6 +2078,9 @@ var binanceTriggered = CheckForTrigger(lastBinanceTicker);</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="shared-htx-tab" data-toggle="tab" href="#shared-htx" role="tab" aria-controls="shared-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="shared-hyperliquid-tab" data-toggle="tab" href="#shared-hyperliquid" role="tab" aria-controls="shared-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="shared-kraken-tab" data-toggle="tab" href="#shared-kraken" role="tab" aria-controls="shared-kraken" aria-selected="false">Kraken</a>
</li>
@@ -2152,6 +2234,19 @@ var usdFuturesSharedRestClient = htxRestClient.UsdtFuturesApi.SharedClient;
// USDT Futures API common functionality socket client
var usdFuturesSharedSocketClient = htxSocketClient.UsdtFuturesApi.SharedClient;</code></pre>
</div>
<div class="tab-pane fade" id="shared-hyperliquid" role="tabpanel" aria-labelledby="shared-hyperliquid-tab">
<pre><code>// Spot API common functionality rest client
var spotSharedRestClients = hyperliquidRestClient.SpotApi.SharedClient;
// Spot API common functionality socket client
var spotSharedSocketClient = hyperliquidSocketClient.SpotApi.SharedClient;
// Perpetual Futures API common functionality rest client
var futuresSharedRestClient = hyperliquidRestClient.FuturesApi.SharedClient;
// Perpetual Futures API common functionality socket client
var futuresSharedSocketClient = hyperliquidSocketClient.FuturesApi.SharedClient;</code></pre>
</div>
<div class="tab-pane fade" id="shared-kraken" role="tabpanel" aria-labelledby="shared-kraken-tab">
<pre><code>// Spot API common functionality rest client
@@ -2485,6 +2580,9 @@ options.ApiCredentials = new ApiCredentials("YOUR PUBLIC KEY", "YOUR PRIVATE KEY
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-htx" role="tab" aria-controls="options-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-hyperliquid-tab" data-toggle="tab" href="#options-hyperliquid" role="tab" aria-controls="options-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-kraken" role="tab" aria-controls="options-kraken" aria-selected="false">Kraken</a>
</li>
@@ -2656,6 +2754,18 @@ builder.Services.AddGateIo(builder.Configuration.GetSection("GateIo"));</code></
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddHTX(builder.Configuration.GetSection("HTX"));</code></pre>
</div>
<div class="tab-pane fade" id="options-hyperliquid" role="tabpanel" aria-labelledby="options-hyperliquid-tab">
<pre><code>builder.Services.AddHyperLiquid(
options => {
options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
});
// OR
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddHyperLiquid(builder.Configuration.GetSection("HyperLiquid"));</code></pre>
</div>
<div class="tab-pane fade" id="options-kraken" role="tabpanel" aria-labelledby="options-kraken-tab">
<pre><code>builder.Services.AddKraken(
@@ -2775,6 +2885,9 @@ builder.Services.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-constr-htx" role="tab" aria-controls="options-constr-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-hyperliquid-tab" data-toggle="tab" href="#options-constr-hyperliquid" role="tab" aria-controls="options-constr-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-constr-kraken" role="tab" aria-controls="options-constr-kraken" aria-selected="false">Kraken</a>
</li>
@@ -2869,6 +2982,12 @@ builder.Services.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
</div>
<div class="tab-pane fade" id="options-constr-htx" role="tabpanel" aria-labelledby="options-htx-tab">
<pre><code>var client = new HTXRestClient(opts =>
{
opts.RequestTimeout = TimeSpan.FromSeconds(30);
});</code></pre>
</div>
<div class="tab-pane fade" id="options-constr-hyperliquid" role="tabpanel" aria-labelledby="options-hyperliquid-tab">
<pre><code>var client = new HyperLiquidRestClient(opts =>
{
opts.RequestTimeout = TimeSpan.FromSeconds(30);
});</code></pre>
@@ -2952,6 +3071,9 @@ builder.Services.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-default-htx" role="tab" aria-controls="options-default-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-hyperliquid-tab" data-toggle="tab" href="#options-default-hyperliquid" role="tab" aria-controls="options-default-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-default-kraken" role="tab" aria-controls="options-default-kraken" aria-selected="false">Kraken</a>
</li>
@@ -3055,6 +3177,13 @@ var client = new GateIoRestClient();</code></pre>
options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new HTXRestClient();</code></pre>
</div>
<div class="tab-pane fade" id="options-default-hyperliquid" role="tabpanel" aria-labelledby="options-hyperliquid-tab">
<pre><code>HyperLiquidRestClient.SetDefaultOptions(options =>
{
options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new HyperLiquidRestClient();</code></pre>
</div>
<div class="tab-pane fade" id="options-default-kraken" role="tabpanel" aria-labelledby="options-kraken-tab">
<pre><code>KrakenRestClient.SetDefaultOptions(options =>
@@ -3317,6 +3446,9 @@ var client = new XTRestClient();</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="book-htx-tab" data-toggle="tab" href="#book-htx" role="tab" aria-controls="book-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="book-hyperliquid-tab" data-toggle="tab" href="#book-hyperliquid" role="tab" aria-controls="book-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="book-kraken-tab" data-toggle="tab" href="#book-kraken" role="tab" aria-controls="book-kraken" aria-selected="false">Kraken</a>
</li>
@@ -3490,6 +3622,19 @@ if (!startResult.Success)
}
// Book has successfully started and synchronized
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
</code></pre>
</div>
<div class="tab-pane fade" id="book-hyperliquid" role="tabpanel" aria-labelledby="book-hyperliquid-tab">
<pre><code>var book = new HyperLiquidSymbolOrderBook("HYPE/USDC");
var startResult = await book.StartAsync();
if (!startResult.Success)
{
// Handle error, error info available in startResult.Error
}
// Book has successfully started and synchronized
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await book.StopAsync();
</code></pre>
@@ -3726,6 +3871,9 @@ foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-htx-tab" data-toggle="tab" href="#tracker-htx" role="tab" aria-controls="tracker-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-hyperliquid-tab" data-toggle="tab" href="#tracker-hyperliquid" role="tab" aria-controls="tracker-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-kraken-tab" data-toggle="tab" href="#tracker-kraken" role="tab" aria-controls="tracker-kraken" aria-selected="false">Kraken</a>
</li>
@@ -3983,6 +4131,26 @@ if (!startResult.Success)
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-hyperliquid" role="tabpanel" aria-labelledby="tracker-hyperliquid-tab">
<pre><code>// Either create a new factory or inject the IHyperLiquidTrackerFactory interface
var factory = new HyperLiquidTrackerFactory();
var symbol = new SharedSymbol(TradingMode.Spot, "HYPE", "USDC");
// Create a tracker for HYPE/USDC keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
// Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
</code></pre>
@@ -4441,6 +4609,9 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options
<li class="nav-item" role="presentation">
<a class="nav-link" id="limit-htx-tab" data-toggle="tab" href="#limit-htx" role="tab" aria-controls="limit-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="limit-hyperliquid-tab" data-toggle="tab" href="#limit-hyperliquid" role="tab" aria-controls="limit-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="limit-kraken-tab" data-toggle="tab" href="#limit-kraken" role="tab" aria-controls="limit-kraken" aria-selected="false">Kraken</a>
</li>
@@ -4583,6 +4754,20 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options
<p>To be notified of when a rate limit is hit the static <code>HTXExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
<pre><code>HTXExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
</code></pre>
</div>
<div class="tab-pane fade" id="limit-hyperliquid" role="tabpanel" aria-labelledby="limit-hyperliquid-tab">
<pre><code>services.AddHyperLiquid(x =>
x.RatelimiterEnabled = true;
x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
}, x =>
{
x.RatelimiterEnabled = true;
x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
});</code></pre>
<p>To be notified of when a rate limit is hit the static <code>HyperLiquidExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
<pre><code>HyperLiquidExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
</code></pre>
</div>
<div class="tab-pane fade" id="limit-kraken" role="tabpanel" aria-labelledby="limit-kraken-tab">
@@ -4760,6 +4945,9 @@ var responseSource = result.DataSource;</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-symbols-htx-tab" data-toggle="tab" href="#example-symbols-htx" role="tab" aria-controls="example-symbols-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-symbols-hyperliquid-tab" data-toggle="tab" href="#example-symbols-hyperliquid" role="tab" aria-controls="example-symbols-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-symbols-kraken-tab" data-toggle="tab" href="#example-symbols-kraken" role="tab" aria-controls="example-symbols-kraken" aria-selected="false">Kraken</a>
</li>
@@ -4823,6 +5011,9 @@ await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance"
</div>
<div class="tab-pane fade" id="example-symbols-htx" role="tabpanel" aria-labelledby="example-symbols-htx-tab">
<pre><code>await htxClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
</div>
<div class="tab-pane fade" id="example-symbols-hyperliquid" role="tabpanel" aria-labelledby="example-symbols-hyperliquid-tab">
<pre><code>await hyperLiquidClient.SpotApi.ExchangeData.GetExchangeInfoAsync();</code></pre>
</div>
<div class="tab-pane fade" id="example-symbols-kraken" role="tabpanel" aria-labelledby="example-symbols-kraken-tab">
<pre><code>await krakenClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
@@ -4896,6 +5087,9 @@ await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance"
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-ticker-htx-tab" data-toggle="tab" href="#example-ticker-htx" role="tab" aria-controls="example-ticker-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-ticker-hyperliquid-tab" data-toggle="tab" href="#example-ticker-hyperliquid" role="tab" aria-controls="example-ticker-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-ticker-kraken-tab" data-toggle="tab" href="#example-ticker-kraken" role="tab" aria-controls="example-ticker-kraken" aria-selected="false">Kraken</a>
</li>
@@ -4961,6 +5155,11 @@ await coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");</
</div>
<div class="tab-pane fade" id="example-ticker-htx" role="tabpanel" aria-labelledby="example-ticker-htx-tab">
<pre><code>await htxClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");</code></pre>
</div>
<div class="tab-pane fade" id="example-ticker-hyperliquid" role="tabpanel" aria-labelledby="example-ticker-hyperliquid-tab">
<pre><code>// HyperLiquid API doesn't offer a symbol filter, so we have to filter client side
var tickersResult = await hyperLiquidClient.SpotApi.ExchangeData.GetExchangeInfoAndTickersAsync();
var ticker = tickersResult.Data.Tickers.Single(x => x.Symbol == "HYPE/USDC");</code></pre>
</div>
<div class="tab-pane fade" id="example-ticker-kraken" role="tabpanel" aria-labelledby="example-ticker-kraken-tab">
<pre><code>await krakenClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");</code></pre>
@@ -5036,6 +5235,9 @@ var ticker = tickersResult.Data.Single(x => x.Symbol == "BTC_USDT");</code></pre
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-balances-htx-tab" data-toggle="tab" href="#example-balances-htx" role="tab" aria-controls="example-balances-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-balances-hyperliquid-tab" data-toggle="tab" href="#example-balances-hyperliquid" role="tab" aria-controls="example-balances-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-balances-kraken-tab" data-toggle="tab" href="#example-balances-kraken" role="tab" aria-controls="example-balances-kraken" aria-selected="false">Kraken</a>
</li>
@@ -5103,6 +5305,9 @@ var accounts = await htxClient.SpotApi.Account.GetAccountsAsync();
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);
var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
</div>
<div class="tab-pane fade" id="example-balances-hyperliquid" role="tabpanel" aria-labelledby="example-balances-hyperliquid-tab">
<pre><code>await hyperLiquidClient.SpotApi.Account.GetBalancesAsync();</code></pre>
</div>
<div class="tab-pane fade" id="example-balances-kraken" role="tabpanel" aria-labelledby="example-balances-kraken-tab">
<pre><code>await krakenClient.SpotApi.Account.GetBalancesAsync();</code></pre>
@@ -5176,6 +5381,9 @@ var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-place-htx-tab" data-toggle="tab" href="#example-place-htx" role="tab" aria-controls="example-place-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-place-hyperliquid-tab" data-toggle="tab" href="#example-place-hyperliquid" role="tab" aria-controls="example-place-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-place-kraken-tab" data-toggle="tab" href="#example-place-kraken" role="tab" aria-controls="example-place-kraken" aria-selected="false">Kraken</a>
</li>
@@ -5241,6 +5449,10 @@ var accounts = await htxClient.SpotApi.Account.GetAccountsAsync();
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);
var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000);</code></pre>
</div>
<div class="tab-pane fade" id="example-place-hyperliquid" role="tabpanel" aria-labelledby="example-place-hyperliquid-tab">
<pre><code>// BTC not support on HyperLiquid Spot trading, example uses HYPE/USDC Pair
await hyperLiquidClient.SpotApi.Trading.PlaceOrderAsync("HYPE/USDC",OrderSide.Buy, OrderType.Limit, 1m, 20);</code></pre>
</div>
<div class="tab-pane fade" id="example-place-kraken" role="tabpanel" aria-labelledby="example-place-kraken-tab">
<pre><code>await krakenClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT",OrderSide.Buy, OrderType.Limit, 0.1m, 50000);</code></pre>
@@ -5314,6 +5526,9 @@ var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSD
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-ticker-htx-tab" data-toggle="tab" href="#example-stream-ticker-htx" role="tab" aria-controls="example-stream-ticker-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-ticker-hyperliquid-tab" data-toggle="tab" href="#example-stream-ticker-hyperliquid" role="tab" aria-controls="example-stream-ticker-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-ticker-kraken-tab" data-toggle="tab" href="#example-stream-ticker-kraken" role="tab" aria-controls="example-stream-ticker-kraken" aria-selected="false">Kraken</a>
</li>
@@ -5400,6 +5615,11 @@ await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequ
<div class="tab-pane fade" id="example-stream-ticker-gateio" role="tabpanel" aria-labelledby="example-stream-ticker-gateio-tab">
<pre><code>await gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
// Handle update
});</code></pre>
</div>
<div class="tab-pane fade" id="example-stream-ticker-hyperliquid" role="tabpanel" aria-labelledby="example-stream-ticker-hyperliquid-tab">
<pre><code>await hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("HYPE/USDC", data => {
// Handle update
});</code></pre>
</div>
<div class="tab-pane fade" id="example-stream-ticker-htx" role="tabpanel" aria-labelledby="example-stream-ticker-htx-tab">
@@ -5494,6 +5714,9 @@ await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequ
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-order-htx-tab" data-toggle="tab" href="#example-stream-order-htx" role="tab" aria-controls="example-stream-order-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-order-hyperliquid-tab" data-toggle="tab" href="#example-stream-order-hyperliquid" role="tab" aria-controls="example-stream-order-hyperliquid" aria-selected="false">HyperLiquid</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-order-kraken-tab" data-toggle="tab" href="#example-stream-order-kraken" role="tab" aria-controls="example-stream-order-kraken" aria-selected="false">Kraken</a>
</li>
@@ -5621,6 +5844,11 @@ _ = Task.Run(async () => {
<div class="tab-pane fade" id="example-stream-order-htx" role="tabpanel" aria-labelledby="example-stream-order-htx-tab">
<pre><code>await htxSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(onOrderMatched: data => {
// Handle update
});</code></pre>
</div>
<div class="tab-pane fade" id="example-stream-order-hyperliquid" role="tabpanel" aria-labelledby="example-stream-order-hyperliquid-tab">
<pre><code>await hyperLiquidSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(null, data => {
// Handle update
});</code></pre>
</div>
<div class="tab-pane fade" id="example-stream-order-kraken" role="tabpanel" aria-labelledby="example-stream-order-kraken-tab">