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

Compare commits

..

7 Commits

Author SHA1 Message Date
Jan Korf 9fcd722991 Feature/body uri param split (#203)
* Added support for specifying seperate uri and body parameters
* Added support for different message and handling generic types on socket queries
* Split DataEvent.Topic into StreamId and Symbol properties
* Added support for negative time values parsing
* Added some helper methods for converting DataEvent to CallResult
* Added support for GZip/Deflate automatic decompressing in the default HttpClient
* Updated some testing methods
2024-06-11 16:23:48 +02:00
JKorf 8080ecccc0 Added support for Patch requests, added SetBody to ParameterCollection for directly setting the request body 2024-06-04 09:54:24 +02:00
JKorf 4b6fa9a1b1 Delete release.yml 2024-06-02 09:38:23 +02:00
JKorf 0b6dbde7d4 docs 2024-06-01 22:56:43 +02:00
JKorf fe4d63ba75 Update index.html 2024-06-01 22:17:26 +02:00
JKorf 04bd3727ca Updated version 2024-05-07 09:56:59 +02:00
JKorf 7e6fcd03c2 Update release.yml 2024-05-07 09:50:22 +02:00
19 changed files with 291 additions and 109 deletions
-23
View File
@@ -1,23 +0,0 @@
name: Publish Nuget
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup .NET SDK
uses: actions/setup-dotnet@v1
- name: Build
run: dotnet build -c Release
- name: Test
run: dotnet test -c Release --no-build
- name: Pack nugets
run: dotnet pack -c Release --no-build --include-symbols -p:PackageVersion=${{github.event.release.name}} --output .
- name: Push Package to NuGet
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.nuget_api_key}} --source https://api.nuget.org/v3/index.json
@@ -49,11 +49,11 @@ namespace CryptoExchange.Net.Authentication
/// <param name="method">The method of the request</param> /// <param name="method">The method of the request</param>
/// <param name="auth">If the requests should be authenticated</param> /// <param name="auth">If the requests should be authenticated</param>
/// <param name="arraySerialization">Array serialization type</param> /// <param name="arraySerialization">Array serialization type</param>
/// <param name="parameterPosition">The position where the providedParameters should go</param>
/// <param name="requestBodyFormat">The formatting of the request body</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="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="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="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( public abstract void AuthenticateRequest(
RestApiClient apiClient, RestApiClient apiClient,
Uri uri, Uri uri,
@@ -434,6 +434,20 @@ namespace CryptoExchange.Net.Authentication
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture); return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
} }
/// <summary>
/// Return the serialized request body
/// </summary>
/// <param name="serializer"></param>
/// <param name="parameters"></param>
/// <returns></returns>
protected string GetSerializedBody(IMessageSerializer serializer, IDictionary<string, object> parameters)
{
if (parameters.Count == 1 && parameters.ContainsKey(Constants.BodyPlaceHolderKey))
return serializer.Serialize(parameters[Constants.BodyPlaceHolderKey]);
else
return serializer.Serialize(parameters);
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
+64 -30
View File
@@ -75,7 +75,8 @@ namespace CryptoExchange.Net.Clients
{ HttpMethod.Get, HttpMethodParameterPosition.InUri }, { HttpMethod.Get, HttpMethodParameterPosition.InUri },
{ HttpMethod.Post, HttpMethodParameterPosition.InBody }, { HttpMethod.Post, HttpMethodParameterPosition.InBody },
{ HttpMethod.Delete, HttpMethodParameterPosition.InBody }, { HttpMethod.Delete, HttpMethodParameterPosition.InBody },
{ HttpMethod.Put, HttpMethodParameterPosition.InBody } { HttpMethod.Put, HttpMethodParameterPosition.InBody },
{ new HttpMethod("Patch"), HttpMethodParameterPosition.InBody },
}; };
/// <inheritdoc /> /// <inheritdoc />
@@ -149,23 +150,60 @@ namespace CryptoExchange.Net.Clients
/// <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>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<WebCallResult<T>> SendAsync<T>( protected virtual Task<WebCallResult<T>> SendAsync<T>(
string baseAddress, string baseAddress,
RequestDefinition definition, RequestDefinition definition,
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) where T : class
{
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
return SendAsync<T>(
baseAddress,
definition,
parameterPosition == HttpMethodParameterPosition.InUri ? parameters : null,
parameterPosition == HttpMethodParameterPosition.InBody ? parameters : null,
cancellationToken,
additionalHeaders,
weight);
}
/// <summary>
/// Send a request to the base address based on the request definition
/// </summary>
/// <typeparam name="T">Response type</typeparam>
/// <param name="baseAddress">Host and schema</param>
/// <param name="definition">Request definition</param>
/// <param name="uriParameters">Request query parameters</param>
/// <param name="bodyParameters">Request body parameters</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <param name="additionalHeaders">Additional headers for this request</param>
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
/// <returns></returns>
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
string baseAddress,
RequestDefinition definition,
ParameterCollection? uriParameters,
ParameterCollection? bodyParameters,
CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null,
int? weight = null) where T : class
{ {
int currentTry = 0; int currentTry = 0;
while (true) while (true)
{ {
currentTry++; currentTry++;
var prepareResult = await PrepareAsync(baseAddress, definition, parameters, cancellationToken, additionalHeaders, weight).ConfigureAwait(false); var prepareResult = await PrepareAsync(baseAddress, definition, cancellationToken, additionalHeaders, weight).ConfigureAwait(false);
if (!prepareResult) if (!prepareResult)
return new WebCallResult<T>(prepareResult.Error!); return new WebCallResult<T>(prepareResult.Error!);
var request = CreateRequest(baseAddress, definition, parameters, additionalHeaders); var request = CreateRequest(
baseAddress,
definition,
uriParameters,
bodyParameters,
additionalHeaders);
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"))); _logger.RestApiSendRequest(request.RequestId, definition, request.Content, request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
TotalRequestsMade++; TotalRequestsMade++;
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false); var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
@@ -186,7 +224,6 @@ namespace CryptoExchange.Net.Clients
/// </summary> /// </summary>
/// <param name="baseAddress">Host and schema</param> /// <param name="baseAddress">Host and schema</param>
/// <param name="definition">Request definition</param> /// <param name="definition">Request definition</param>
/// <param name="parameters">Request parameters</param>
/// <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>
@@ -195,7 +232,6 @@ namespace CryptoExchange.Net.Clients
protected virtual async Task<CallResult> PrepareAsync( protected virtual async Task<CallResult> PrepareAsync(
string baseAddress, string baseAddress,
RequestDefinition definition, RequestDefinition definition,
ParameterCollection? parameters,
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null, Dictionary<string, string>? additionalHeaders = null,
int? weight = null) int? weight = null)
@@ -263,25 +299,27 @@ namespace CryptoExchange.Net.Clients
/// </summary> /// </summary>
/// <param name="baseAddress">Host and schema</param> /// <param name="baseAddress">Host and schema</param>
/// <param name="definition">Request definition</param> /// <param name="definition">Request definition</param>
/// <param name="parameters">The parameters of the request</param> /// <param name="uriParameters">The query parameters of the request</param>
/// <param name="bodyParameters">The body parameters of the request</param>
/// <param name="additionalHeaders">Additional headers to send with the request</param> /// <param name="additionalHeaders">Additional headers to send with the request</param>
/// <returns></returns> /// <returns></returns>
protected virtual IRequest CreateRequest( protected virtual IRequest CreateRequest(
string baseAddress, string baseAddress,
RequestDefinition definition, RequestDefinition definition,
ParameterCollection? parameters, ParameterCollection? uriParameters,
ParameterCollection? bodyParameters,
Dictionary<string, string>? additionalHeaders) Dictionary<string, string>? additionalHeaders)
{ {
parameters ??= new ParameterCollection(); var uriParams = uriParameters == null ? new ParameterCollection() : CreateParameterDictionary(uriParameters);
var bodyParams = bodyParameters == null ? new ParameterCollection() : CreateParameterDictionary(bodyParameters);
var uri = new Uri(baseAddress.AppendPath(definition.Path)); var uri = new Uri(baseAddress.AppendPath(definition.Path));
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
var arraySerialization = definition.ArraySerialization ?? ArraySerialization; var arraySerialization = definition.ArraySerialization ?? ArraySerialization;
var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat; var bodyFormat = definition.RequestBodyFormat ?? RequestBodyFormat;
var requestId = ExchangeHelpers.NextId(); var requestId = ExchangeHelpers.NextId();
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
var headers = new Dictionary<string, string>(); var headers = new Dictionary<string, string>();
var uriParameters = parameterPosition == HttpMethodParameterPosition.InUri ? CreateParameterDictionary(parameters) : new Dictionary<string, object>();
var bodyParameters = parameterPosition == HttpMethodParameterPosition.InBody ? CreateParameterDictionary(parameters) : new Dictionary<string, object>();
if (AuthenticationProvider != null) if (AuthenticationProvider != null)
{ {
try try
@@ -290,13 +328,14 @@ namespace CryptoExchange.Net.Clients
this, this,
uri, uri,
definition.Method, definition.Method,
uriParameters, uriParams,
bodyParameters, bodyParams,
headers, headers,
definition.Authenticated, definition.Authenticated,
arraySerialization, arraySerialization,
parameterPosition, parameterPosition,
bodyFormat); bodyFormat
);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -304,18 +343,8 @@ namespace CryptoExchange.Net.Clients
} }
} }
// Sanity check
foreach (var param in parameters)
{
if (!uriParameters.ContainsKey(param.Key) && !bodyParameters.ContainsKey(param.Key))
{
throw new Exception($"Missing parameter {param.Key} after authentication processing. AuthenticationProvider implementation " +
$"should return provided parameters in either the uri or body parameters output");
}
}
// Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
uri = uri.SetParameters(uriParameters, arraySerialization); uri = uri.SetParameters(uriParams, arraySerialization);
var request = RequestFactory.Create(definition.Method, uri, requestId); var request = RequestFactory.Create(definition.Method, uri, requestId);
request.Accept = Constants.JsonContentHeader; request.Accept = Constants.JsonContentHeader;
@@ -342,8 +371,8 @@ namespace CryptoExchange.Net.Clients
if (parameterPosition == HttpMethodParameterPosition.InBody) if (parameterPosition == HttpMethodParameterPosition.InBody)
{ {
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
if (bodyParameters.Count != 0) if (bodyParams.Count != 0)
WriteParamBody(request, bodyParameters, contentType); WriteParamBody(request, bodyParams, contentType);
else else
request.SetContent(RequestBodyEmptyContent, contentType); request.SetContent(RequestBodyEmptyContent, contentType);
} }
@@ -738,7 +767,8 @@ namespace CryptoExchange.Net.Clients
signed, signed,
arraySerialization, arraySerialization,
parameterPosition, parameterPosition,
bodyFormat); bodyFormat
);
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -804,7 +834,11 @@ namespace CryptoExchange.Net.Clients
if (contentType == Constants.JsonContentHeader) if (contentType == Constants.JsonContentHeader)
{ {
// Write the parameters as json in the body // Write the parameters as json in the body
var stringData = CreateSerializer().Serialize(parameters); string stringData;
if (parameters.Count == 1 && parameters.ContainsKey(Constants.BodyPlaceHolderKey))
stringData = CreateSerializer().Serialize(parameters[Constants.BodyPlaceHolderKey]);
else
stringData = CreateSerializer().Serialize(parameters);
request.SetContent(stringData, contentType); request.SetContent(stringData, contentType);
} }
else if (contentType == Constants.FormContentHeader) else if (contentType == Constants.FormContentHeader)
+11 -9
View File
@@ -278,10 +278,11 @@ namespace CryptoExchange.Net.Clients
/// <summary> /// <summary>
/// Send a query on a socket connection to the BaseAddress and wait for the response /// Send a query on a socket connection to the BaseAddress and wait for the response
/// </summary> /// </summary>
/// <typeparam name="T">Expected result type</typeparam> /// <typeparam name="THandlerResponse">Expected result type</typeparam>
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <returns></returns> /// <returns></returns>
protected virtual Task<CallResult<T>> QueryAsync<T>(Query<T> query) protected virtual Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query)
{ {
return QueryAsync(BaseAddress, query); return QueryAsync(BaseAddress, query);
} }
@@ -289,14 +290,15 @@ namespace CryptoExchange.Net.Clients
/// <summary> /// <summary>
/// Send a query on a socket connection and wait for the response /// Send a query on a socket connection and wait for the response
/// </summary> /// </summary>
/// <typeparam name="T">The expected result type</typeparam> /// <typeparam name="THandlerResponse">Expected result type</typeparam>
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
/// <param name="url">The url for the request</param> /// <param name="url">The url for the request</param>
/// <param name="query">The query</param> /// <param name="query">The query</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<CallResult<T>> QueryAsync<T>(string url, Query<T> query) protected virtual async Task<CallResult<THandlerResponse>> QueryAsync<TServerResponse, THandlerResponse>(string url, Query<TServerResponse, THandlerResponse> query)
{ {
if (_disposing) if (_disposing)
return new CallResult<T>(new InvalidOperationError("Client disposed, can't query")); return new CallResult<THandlerResponse>(new InvalidOperationError("Client disposed, can't query"));
SocketConnection socketConnection; SocketConnection socketConnection;
var released = false; var released = false;
@@ -305,7 +307,7 @@ namespace CryptoExchange.Net.Clients
{ {
var socketResult = await GetSocketConnection(url, query.Authenticated).ConfigureAwait(false); var socketResult = await GetSocketConnection(url, query.Authenticated).ConfigureAwait(false);
if (!socketResult) if (!socketResult)
return socketResult.As<T>(default); return socketResult.As<THandlerResponse>(default);
socketConnection = socketResult.Data; socketConnection = socketResult.Data;
@@ -318,7 +320,7 @@ namespace CryptoExchange.Net.Clients
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false); var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated).ConfigureAwait(false);
if (!connectResult) if (!connectResult)
return new CallResult<T>(connectResult.Error!); return new CallResult<THandlerResponse>(connectResult.Error!);
} }
finally finally
{ {
@@ -329,10 +331,10 @@ namespace CryptoExchange.Net.Clients
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId); _logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
return new CallResult<T>(new ServerError("Socket is paused")); return new CallResult<THandlerResponse>(new ServerError("Socket is paused"));
} }
return await socketConnection.SendAndWaitQueryAsync(query).ConfigureAwait(false); return await socketConnection.SendAndWaitQueryAsync<TServerResponse, THandlerResponse>(query).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -154,6 +154,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue)) if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue))
{ {
// Parse 1637745563.000 format // Parse 1637745563.000 format
if (doubleValue <= 0)
return default;
if (doubleValue < 19999999999) if (doubleValue < 19999999999)
return ConvertFromSeconds(doubleValue); return ConvertFromSeconds(doubleValue);
if (doubleValue < 19999999999999) if (doubleValue < 19999999999999)
@@ -68,6 +68,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}"; var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]")); return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
} }
catch (Exception ex)
{
var info = $"Unknown exception: {ex.Message}";
return new CallResult<T>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
} }
/// <inheritdoc /> /// <inheritdoc />
+3 -3
View File
@@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description> <Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>7.5.1</PackageVersion> <PackageVersion>7.5.2</PackageVersion>
<AssemblyVersion>7.5.1</AssemblyVersion> <AssemblyVersion>7.5.2</AssemblyVersion>
<FileVersion>7.5.1</FileVersion> <FileVersion>7.5.2</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>
+4
View File
@@ -13,5 +13,9 @@
/// Form content type header /// Form content type header
/// </summary> /// </summary>
public const string FormContentHeader = "application/x-www-form-urlencoded"; public const string FormContentHeader = "application/x-www-form-urlencoded";
/// <summary>
/// Placeholder key for when request body should be set to the value of this KVP
/// </summary>
public const string BodyPlaceHolderKey = "_BODY_";
} }
} }
@@ -3,6 +3,7 @@ using CryptoExchange.Net.Converters.SystemTextJson;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
@@ -193,5 +194,18 @@ namespace CryptoExchange.Net.Objects
Add(key, int.Parse(stringVal)); Add(key, int.Parse(stringVal));
} }
} }
/// <summary>
/// Set the request body. Can be used to specify a simple value or array as the body instead of an object
/// </summary>
/// <param name="body">Body to set</param>
/// <exception cref="InvalidOperationException"></exception>
public void SetBody(object body)
{
if (this.Any())
throw new InvalidOperationException("Can't set body when other parameters already specified");
Add(Constants.BodyPlaceHolderKey, body);
}
} }
} }
+78 -11
View File
@@ -14,9 +14,14 @@ namespace CryptoExchange.Net.Objects.Sockets
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
/// <summary> /// <summary>
/// The topic of the update, what symbol/asset etc.. /// The stream producing the update
/// </summary> /// </summary>
public string? Topic { get; set; } public string? StreamId { get; set; }
/// <summary>
/// The symbol the update is for
/// </summary>
public string? Symbol { get; set; }
/// <summary> /// <summary>
/// The original data that was received, only available when OutputOriginalData is set to true in the client options /// The original data that was received, only available when OutputOriginalData is set to true in the client options
@@ -33,10 +38,11 @@ namespace CryptoExchange.Net.Objects.Sockets
/// </summary> /// </summary>
public T Data { get; set; } public T Data { get; set; }
internal DataEvent(T data, string? topic, string? originalData, DateTime timestamp, SocketUpdateType? updateType) internal DataEvent(T data, string? streamId, string? symbol, string? originalData, DateTime timestamp, SocketUpdateType? updateType)
{ {
Data = data; Data = data;
Topic = topic; StreamId = streamId;
Symbol = symbol;
OriginalData = originalData; OriginalData = originalData;
Timestamp = timestamp; Timestamp = timestamp;
UpdateType = updateType; UpdateType = updateType;
@@ -50,7 +56,7 @@ namespace CryptoExchange.Net.Objects.Sockets
/// <returns></returns> /// <returns></returns>
public DataEvent<K> As<K>(K data) public DataEvent<K> As<K>(K data)
{ {
return new DataEvent<K>(data, Topic, OriginalData, Timestamp, UpdateType); return new DataEvent<K>(data, StreamId, Symbol, OriginalData, Timestamp, UpdateType);
} }
/// <summary> /// <summary>
@@ -58,11 +64,11 @@ namespace CryptoExchange.Net.Objects.Sockets
/// </summary> /// </summary>
/// <typeparam name="K">The type of the new data</typeparam> /// <typeparam name="K">The type of the new data</typeparam>
/// <param name="data">The new data</param> /// <param name="data">The new data</param>
/// <param name="topic">The new topic</param> /// <param name="symbol">The new symbol</param>
/// <returns></returns> /// <returns></returns>
public DataEvent<K> As<K>(K data, string? topic) public DataEvent<K> As<K>(K data, string? symbol)
{ {
return new DataEvent<K>(data, topic, OriginalData, Timestamp, UpdateType); return new DataEvent<K>(data, StreamId, symbol, OriginalData, Timestamp, UpdateType);
} }
/// <summary> /// <summary>
@@ -70,12 +76,73 @@ namespace CryptoExchange.Net.Objects.Sockets
/// </summary> /// </summary>
/// <typeparam name="K">The type of the new data</typeparam> /// <typeparam name="K">The type of the new data</typeparam>
/// <param name="data">The new data</param> /// <param name="data">The new data</param>
/// <param name="topic">The new topic</param> /// <param name="streamId">The new stream id</param>
/// <param name="symbol">The new symbol</param>
/// <param name="updateType">The type of update</param> /// <param name="updateType">The type of update</param>
/// <returns></returns> /// <returns></returns>
public DataEvent<K> As<K>(K data, string? topic, SocketUpdateType updateType) public DataEvent<K> As<K>(K data, string streamId, string? symbol, SocketUpdateType updateType)
{ {
return new DataEvent<K>(data, topic, OriginalData, Timestamp, updateType); return new DataEvent<K>(data, streamId, symbol, OriginalData, Timestamp, updateType);
}
/// <summary>
/// Specify the symbol
/// </summary>
/// <param name="symbol"></param>
/// <returns></returns>
public DataEvent<T> WithSymbol(string symbol)
{
Symbol = symbol;
return this;
}
/// <summary>
/// Specify the update type
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public DataEvent<T> WithUpdateType(SocketUpdateType type)
{
UpdateType = type;
return this;
}
/// <summary>
/// Specify the stream id
/// </summary>
/// <param name="streamId"></param>
/// <returns></returns>
public DataEvent<T> WithStreamId(string streamId)
{
StreamId = streamId;
return this;
}
/// <summary>
/// Create a CallResult from this DataEvent
/// </summary>
/// <returns></returns>
public CallResult<T> ToCallResult()
{
return new CallResult<T>(Data, OriginalData, null);
}
/// <summary>
/// Create a CallResult from this DataEvent
/// </summary>
/// <returns></returns>
public CallResult<K> ToCallResult<K>(K data)
{
return new CallResult<K>(data, OriginalData, null);
}
/// <summary>
/// Create a CallResult from this DataEvent
/// </summary>
/// <returns></returns>
public CallResult<K> ToCallResult<K>(Error error)
{
return new CallResult<K>(default, OriginalData, error);
} }
} }
} }
@@ -19,6 +19,7 @@ namespace CryptoExchange.Net.Requests
if (client == null) if (client == null)
{ {
var handler = new HttpClientHandler(); var handler = new HttpClientHandler();
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
if (proxy != null) if (proxy != null)
{ {
handler.Proxy = new WebProxy handler.Proxy = new WebProxy
+35 -8
View File
@@ -1,6 +1,7 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Requests;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
@@ -145,16 +146,17 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Query /// Query
/// </summary> /// </summary>
/// <typeparam name="TResponse">Response object type</typeparam> /// <typeparam name="TServerResponse">The type returned from the server</typeparam>
public abstract class Query<TResponse> : Query /// <typeparam name="THandlerResponse">The type to be returned to the caller</typeparam>
public abstract class Query<TServerResponse, THandlerResponse> : Query
{ {
/// <inheritdoc /> /// <inheritdoc />
public override Type? GetMessageType(IMessageAccessor message) => typeof(TResponse); public override Type? GetMessageType(IMessageAccessor message) => typeof(TServerResponse);
/// <summary> /// <summary>
/// The typed call result /// The typed call result
/// </summary> /// </summary>
public CallResult<TResponse>? TypedResult => (CallResult<TResponse>?)Result; public CallResult<THandlerResponse>? TypedResult => (CallResult<THandlerResponse>?)Result;
/// <summary> /// <summary>
/// ctor /// ctor
@@ -171,7 +173,7 @@ namespace CryptoExchange.Net.Sockets
{ {
Completed = true; Completed = true;
Response = message.Data; Response = message.Data;
Result = HandleMessage(connection, message.As((TResponse)message.Data)); Result = HandleMessage(connection, message.As((TServerResponse)message.Data));
_event.Set(); _event.Set();
ContinueAwaiter?.WaitOne(); ContinueAwaiter?.WaitOne();
return Result; return Result;
@@ -183,7 +185,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="connection"></param> /// <param name="connection"></param>
/// <param name="message"></param> /// <param name="message"></param>
/// <returns></returns> /// <returns></returns>
public virtual CallResult<TResponse> HandleMessage(SocketConnection connection, DataEvent<TResponse> message) => new CallResult<TResponse>(message.Data, message.OriginalData, null); public abstract CallResult<THandlerResponse> HandleMessage(SocketConnection connection, DataEvent<TServerResponse> message);
/// <inheritdoc /> /// <inheritdoc />
public override void Timeout() public override void Timeout()
@@ -192,7 +194,7 @@ namespace CryptoExchange.Net.Sockets
return; return;
Completed = true; Completed = true;
Result = new CallResult<TResponse>(new CancellationRequestedError(null, "Query timeout", null)); Result = new CallResult<THandlerResponse>(new CancellationRequestedError(null, "Query timeout", null));
ContinueAwaiter?.Set(); ContinueAwaiter?.Set();
_event.Set(); _event.Set();
} }
@@ -200,10 +202,35 @@ namespace CryptoExchange.Net.Sockets
/// <inheritdoc /> /// <inheritdoc />
public override void Fail(Error error) public override void Fail(Error error)
{ {
Result = new CallResult<TResponse>(error); Result = new CallResult<THandlerResponse>(error);
Completed = true; Completed = true;
ContinueAwaiter?.Set(); ContinueAwaiter?.Set();
_event.Set(); _event.Set();
} }
} }
/// <summary>
/// Query
/// </summary>
/// <typeparam name="TResponse">Response object type</typeparam>
public abstract class Query<TResponse> : Query<TResponse, TResponse>
{
/// <summary>
/// ctor
/// </summary>
/// <param name="request"></param>
/// <param name="authenticated"></param>
/// <param name="weight"></param>
protected Query(object request, bool authenticated, int weight = 1) : base(request, authenticated, weight)
{
}
/// <summary>
/// Handle the query response
/// </summary>
/// <param name="connection"></param>
/// <param name="message"></param>
/// <returns></returns>
public override CallResult<TResponse> HandleMessage(SocketConnection connection, DataEvent<TResponse> message) => message.ToCallResult();
}
} }
@@ -498,7 +498,7 @@ namespace CryptoExchange.Net.Sockets
try try
{ {
var innerSw = Stopwatch.StartNew(); var innerSw = Stopwatch.StartNew();
processor.Handle(this, new DataEvent<object>(deserialized, null, originalData, receiveTime, null)); processor.Handle(this, new DataEvent<object>(deserialized, null, null, originalData, receiveTime, null));
totalUserTime += (int)innerSw.ElapsedMilliseconds; totalUserTime += (int)innerSw.ElapsedMilliseconds;
} }
catch (Exception ex) catch (Exception ex)
@@ -696,14 +696,15 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Send a query request and wait for an answer /// Send a query request and wait for an answer
/// </summary> /// </summary>
/// <typeparam name="T">Query response type</typeparam> /// <typeparam name="THandlerResponse">Expected result type</typeparam>
/// <typeparam name="TServerResponse">The type returned to the caller</typeparam>
/// <param name="query">Query to send</param> /// <param name="query">Query to send</param>
/// <param name="continueEvent">Wait event for when the socket message handler can continue</param> /// <param name="continueEvent">Wait event for when the socket message handler can continue</param>
/// <returns></returns> /// <returns></returns>
public virtual async Task<CallResult<T>> SendAndWaitQueryAsync<T>(Query<T> query, ManualResetEvent? continueEvent = null) public virtual async Task<CallResult<THandlerResponse>> SendAndWaitQueryAsync<TServerResponse, THandlerResponse>(Query<TServerResponse, THandlerResponse> query, ManualResetEvent? continueEvent = null)
{ {
await SendAndWaitIntAsync(query, continueEvent).ConfigureAwait(false); await SendAndWaitIntAsync(query, continueEvent).ConfigureAwait(false);
return query.TypedResult ?? new CallResult<T>(new ServerError("Timeout")); return query.TypedResult ?? new CallResult<THandlerResponse>(new ServerError("Timeout"));
} }
private async Task SendAndWaitIntAsync(Query query, ManualResetEvent? continueEvent) private async Task SendAndWaitIntAsync(Query query, ManualResetEvent? continueEvent)
@@ -52,8 +52,13 @@ namespace CryptoExchange.Net.Testing.Comparers
else else
{ {
if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null) if (dict[dictProp.Name] == default && dictProp.Value.Type != JTokenType.Null)
{
if (dictProp.Value.ToString() == "")
continue;
// Property value not correct // Property value not correct
throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {dictProp.Value}"); throw new Exception($"{method}: Dictionary entry `{dictProp.Name}` has no value while input json has value {dictProp.Value}");
}
} }
} }
} }
@@ -162,7 +167,7 @@ namespace CryptoExchange.Net.Testing.Comparers
if (dictProp.Value.Type == JTokenType.Object) if (dictProp.Value.Type == JTokenType.Object)
{ {
CheckObject(method, dictProp, dict[dictProp.Name]!, ignoreProperties); CheckPropertyValue(method, dictProp.Value, dict[dictProp.Name]!, dict[dictProp.Name].GetType(), null, null, ignoreProperties);
} }
else else
{ {
@@ -180,7 +185,10 @@ namespace CryptoExchange.Net.Testing.Comparers
var enumerator = list.GetEnumerator(); var enumerator = list.GetEnumerator();
foreach (JToken jtoken in jObjs) foreach (JToken jtoken in jObjs)
{ {
enumerator.MoveNext(); var moved = enumerator.MoveNext();
if (!moved)
throw new Exception("Enumeration not moved; incorrect amount of results?");
var typeConverter = enumerator.Current.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true); var typeConverter = enumerator.Current.GetType().GetCustomAttributes(typeof(JsonConverterAttribute), true);
if (typeConverter.Length != 0 && ((JsonConverterAttribute)typeConverter.First()).ConverterType != typeof(ArrayConverter)) if (typeConverter.Length != 0 && ((JsonConverterAttribute)typeConverter.First()).ConverterType != typeof(ArrayConverter))
// Custom converter for the type, skip // Custom converter for the type, skip
@@ -260,9 +268,9 @@ namespace CryptoExchange.Net.Testing.Comparers
else if (objectValue is DateTime time) else if (objectValue is DateTime time)
{ {
if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!)) if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!))
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}"); throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} vs {time}");
} }
else if (propertyType.IsEnum) else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
{ {
// TODO enum comparing // TODO enum comparing
} }
@@ -278,6 +286,10 @@ namespace CryptoExchange.Net.Testing.Comparers
if (time != DateTimeConverter.ParseFromDouble(jsonValue.Value<long>()!)) if (time != DateTimeConverter.ParseFromDouble(jsonValue.Value<long>()!))
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}"); throw new Exception($"{method}: {property} not equal: {jsonValue.Value<decimal>()} vs {time}");
} }
else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)
{
// TODO enum comparing
}
else if (jsonValue.Value<long>() != Convert.ToInt64(objectValue)) else if (jsonValue.Value<long>() != Convert.ToInt64(objectValue))
{ {
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} vs {Convert.ToInt64(objectValue)}"); throw new Exception($"{method}: {property} not equal: {jsonValue.Value<long>()} vs {Convert.ToInt64(objectValue)}");
@@ -109,6 +109,7 @@ namespace CryptoExchange.Net.Testing
if (lastMessage == null) if (lastMessage == null)
throw new Exception($"{name} expected to {line} to be send to server but did not receive anything"); throw new Exception($"{name} expected to {line} to be send to server but did not receive anything");
var lastMessageJson = JToken.Parse(lastMessage); var lastMessageJson = JToken.Parse(lastMessage);
var expectedJson = JToken.Parse(line.Substring(2)); var expectedJson = JToken.Parse(line.Substring(2));
foreach(var item in expectedJson) foreach(var item in expectedJson)
@@ -121,6 +122,12 @@ namespace CryptoExchange.Net.Testing
overrideKey = val.ToString(); overrideKey = val.ToString();
overrideValue = lastMessageJson[prop.Name]?.Value<string>(); overrideValue = lastMessageJson[prop.Name]?.Value<string>();
} }
else if (val.ToString() == "-999")
{
// -999 value is used to replace parts or response messages
overrideKey = val.ToString();
overrideValue = lastMessageJson[prop.Name]?.Value<decimal>().ToString();
}
else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true) else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true)
throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}"); throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}");
} }
+3 -2
View File
@@ -136,8 +136,9 @@ namespace CryptoExchange.Net.Testing
headers, headers,
true, true,
client.ArraySerialization, client.ArraySerialization,
client.ParameterPositions[method], client.ParameterPositions[method],
client.RequestBodyFormat); client.RequestBodyFormat
);
var signature = getSignature(uriParams, bodyParams, headers); var signature = getSignature(uriParams, bodyParams, headers);
+3
View File
@@ -45,6 +45,9 @@ 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 7.5.2 - 07 May 2024
* Fixed SetApiCredentials not correctly being used by rate limiter causing exception
* Version 7.5.1 - 03 May 2024 * Version 7.5.1 - 03 May 2024
* Some small improvements in unit testing components * Some small improvements in unit testing components
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+25 -14
View File
@@ -82,16 +82,17 @@
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="#idocs_installation">Installation</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_installation">Installation</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_di">Dependency Injection</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_di">Dependency Injection</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_general">General Client Usage</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_rest">REST API Client</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_rest">REST API Client</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_socket">Websocket API Client</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_socket">Websocket API Client</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_common">Common clients</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_common">Common Clients</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item"><a class="nav-link" href="#idocs_options">Options & Authorization</a> <li class="nav-item"><a class="nav-link" href="#idocs_options">Options & Authorization</a>
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="#idocs_auth">Authorization</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_auth">Authorization</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_options_set">Setting options</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_options_set">Setting Options</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_options_def">Option definitions</a></li> <li class="nav-item"><a class="nav-link" href="#idocs_options_def">Option Definitions</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item"><a class="nav-link" href="#idocs_features">Additional Features</a> <li class="nav-item"><a class="nav-link" href="#idocs_features">Additional Features</a>
@@ -122,15 +123,16 @@
<div class="idocs-content"> <div class="idocs-content">
<div class="container"> <div class="container">
<section id="idocs_intro"> <section id="idocs_intro">
<h1>CryptoExchange.Net</h1> <h1>CryptoExchange.Net & Implementations</h1>
<p>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.</p> <p>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.</p>
<div class="alert alert-info">All libraries can be used in the same project as well as individually, just install the exchange libraries you need!</div> <p>When access to multiple or all exchange API's is needed, the CryptoClients.Net library combines all different client libraries in a single Nuget package. The following image illustrates the structure:</p>
<p>The following API's are directly supported. Note that there are 3rd party implementations going around, but only these are created and supported by me</p> <p><img src="assets/images/struct.png" /></p>
<p>These Exchanges/API's are directly supported:</p>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
<th>Exchange</th> <th>API</th>
<th>Repository</th> <th>Repository</th>
<th>Nuget</th> <th>Nuget</th>
</tr> </tr>
@@ -147,12 +149,12 @@
<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"><img src="https://img.shields.io/nuget/v/JK.Mexc.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"><img src="https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square" /></a></td></tr>
<tr><td>OKX</td><td><a href="https://github.com/JKorf/OKX.Net">JKorf/OKX.Net</a></td><td><a href="https://www.nuget.org/packages/JK.OKX.Net"><img src="https://img.shields.io/nuget/v/JK.OKX.net.svg?style=flat-square" /></a></td></tr> <tr><td>OKX</td><td><a href="https://github.com/JKorf/OKX.Net">JKorf/OKX.Net</a></td><td><a href="https://www.nuget.org/packages/JK.OKX.Net"><img src="https://img.shields.io/nuget/v/JK.OKX.net.svg?style=flat-square" /></a></td></tr>
</table> </table>
<p>Note that there are 3rd party implementations going around, but only the listed ones here are created and supported by me.</p>
<p>Alternatively, use <a href="https://github.com/jkorf/CryptoClients.Net">CryptoClients.Net</a> which combines these packages and allows easy access to all exchange API's.</p> <p>When using multiple of these API's the <a href="https://github.com/jkorf/CryptoClients.Net">CryptoClients.Net</a> package can be used which combines these packages and allows easy access to all exchange API's.</p>
<h4>Supported Frameworks</h4> <h4>Supported Frameworks</h4>
<p> <p>
The library is targeting both <code>.NET Standard 2.0</code> and <code>.NET Standard 2.1</code> for optimal compatibility The libraries are targeting both <code>.NET Standard 2.0</code> and <code>.NET Standard 2.1</code> for optimal compatibility
</p> </p>
<table class="table table-bordered"> <table class="table table-bordered">
<tr> <tr>
@@ -811,6 +813,15 @@
<hr class="divider"> <hr class="divider">
<section id="idocs_general">
<h2>General client usage</h2>
<p>All clients work with the same principles:</p>
<ul>
<li>Mandatory parameters are non-nullable while optional parameters are nullable and will have a default value of null.</li>
<li>Any operation will return a form of <code>CallResult</code>. This result can and should be check for success. The clients will not throw exceptions.</li>
</ul>
</section>
<!-- HTML Structure <!-- HTML Structure
============================ --> ============================ -->
<section id="idocs_rest"> <section id="idocs_rest">
@@ -1376,14 +1387,14 @@ await client.UnsubscribeAllAsync();</code></pre>
============================ --> ============================ -->
<section id="idocs_common"> <section id="idocs_common">
<h2>Common Clients</h2> <h2>Common Clients</h2>
<p>CryptoClients.Net exposes some common clients. These clients aim to make using the different API's easier.</p> <p>The CryptoClients.Net client exposes some common client classes. These clients aim to make using the different API's easier.</p>
<p><b>(I)ExchangeRestClient</b><br /> <p><b>(I)ExchangeRestClient</b><br />
The <code>ExchangeRestClient</code> (or <code>ExchangeRestClient</code> when used directly) can be used to easily access REST clients for different API's. The <code>IExchangeRestClient</code> (or <code>ExchangeRestClient</code> when used directly) can be used to easily access REST clients for different API's.
</p> </p>
<p> <p>
For example, using the Binance, Bybit and Kucoin API's can be done like this: For example, using the Binance, Bybit and Kucoin API's can be done like this:
<pre><code>var exchangeRestClient = new ExchangeRestClient(); // Either construct it or inject the IExchangeRestClient into your service <pre><code>var exchangeRestClient = new ExchangeRestClient(); // Either construct it or inject the IExchangeRestClient into your service after having called 'AddCryptoClients()' during service regirations
var binanceTicker = await exchangeRestClient.Binance.SpotApi.ExchangeData.GetTickersAsync(); var binanceTicker = await exchangeRestClient.Binance.SpotApi.ExchangeData.GetTickersAsync();
var bybitTicker = await exchangeRestClient.Bybit.V5Api.ExchangeData.GetTickers(); var bybitTicker = await exchangeRestClient.Bybit.V5Api.ExchangeData.GetTickers();
var kucoinTicker = await exchangeRestClient.Kucoin.SpotApi.ExchangeData.GetTickers();</code></pre> var kucoinTicker = await exchangeRestClient.Kucoin.SpotApi.ExchangeData.GetTickers();</code></pre>
@@ -1393,7 +1404,7 @@ var kucoinTicker = await exchangeRestClient.Kucoin.SpotApi.ExchangeData.GetTicke
Similarly as the <code>(I)ExchangeRestClient</code> this client allows you to access the different Websocket clients through a single access point. Similarly as the <code>(I)ExchangeRestClient</code> this client allows you to access the different Websocket clients through a single access point.
</p> </p>
<p>For example accessing the Bitget, Kraken and OKX API's could be done like this: <p>For example accessing the Bitget, Kraken and OKX API's could be done like this:
<pre><code>var exchangeSocketClient = new ExchangeSocketClient(); // Either construct it or inject the ExchangeSocketClient into your service <pre><code>var exchangeSocketClient = new ExchangeSocketClient(); // Either construct it or inject the IExchangeSocketClient into your service after having called 'AddCryptoClients()' during service regirations
var bitgetSub = await exchangeSocketClient.Bitget.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {}); var bitgetSub = await exchangeSocketClient.Bitget.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {});
var krakenSub = await exchangeSocketClient.Kraken.SpotApi.SubscribeToTickerUpdatesAsync("ETH/USD", data => {}); var krakenSub = await exchangeSocketClient.Kraken.SpotApi.SubscribeToTickerUpdatesAsync("ETH/USD", data => {});
var okxSub = await exchangeSocketClient.OKX.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-USDT", data => {});</code></pre> var okxSub = await exchangeSocketClient.OKX.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-USDT", data => {});</code></pre>