mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-13 17:33:02 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 175e23f110 | |||
| 9b7019ded2 | |||
| 7904aa9ba7 | |||
| 3fe6db589f | |||
| 625dccbbe4 | |||
| e650771d16 | |||
| 3dad28b19d | |||
| 2b9fda985e | |||
| ff8759409b | |||
| 0d9627c13f | |||
| 0179fd7e2a | |||
| b8d0b0cf95 |
@@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 9.0.x
|
||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -403,10 +403,14 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected static string BytesToHexString(byte[] buff)
|
protected static string BytesToHexString(byte[] buff)
|
||||||
{
|
{
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
|
return Convert.ToHexString(buff);
|
||||||
|
#else
|
||||||
var result = string.Empty;
|
var result = string.Empty;
|
||||||
foreach (var t in buff)
|
foreach (var t in buff)
|
||||||
result += t.ToString("X2");
|
result += t.ToString("X2");
|
||||||
return result;
|
return result;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <param name="additionalHeaders">Additional headers for this request</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="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>
|
/// <returns></returns>
|
||||||
protected virtual Task<WebCallResult<T>> SendAsync<T>(
|
protected virtual Task<WebCallResult<T>> SendAsync<T>(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
@@ -161,7 +162,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
ParameterCollection? parameters,
|
ParameterCollection? parameters,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null) where T : class
|
int? weight = null,
|
||||||
|
int? weightSingleLimiter = null)
|
||||||
{
|
{
|
||||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||||
return SendAsync<T>(
|
return SendAsync<T>(
|
||||||
@@ -171,7 +173,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
|
parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
|
||||||
cancellationToken,
|
cancellationToken,
|
||||||
additionalHeaders,
|
additionalHeaders,
|
||||||
weight);
|
weight,
|
||||||
|
weightSingleLimiter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -185,6 +188,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <param name="additionalHeaders">Additional headers for this request</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="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>
|
/// <returns></returns>
|
||||||
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
@@ -193,7 +197,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
ParameterCollection? bodyParameters,
|
ParameterCollection? bodyParameters,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null) where T : class
|
int? weight = null,
|
||||||
|
int? weightSingleLimiter = null)
|
||||||
{
|
{
|
||||||
string? cacheKey = null;
|
string? cacheKey = null;
|
||||||
if (ShouldCache(definition))
|
if (ShouldCache(definition))
|
||||||
@@ -217,7 +222,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
currentTry++;
|
currentTry++;
|
||||||
var requestId = ExchangeHelpers.NextId();
|
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)
|
if (!prepareResult)
|
||||||
return new WebCallResult<T>(prepareResult.Error!);
|
return new WebCallResult<T>(prepareResult.Error!);
|
||||||
|
|
||||||
@@ -258,6 +263,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
/// <param name="weight">Override the request weight 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>
|
/// <returns></returns>
|
||||||
/// <exception cref="Exception"></exception>
|
/// <exception cref="Exception"></exception>
|
||||||
protected virtual async Task<CallResult> PrepareAsync(
|
protected virtual async Task<CallResult> PrepareAsync(
|
||||||
@@ -266,10 +272,9 @@ namespace CryptoExchange.Net.Clients
|
|||||||
RequestDefinition definition,
|
RequestDefinition definition,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null)
|
int? weight = null,
|
||||||
|
int? weightSingleLimiter = null)
|
||||||
{
|
{
|
||||||
var requestWeight = weight ?? definition.Weight;
|
|
||||||
|
|
||||||
// Time sync
|
// Time sync
|
||||||
if (definition.Authenticated)
|
if (definition.Authenticated)
|
||||||
{
|
{
|
||||||
@@ -295,6 +300,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rate limiting
|
// Rate limiting
|
||||||
|
var requestWeight = weight ?? definition.Weight;
|
||||||
if (requestWeight != 0)
|
if (requestWeight != 0)
|
||||||
{
|
{
|
||||||
if (definition.RateLimitGate == null)
|
if (definition.RateLimitGate == null)
|
||||||
@@ -316,7 +322,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
|
|
||||||
if (ClientOptions.RateLimiterEnabled)
|
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)
|
if (!limitResult)
|
||||||
return new CallResult(limitResult.Error!);
|
return new CallResult(limitResult.Error!);
|
||||||
}
|
}
|
||||||
@@ -617,7 +624,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
paramString = $" with request body '{request.Content}'";
|
paramString = $" with request body '{request.Content}'";
|
||||||
|
|
||||||
var headers = request.GetHeaders();
|
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)}]"));
|
paramString += " with headers " + string.Join(", ", headers.Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"));
|
||||||
|
|
||||||
TotalRequestsMade++;
|
TotalRequestsMade++;
|
||||||
@@ -693,10 +700,21 @@ namespace CryptoExchange.Net.Clients
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Json response received
|
// Json response received
|
||||||
var parsedError = TryParseError(accessor);
|
var parsedError = TryParseError(response.ResponseHeaders, accessor);
|
||||||
if (parsedError != null)
|
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
|
// 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);
|
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>();
|
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);
|
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>
|
/// <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.
|
/// 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
|
/// If the response is an error this method should return the parsed error, else it should return null
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="accessor">Data accessor</param>
|
/// <param name="accessor">Data accessor</param>
|
||||||
|
/// <param name="responseHeaders">The response headers</param>
|
||||||
/// <returns>Null if not an error, Error otherwise</returns>
|
/// <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>
|
/// <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.
|
/// 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
|
// Only retry once
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ((int?)callResult.ResponseStatusCode == 429
|
if (callResult.Error is ServerRateLimitError
|
||||||
&& ClientOptions.RateLimiterEnabled
|
&& ClientOptions.RateLimiterEnabled
|
||||||
&& ClientOptions.RateLimitingBehaviour != RateLimitingBehaviour.Fail
|
&& ClientOptions.RateLimitingBehaviour != RateLimitingBehaviour.Fail
|
||||||
&& gate != null)
|
&& gate != null)
|
||||||
@@ -889,8 +908,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
// Write the parameters as json in the body
|
// Write the parameters as json in the body
|
||||||
string stringData;
|
string stringData;
|
||||||
if (parameters.Count == 1 && parameters.ContainsKey(Constants.BodyPlaceHolderKey))
|
if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
|
||||||
stringData = CreateSerializer().Serialize(parameters[Constants.BodyPlaceHolderKey]);
|
stringData = CreateSerializer().Serialize(value);
|
||||||
else
|
else
|
||||||
stringData = CreateSerializer().Serialize(parameters);
|
stringData = CreateSerializer().Serialize(parameters);
|
||||||
request.SetContent(stringData, contentType);
|
request.SetContent(stringData, contentType);
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||||
<PackageVersion>8.5.0</PackageVersion>
|
<PackageVersion>8.6.1</PackageVersion>
|
||||||
<AssemblyVersion>8.5.0</AssemblyVersion>
|
<AssemblyVersion>8.6.1</AssemblyVersion>
|
||||||
<FileVersion>8.5.0</FileVersion>
|
<FileVersion>8.6.1</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange</PackageTags>
|
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
|||||||
@@ -435,6 +435,8 @@ namespace CryptoExchange.Net
|
|||||||
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
|
services.AddTransient(x => (IWithdrawalRestClient)client(x)!);
|
||||||
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
|
if (typeof(IWithdrawRestClient).IsAssignableFrom(typeof(T)))
|
||||||
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
|
services.AddTransient(x => (IWithdrawRestClient)client(x)!);
|
||||||
|
if (typeof(IFeeRestClient).IsAssignableFrom(typeof(T)))
|
||||||
|
services.AddTransient(x => (IFeeRestClient)client(x)!);
|
||||||
|
|
||||||
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
|
if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T)))
|
||||||
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
|
services.AddTransient(x => (ISpotOrderRestClient)client(x)!);
|
||||||
|
|||||||
+14
-2
@@ -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?> _startingTaskForNoDataReceivedCheck;
|
||||||
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimoutReconnect;
|
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, string, string, Exception?> _socketProcessingStateChanged;
|
||||||
|
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
|
||||||
|
|
||||||
static CryptoExchangeWebSocketClientLoggingExtension()
|
static CryptoExchangeWebSocketClientLoggingExtension()
|
||||||
{
|
{
|
||||||
@@ -169,7 +170,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
|
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
|
||||||
|
|
||||||
_noDataReceiveTimoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
|
_noDataReceiveTimoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
|
||||||
LogLevel.Debug,
|
LogLevel.Warning,
|
||||||
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
|
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
|
||||||
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
|
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
|
||||||
|
|
||||||
@@ -180,9 +181,14 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
|
|
||||||
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
|
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(1028, "SocketProcessingStateChanged"),
|
new EventId(1029, "SocketProcessingStateChanged"),
|
||||||
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
|
"[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(
|
public static void SocketConnecting(
|
||||||
@@ -358,5 +364,11 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
{
|
{
|
||||||
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
|
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SocketPingTimeout(
|
||||||
|
this ILogger logger, int socketId)
|
||||||
|
{
|
||||||
|
_socketPingTimeout(logger, socketId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,4 +235,18 @@
|
|||||||
Cache
|
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,
|
HttpMethodParameterPosition? parameterPosition = null,
|
||||||
ArrayParametersSerialization? arraySerialization = null,
|
ArrayParametersSerialization? arraySerialization = null,
|
||||||
bool? preventCaching = 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)
|
def = new RequestDefinition(path, method)
|
||||||
{
|
{
|
||||||
@@ -73,7 +102,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
ParameterPosition = parameterPosition,
|
ParameterPosition = parameterPosition,
|
||||||
PreventCaching = preventCaching ?? false
|
PreventCaching = preventCaching ?? false
|
||||||
};
|
};
|
||||||
_definitions.TryAdd(method + path, def);
|
_definitions.TryAdd(identifier, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
return def;
|
return def;
|
||||||
|
|||||||
@@ -82,12 +82,12 @@ namespace CryptoExchange.Net.Objects
|
|||||||
TimeSyncState.LastSyncTime = DateTime.UtcNow;
|
TimeSyncState.LastSyncTime = DateTime.UtcNow;
|
||||||
if (offset.TotalMilliseconds > 0 && offset.TotalMilliseconds < 500)
|
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;
|
TimeSyncState.TimeOffset = TimeSpan.Zero;
|
||||||
}
|
}
|
||||||
else
|
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;
|
TimeSyncState.TimeOffset = offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,8 +68,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
|
|||||||
/// <param name="baseAddress">The host address</param>
|
/// <param name="baseAddress">The host address</param>
|
||||||
/// <param name="apiKey">The API key</param>
|
/// <param name="apiKey">The API key</param>
|
||||||
/// <param name="behaviour">Behaviour when rate limit is hit</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>
|
/// <param name="ct">Cancelation token</param>
|
||||||
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
|
/// <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,
|
RequestDefinition definition,
|
||||||
string host,
|
string host,
|
||||||
string? apiKey,
|
string? apiKey,
|
||||||
|
int requestWeight,
|
||||||
RateLimitingBehaviour rateLimitingBehaviour,
|
RateLimitingBehaviour rateLimitingBehaviour,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
@@ -77,7 +78,7 @@ namespace CryptoExchange.Net.RateLimiting
|
|||||||
_waitingCount++;
|
_waitingCount++;
|
||||||
try
|
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)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace CryptoExchange.Net.Requests
|
|||||||
_httpClient = CreateClient(proxy, requestTimeout);
|
_httpClient = CreateClient(proxy, requestTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
|
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
|
||||||
{
|
{
|
||||||
var handler = new HttpClientHandler();
|
var handler = new HttpClientHandler();
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -587,15 +587,27 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
lock (_receivedMessagesLock)
|
lock (_receivedMessagesLock)
|
||||||
_receivedMessages.Add(new ReceiveItem(DateTime.UtcNow, receiveResult.Count));
|
_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
|
// canceled
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception wse)
|
catch (Exception wse)
|
||||||
{
|
{
|
||||||
// Connection closed unexpectedly
|
if (!_ctsSource.Token.IsCancellationRequested)
|
||||||
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
|
// Connection closed unexpectedly
|
||||||
|
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
|
|
||||||
if (_closeTask?.IsCompleted != false)
|
if (_closeTask?.IsCompleted != false)
|
||||||
_closeTask = CloseInternalAsync();
|
_closeTask = CloseInternalAsync();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -66,6 +66,19 @@ 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).
|
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* 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
|
* Version 8.5.0 - 23 Dec 2024
|
||||||
* Added SetOptions method to update client settings
|
* Added SetOptions method to update client settings
|
||||||
* Added SocketConnection parameter to PeriodicQuery callback
|
* Added SocketConnection parameter to PeriodicQuery callback
|
||||||
|
|||||||
Reference in New Issue
Block a user