mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-08-31 12:42:00 +00:00
Compare commits
7 Commits
3e365f83c9
...
6156fb8154
Author | SHA1 | Date | |
---|---|---|---|
|
6156fb8154 | ||
|
f2753aed1e | ||
|
e33d826381 | ||
|
364aa4d324 | ||
|
455b332757 | ||
|
876b895645 | ||
|
daf7ed9fe6 |
@ -6,9 +6,9 @@
|
||||
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>Protobuf support for CryptoExchange.Net</Description>
|
||||
<PackageVersion>9.4.0</PackageVersion>
|
||||
<AssemblyVersion>9.4.0</AssemblyVersion>
|
||||
<FileVersion>9.4.0</FileVersion>
|
||||
<PackageVersion>9.5.0</PackageVersion>
|
||||
<AssemblyVersion>9.5.0</AssemblyVersion>
|
||||
<FileVersion>9.5.0</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
@ -41,9 +41,7 @@
|
||||
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CryptoExchange.Net" Version="9.5.0" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CryptoExchange.Net\CryptoExchange.Net.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -5,6 +5,9 @@
|
||||
Protobuf support for CryptoExchange.Net.
|
||||
|
||||
## Release notes
|
||||
* Version 9.5.0 - 19 Aug 2025
|
||||
* Updated CryptoExchange.Net version to 9.5.0
|
||||
|
||||
* Version 9.4.0 - 04 Aug 2025
|
||||
* Updated CryptoExchange.Net to version 9.4.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
||||
* Updated protobuf-net package version to 3.2.56
|
||||
|
@ -182,7 +182,7 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[TestCase("/sapi/test1", true)]
|
||||
[TestCase("/sapi/test2", true)]
|
||||
[TestCase("/api/test1", false)]
|
||||
[TestCase("sapi/test1", false)]
|
||||
[TestCase("sapi/test1", true)]
|
||||
[TestCase("/sapi/", true)]
|
||||
public async Task PartialEndpointRateLimiterEndpoints(string endpoint, bool expectLimiting)
|
||||
{
|
||||
|
@ -78,7 +78,7 @@ namespace CryptoExchange.Net.UnitTests
|
||||
{
|
||||
}
|
||||
|
||||
public override void AuthenticateRequest(RestApiClient apiClient, Uri uri, HttpMethod method, ref IDictionary<string, object> uriParams, ref IDictionary<string, object> bodyParams, ref Dictionary<string, string> headers, bool auth, ArrayParametersSerialization arraySerialization, HttpMethodParameterPosition parameterPosition, RequestBodyFormat bodyFormat)
|
||||
public override void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -51,30 +51,11 @@ namespace CryptoExchange.Net.Authentication
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate a request. Output parameters should include the providedParameters input
|
||||
/// Authenticate a request
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The Api client sending the request</param>
|
||||
/// <param name="uri">The uri for the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="auth">If the requests should be authenticated</param>
|
||||
/// <param name="arraySerialization">Array serialization type</param>
|
||||
/// <param name="requestBodyFormat">The formatting of the request body</param>
|
||||
/// <param name="uriParameters">Parameters that need to be in the Uri of the request. Should include the provided parameters if they should go in the uri</param>
|
||||
/// <param name="bodyParameters">Parameters that need to be in the body of the request. Should include the provided parameters if they should go in the body</param>
|
||||
/// <param name="headers">The headers that should be send with the request</param>
|
||||
/// <param name="parameterPosition">The position where the providedParameters should go</param>
|
||||
public abstract void AuthenticateRequest(
|
||||
RestApiClient apiClient,
|
||||
Uri uri,
|
||||
HttpMethod method,
|
||||
ref IDictionary<string, object>? uriParameters,
|
||||
ref IDictionary<string, object>? bodyParameters,
|
||||
ref Dictionary<string, string>? headers,
|
||||
bool auth,
|
||||
ArrayParametersSerialization arraySerialization,
|
||||
HttpMethodParameterPosition parameterPosition,
|
||||
RequestBodyFormat requestBodyFormat
|
||||
);
|
||||
/// <param name="requestConfig">The request configuration</param>
|
||||
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig);
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 sign the data and return the bytes
|
||||
|
@ -55,7 +55,7 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <summary>
|
||||
/// Request headers to be sent with each request
|
||||
/// </summary>
|
||||
protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
|
||||
protected Dictionary<string, string> StandardRequestHeaders { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Whether parameters need to be ordered
|
||||
@ -364,74 +364,58 @@ namespace CryptoExchange.Net.Clients
|
||||
ParameterCollection? bodyParameters,
|
||||
Dictionary<string, string>? additionalHeaders)
|
||||
{
|
||||
var uriParams = uriParameters == null ? null : CreateParameterDictionary(uriParameters);
|
||||
var bodyParams = bodyParameters == null ? null : CreateParameterDictionary(bodyParameters);
|
||||
var requestConfiguration = new RestRequestConfiguration(
|
||||
definition,
|
||||
baseAddress,
|
||||
uriParameters == null ? new Dictionary<string, object>() : CreateParameterDictionary(uriParameters),
|
||||
bodyParameters == null ? new Dictionary<string, object>() : CreateParameterDictionary(bodyParameters),
|
||||
new Dictionary<string, string>(additionalHeaders ?? []),
|
||||
definition.ArraySerialization ?? ArraySerialization,
|
||||
definition.ParameterPosition ?? ParameterPositions[definition.Method],
|
||||
definition.RequestBodyFormat ?? RequestBodyFormat);
|
||||
|
||||
var uri = new Uri(baseAddress.AppendPath(definition.Path));
|
||||
var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
|
||||
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
|
||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||
|
||||
Dictionary<string, string>? headers = null;
|
||||
if (AuthenticationProvider != null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
AuthenticationProvider.AuthenticateRequest(
|
||||
this,
|
||||
uri,
|
||||
definition.Method,
|
||||
ref uriParams,
|
||||
ref bodyParams,
|
||||
ref headers,
|
||||
definition.Authenticated,
|
||||
arraySerialization,
|
||||
parameterPosition,
|
||||
bodyFormat
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Failed to authenticate request, make sure your API credentials are correct", ex);
|
||||
}
|
||||
AuthenticationProvider?.ProcessRequest(this, requestConfiguration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception("Failed to authenticate request, make sure your API credentials are correct", ex);
|
||||
}
|
||||
|
||||
// Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
|
||||
if (uriParams != null)
|
||||
uri = uri.SetParameters(uriParams, arraySerialization);
|
||||
var queryString = requestConfiguration.GetQueryString(true);
|
||||
if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?"))
|
||||
queryString = $"?{queryString}";
|
||||
|
||||
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString);
|
||||
var request = RequestFactory.Create(definition.Method, uri, requestId);
|
||||
request.Accept = Constants.JsonContentHeader;
|
||||
|
||||
if (headers != null)
|
||||
foreach (var header in requestConfiguration.Headers)
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
|
||||
foreach (var header in StandardRequestHeaders)
|
||||
{
|
||||
foreach (var header in headers)
|
||||
// Only add it if it isn't overwritten
|
||||
if (!requestConfiguration.Headers.ContainsKey(header.Key))
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
if (additionalHeaders != null)
|
||||
if (requestConfiguration.ParameterPosition == HttpMethodParameterPosition.InBody)
|
||||
{
|
||||
foreach (var header in additionalHeaders)
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
}
|
||||
|
||||
if (StandardRequestHeaders != null)
|
||||
{
|
||||
foreach (var header in StandardRequestHeaders)
|
||||
var contentType = requestConfiguration.BodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
var bodyContent = requestConfiguration.GetBodyContent();
|
||||
if (bodyContent != null)
|
||||
{
|
||||
// Only add it if it isn't overwritten
|
||||
if (additionalHeaders?.ContainsKey(header.Key) != true)
|
||||
request.AddHeader(header.Key, header.Value);
|
||||
request.SetContent(bodyContent, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameterPosition == HttpMethodParameterPosition.InBody)
|
||||
{
|
||||
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
if (bodyParams != null && bodyParams.Count != 0)
|
||||
WriteParamBody(request, bodyParams, contentType);
|
||||
else
|
||||
request.SetContent(RequestBodyEmptyContent, contentType);
|
||||
{
|
||||
if (requestConfiguration.BodyParameters != null && requestConfiguration.BodyParameters.Count != 0)
|
||||
WriteParamBody(request, requestConfiguration.BodyParameters, contentType);
|
||||
else
|
||||
request.SetContent(RequestBodyEmptyContent, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
|
@ -6,9 +6,9 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||
<PackageVersion>9.4.0</PackageVersion>
|
||||
<AssemblyVersion>9.4.0</AssemblyVersion>
|
||||
<FileVersion>9.4.0</FileVersion>
|
||||
<PackageVersion>9.5.0</PackageVersion>
|
||||
<AssemblyVersion>9.5.0</AssemblyVersion>
|
||||
<FileVersion>9.5.0</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
@ -76,6 +76,9 @@ namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
Path = path;
|
||||
Method = method;
|
||||
|
||||
if (!Path.StartsWith("/"))
|
||||
Path = $"/{Path}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
124
CryptoExchange.Net/Objects/RestRequestConfiguration.cs
Normal file
124
CryptoExchange.Net/Objects/RestRequestConfiguration.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
/// <summary>
|
||||
/// Rest request configuration
|
||||
/// </summary>
|
||||
public class RestRequestConfiguration
|
||||
{
|
||||
private string? _bodyContent;
|
||||
private string? _queryString;
|
||||
|
||||
/// <summary>
|
||||
/// Http method
|
||||
/// </summary>
|
||||
public HttpMethod Method { get; set; }
|
||||
/// <summary>
|
||||
/// Whether the request needs authentication
|
||||
/// </summary>
|
||||
public bool Authenticated { get; set; }
|
||||
/// <summary>
|
||||
/// Base address for the request
|
||||
/// </summary>
|
||||
public string BaseAddress { get; set; }
|
||||
/// <summary>
|
||||
/// The request path
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
/// <summary>
|
||||
/// Query parameters
|
||||
/// </summary>
|
||||
public IDictionary<string, object> QueryParameters { get; set; }
|
||||
/// <summary>
|
||||
/// Body parameters
|
||||
/// </summary>
|
||||
public IDictionary<string, object> BodyParameters { get; set; }
|
||||
/// <summary>
|
||||
/// Request headers
|
||||
/// </summary>
|
||||
public IDictionary<string, string> Headers { get; set; }
|
||||
/// <summary>
|
||||
/// Array serialization type
|
||||
/// </summary>
|
||||
public ArrayParametersSerialization ArraySerialization { get; set; }
|
||||
/// <summary>
|
||||
/// Position of the parameters
|
||||
/// </summary>
|
||||
public HttpMethodParameterPosition ParameterPosition { get; set; }
|
||||
/// <summary>
|
||||
/// Body format
|
||||
/// </summary>
|
||||
public RequestBodyFormat BodyFormat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public RestRequestConfiguration(
|
||||
RequestDefinition requestDefinition,
|
||||
string baseAddress,
|
||||
IDictionary<string, object> queryParams,
|
||||
IDictionary<string, object> bodyParams,
|
||||
IDictionary<string, string> headers,
|
||||
ArrayParametersSerialization arraySerialization,
|
||||
HttpMethodParameterPosition parametersPosition,
|
||||
RequestBodyFormat bodyFormat)
|
||||
{
|
||||
Method = requestDefinition.Method;
|
||||
Authenticated = requestDefinition.Authenticated;
|
||||
Path = requestDefinition.Path;
|
||||
BaseAddress = baseAddress;
|
||||
QueryParameters = queryParams;
|
||||
BodyParameters = bodyParams;
|
||||
Headers = headers;
|
||||
ArraySerialization = arraySerialization;
|
||||
ParameterPosition = parametersPosition;
|
||||
BodyFormat = bodyFormat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the parameter collection based on the ParameterPosition
|
||||
/// </summary>
|
||||
public IDictionary<string, object> GetPositionParameters()
|
||||
{
|
||||
if (ParameterPosition == HttpMethodParameterPosition.InBody)
|
||||
return BodyParameters;
|
||||
|
||||
return QueryParameters;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the query string. If it's not previously set it will return a newly formatted query string. If previously set return that.
|
||||
/// </summary>
|
||||
/// <param name="urlEncode">Whether to URL encode the parameter string if creating new</param>
|
||||
public string GetQueryString(bool urlEncode = true)
|
||||
{
|
||||
return _queryString ?? QueryParameters.CreateParamString(urlEncode, ArraySerialization);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the query string of the request. Will be returned by subsequent <see cref="GetQueryString" /> calls
|
||||
/// </summary>
|
||||
public void SetQueryString(string value)
|
||||
{
|
||||
_queryString = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the body content if it's previously set
|
||||
/// </summary>
|
||||
public string? GetBodyContent()
|
||||
{
|
||||
return _bodyContent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the body content for the request
|
||||
/// </summary>
|
||||
public void SetBodyContent(string content)
|
||||
{
|
||||
_bodyContent = content;
|
||||
}
|
||||
}
|
||||
}
|
@ -128,23 +128,27 @@ namespace CryptoExchange.Net.Testing
|
||||
var uriParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InUri ? client.CreateParameterDictionary(parameters) : null;
|
||||
var bodyParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InBody ? client.CreateParameterDictionary(parameters) : null;
|
||||
|
||||
var headers = new Dictionary<string, string>();
|
||||
var requestDefinition = new RestRequestConfiguration(
|
||||
new RequestDefinition(path, method)
|
||||
{
|
||||
Authenticated = true
|
||||
},
|
||||
host,
|
||||
uriParams ?? new Dictionary<string, object>(),
|
||||
bodyParams ?? new Dictionary<string, object>(),
|
||||
new Dictionary<string, string>(),
|
||||
client.ArraySerialization,
|
||||
client.ParameterPositions[method],
|
||||
client.RequestBodyFormat
|
||||
);
|
||||
|
||||
authProvider.TimeProvider = new TestAuthTimeProvider(time ?? new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc));
|
||||
authProvider.AuthenticateRequest(
|
||||
authProvider.ProcessRequest(
|
||||
client,
|
||||
new Uri(host.AppendPath(path)),
|
||||
method,
|
||||
ref uriParams,
|
||||
ref bodyParams,
|
||||
ref headers,
|
||||
true,
|
||||
client.ArraySerialization,
|
||||
client.ParameterPositions[method],
|
||||
client.RequestBodyFormat
|
||||
requestDefinition
|
||||
);
|
||||
|
||||
var signature = getSignature(uriParams, bodyParams, headers);
|
||||
var signature = getSignature(requestDefinition.QueryParameters, requestDefinition.BodyParameters, requestDefinition.Headers);
|
||||
|
||||
if (!string.Equals(signature, expectedSignature, compareCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
|
||||
throw new Exception($"Signatures do not match. Expected: {expectedSignature}, Actual: {signature}");
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
|
||||
<Router AppAssembly="@typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
</Found>
|
||||
|
10
README.md
10
README.md
@ -59,6 +59,16 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
|
||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||
|
||||
## Release notes
|
||||
* Version 9.5.0 - 19 Aug 2025
|
||||
* Added better error handling support
|
||||
* Added ErrorDescription, ErrorType and IsTransient to Error object
|
||||
* Added ErrorCode in favor of Code
|
||||
* Updated some error messages
|
||||
* Refactored RestApiClient request authentication and AuthenticationProvider to prevent duplicate query string / body serialization
|
||||
* Fixed IOrderBookSocketClient Shared interface not getting registered in DI
|
||||
* Fixed response type in websocket queries not interested in the response
|
||||
* Fixed timing issue in query response processing
|
||||
|
||||
* Version 9.4.0 - 04 Aug 2025
|
||||
* Updated Shared symbol requests/subscriptions to allow multiple symbols in one call if supported
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user