1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-09-06 07:31:19 +00:00

Compare commits

..

No commits in common. "master" and "CryptoExchange.Net.9.3.1" have entirely different histories.

80 changed files with 397 additions and 1340 deletions

View File

@ -285,7 +285,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<object>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex)); return new CallResult<object>(new DeserializeError(ex.Message));
} }
} }
@ -320,7 +320,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
} }
catch(Exception ex) catch(Exception ex)
{ {
return new CallResult<T>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex)); return new CallResult<T>(new DeserializeError(ex.ToLogString()));
} }
} }
@ -354,7 +354,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
{ {
// Not a json message // Not a json message
IsValid = false; IsValid = false;
return Task.FromResult(new CallResult(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex))); return Task.FromResult(new CallResult(new DeserializeError("ProtoBufError: " + ex.Message, ex)));
} }
} }
@ -446,7 +446,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<object>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex)); return new CallResult<object>(new DeserializeError(ex.ToLogString()));
} }
} }
@ -485,7 +485,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<T>(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex)); return new CallResult<T>(new DeserializeError(ex.Message));
} }
} }
@ -504,7 +504,7 @@ namespace CryptoExchange.Net.Converters.Protobuf
{ {
// Not a json message // Not a json message
IsValid = false; IsValid = false;
return new CallResult(new DeserializeError("Protobuf deserialization failed: " + ex.Message, ex)); return new CallResult(new DeserializeError("ProtobufError: " + ex.Message, ex));
} }
} }

View File

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

View File

@ -5,19 +5,6 @@
Protobuf support for CryptoExchange.Net. Protobuf support for CryptoExchange.Net.
## Release notes ## Release notes
* Version 9.7.0 - 01 Sep 2025
* Updated CryptoExchange.Net version to 9.7.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
* Version 9.6.0 - 25 Aug 2025
* Updated CryptoExchange.Net version to 9.6.0
* 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
* Version 9.3.0 - 23 Jul 2025 * Version 9.3.0 - 23 Jul 2025
* Updated CryptoExchange.Net to version 9.3.0, see https://github.com/JKorf/CryptoExchange.Net/releases/ * Updated CryptoExchange.Net to version 9.3.0, see https://github.com/JKorf/CryptoExchange.Net/releases/

View File

@ -1,11 +1,9 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using NUnit.Framework; using NUnit.Framework;
using NUnit.Framework.Legacy; using NUnit.Framework.Legacy;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -18,9 +16,9 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestBasicErrorCallResult() public void TestBasicErrorCallResult()
{ {
var result = new CallResult(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult(new ServerError("TestError"));
ClassicAssert.AreSame(result.Error.ErrorCode, "TestError"); ClassicAssert.AreSame(result.Error.Message, "TestError");
ClassicAssert.IsFalse(result); ClassicAssert.IsFalse(result);
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
} }
@ -38,9 +36,9 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestCallResultError() public void TestCallResultError()
{ {
var result = new CallResult<object>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult<object>(new ServerError("TestError"));
ClassicAssert.AreSame(result.Error.ErrorCode, "TestError"); ClassicAssert.AreSame(result.Error.Message, "TestError");
ClassicAssert.IsNull(result.Data); ClassicAssert.IsNull(result.Data);
ClassicAssert.IsFalse(result); ClassicAssert.IsFalse(result);
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
@ -73,11 +71,11 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestCallResultErrorAs() public void TestCallResultErrorAs()
{ {
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult<TestObjectResult>(new ServerError("TestError"));
var asResult = result.As<TestObject2>(default); var asResult = result.As<TestObject2>(default);
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError"); ClassicAssert.AreSame(asResult.Error.Message, "TestError");
ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success); ClassicAssert.IsFalse(asResult.Success);
@ -86,11 +84,11 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestCallResultErrorAsError() public void TestCallResultErrorAsError()
{ {
var result = new CallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new CallResult<TestObjectResult>(new ServerError("TestError"));
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown)); var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2"); ClassicAssert.AreSame(asResult.Error.Message, "TestError2");
ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success); ClassicAssert.IsFalse(asResult.Success);
@ -99,11 +97,11 @@ namespace CryptoExchange.Net.UnitTests
[Test] [Test]
public void TestWebCallResultErrorAsError() public void TestWebCallResultErrorAsError()
{ {
var result = new WebCallResult<TestObjectResult>(new ServerError("TestError", ErrorInfo.Unknown)); var result = new WebCallResult<TestObjectResult>(new ServerError("TestError"));
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown)); var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
ClassicAssert.AreSame(asResult.Error.ErrorCode, "TestError2"); ClassicAssert.AreSame(asResult.Error.Message, "TestError2");
ClassicAssert.IsNull(asResult.Data); ClassicAssert.IsNull(asResult.Data);
ClassicAssert.IsFalse(asResult); ClassicAssert.IsFalse(asResult);
ClassicAssert.IsFalse(asResult.Success); ClassicAssert.IsFalse(asResult.Success);
@ -114,7 +112,6 @@ namespace CryptoExchange.Net.UnitTests
{ {
var result = new WebCallResult<TestObjectResult>( var result = new WebCallResult<TestObjectResult>(
System.Net.HttpStatusCode.OK, System.Net.HttpStatusCode.OK,
HttpVersion.Version11,
new KeyValuePair<string, string[]>[0], new KeyValuePair<string, string[]>[0],
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
null, null,
@ -127,10 +124,10 @@ namespace CryptoExchange.Net.UnitTests
ResultDataSource.Server, ResultDataSource.Server,
new TestObjectResult(), new TestObjectResult(),
null); null);
var asResult = result.AsError<TestObject2>(new ServerError("TestError2", ErrorInfo.Unknown)); var asResult = result.AsError<TestObject2>(new ServerError("TestError2"));
ClassicAssert.IsNotNull(asResult.Error); ClassicAssert.IsNotNull(asResult.Error);
Assert.That(asResult.Error.ErrorCode == "TestError2"); Assert.That(asResult.Error.Message == "TestError2");
Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK); Assert.That(asResult.ResponseStatusCode == System.Net.HttpStatusCode.OK);
Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1)); Assert.That(asResult.ResponseTime == TimeSpan.FromSeconds(1));
Assert.That(asResult.RequestUrl == "https://test.com/api"); Assert.That(asResult.RequestUrl == "https://test.com/api");
@ -145,7 +142,6 @@ namespace CryptoExchange.Net.UnitTests
{ {
var result = new WebCallResult<TestObjectResult>( var result = new WebCallResult<TestObjectResult>(
System.Net.HttpStatusCode.OK, System.Net.HttpStatusCode.OK,
HttpVersion.Version11,
new KeyValuePair<string, string[]>[0], new KeyValuePair<string, string[]>[0],
TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1),
null, null,

View File

@ -96,7 +96,7 @@ namespace CryptoExchange.Net.UnitTests
ClassicAssert.IsFalse(result.Success); ClassicAssert.IsFalse(result.Success);
Assert.That(result.Error != null); Assert.That(result.Error != null);
Assert.That(result.Error is ServerError); Assert.That(result.Error is ServerError);
Assert.That(result.Error.ErrorCode == "123"); Assert.That(result.Error.Code == 123);
Assert.That(result.Error.Message == "Invalid request"); Assert.That(result.Error.Message == "Invalid request");
} }
@ -182,7 +182,7 @@ namespace CryptoExchange.Net.UnitTests
[TestCase("/sapi/test1", true)] [TestCase("/sapi/test1", true)]
[TestCase("/sapi/test2", true)] [TestCase("/sapi/test2", true)]
[TestCase("/api/test1", false)] [TestCase("/api/test1", false)]
[TestCase("sapi/test1", true)] [TestCase("sapi/test1", false)]
[TestCase("/sapi/", true)] [TestCase("/sapi/", true)]
public async Task PartialEndpointRateLimiterEndpoints(string endpoint, bool expectLimiting) public async Task PartialEndpointRateLimiterEndpoints(string endpoint, bool expectLimiting)
{ {

View File

@ -1,5 +1,4 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.Sockets; using CryptoExchange.Net.Sockets;
using System; using System;
@ -41,7 +40,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
{ {
if (!message.Data.Status.Equals("confirmed", StringComparison.OrdinalIgnoreCase)) if (!message.Data.Status.Equals("confirmed", StringComparison.OrdinalIgnoreCase))
{ {
return new CallResult<SubResponse>(new ServerError(ErrorInfo.Unknown with { Message = message.Data.Status })); return new CallResult<SubResponse>(new ServerError(message.Data.Status));
} }
return message.ToCallResult(); return message.ToCallResult();

View File

@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
return new CallResult(null); return new CallResult(null);
} }
protected override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1); public override Query GetSubQuery(SocketConnection connection) => new TestQuery("sub", new object(), false, 1);
protected override Query GetUnsubQuery(SocketConnection connection) => new TestQuery("unsub", new object(), false, 1); public override Query GetUnsubQuery() => new TestQuery("unsub", new object(), false, 1);
} }
} }

View File

@ -28,7 +28,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations.Sockets
return new CallResult(null); return new CallResult(null);
} }
protected override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1); public override Query GetSubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "subscribe", false, 1);
protected override Query GetUnsubQuery(SocketConnection connection) => new TestChannelQuery(_channel, "unsubscribe", false, 1); public override Query GetUnsubQuery() => new TestChannelQuery(_channel, "unsubscribe", false, 1);
} }
} }

View File

@ -10,7 +10,6 @@ using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Converters.SystemTextJson; using CryptoExchange.Net.Converters.SystemTextJson;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using CryptoExchange.Net.UnitTests.TestImplementations; using CryptoExchange.Net.UnitTests.TestImplementations;
@ -56,7 +55,7 @@ namespace CryptoExchange.Net.UnitTests
var accessor = CreateAccessor(); var accessor = CreateAccessor();
var valid = accessor.Read(stream, true).Result; var valid = accessor.Read(stream, true).Result;
if (!valid) if (!valid)
return new CallResult<T>(new ServerError(ErrorInfo.Unknown with { Message = data })); return new CallResult<T>(new ServerError(data));
var deserializeResult = accessor.Deserialize<T>(); var deserializeResult = accessor.Deserialize<T>();
return deserializeResult; return deserializeResult;
@ -78,7 +77,7 @@ namespace CryptoExchange.Net.UnitTests
{ {
} }
public override void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig) 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)
{ {
} }

View File

@ -18,7 +18,6 @@ using Microsoft.Extensions.Options;
using System.Linq; using System.Linq;
using CryptoExchange.Net.Converters.SystemTextJson; using CryptoExchange.Net.Converters.SystemTextJson;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using CryptoExchange.Net.Objects.Errors;
namespace CryptoExchange.Net.UnitTests.TestImplementations namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
@ -60,8 +59,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetHeaders()).Returns(() => headers.ToArray()); request.Setup(c => c.GetHeaders()).Returns(() => headers.ToArray());
var factory = Mock.Get(Api1.RequestFactory); var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => .Callback<HttpMethod, Uri, int>((method, uri, id) =>
{ {
request.Setup(a => a.Uri).Returns(uri); request.Setup(a => a.Uri).Returns(uri);
request.Setup(a => a.Method).Returns(method); request.Setup(a => a.Method).Returns(method);
@ -69,8 +68,8 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
.Returns(request.Object); .Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory); factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => .Callback<HttpMethod, Uri, int>((method, uri, id) =>
{ {
request.Setup(a => a.Uri).Returns(uri); request.Setup(a => a.Uri).Returns(uri);
request.Setup(a => a.Method).Returns(method); request.Setup(a => a.Method).Returns(method);
@ -90,12 +89,12 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we); request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Throws(we);
var factory = Mock.Get(Api1.RequestFactory); var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Returns(request.Object); .Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory); factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Returns(request.Object); .Returns(request.Object);
} }
@ -118,13 +117,13 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
request.Setup(c => c.GetHeaders()).Returns(headers.ToArray()); request.Setup(c => c.GetHeaders()).Returns(headers.ToArray());
var factory = Mock.Get(Api1.RequestFactory); var factory = Mock.Get(Api1.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => request.Setup(a => a.Uri).Returns(uri)) .Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
.Returns(request.Object); .Returns(request.Object);
factory = Mock.Get(Api2.RequestFactory); factory = Mock.Get(Api2.RequestFactory);
factory.Setup(c => c.Create(It.IsAny<Version>(), It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>())) factory.Setup(c => c.Create(It.IsAny<HttpMethod>(), It.IsAny<Uri>(), It.IsAny<int>()))
.Callback<Version, HttpMethod, Uri, int>((version, method, uri, id) => request.Setup(a => a.Uri).Returns(uri)) .Callback<HttpMethod, Uri, int>((method, uri, id) => request.Setup(a => a.Uri).Returns(uri))
.Returns(request.Object); .Returns(request.Object);
} }
} }
@ -198,7 +197,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
{ {
var errorData = accessor.Deserialize<TestError>(); var errorData = accessor.Deserialize<TestError>();
return new ServerError(errorData.Data.ErrorCode, GetErrorInfo(errorData.Data.ErrorCode, errorData.Data.ErrorMessage)); return new ServerError(errorData.Data.ErrorCode, errorData.Data.ErrorMessage);
} }
public override TimeSpan? GetTimeOffset() public override TimeSpan? GetTimeOffset()

View File

@ -51,11 +51,30 @@ namespace CryptoExchange.Net.Authentication
} }
/// <summary> /// <summary>
/// Authenticate a request /// Authenticate a request. Output parameters should include the providedParameters input
/// </summary> /// </summary>
/// <param name="apiClient">The Api client sending the request</param> /// <param name="apiClient">The Api client sending the request</param>
/// <param name="requestConfig">The request configuration</param> /// <param name="uri">The uri for the request</param>
public abstract void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig); /// <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
);
/// <summary> /// <summary>
/// SHA256 sign the data and return the bytes /// SHA256 sign the data and return the bytes

View File

@ -1,9 +1,7 @@
using System; using System;
using System.Collections.Generic;
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.SharedApis;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -56,11 +54,6 @@ namespace CryptoExchange.Net.Clients
/// </summary> /// </summary>
public ExchangeOptions ClientOptions { get; } public ExchangeOptions ClientOptions { get; }
/// <summary>
/// Mapping of a response code to known error types
/// </summary>
protected internal virtual ErrorMapping ErrorMapping { get; } = new ErrorMapping([]);
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -94,16 +87,6 @@ namespace CryptoExchange.Net.Clients
/// <inheritdoc /> /// <inheritdoc />
public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null); public abstract string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null);
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(int code, string? message = null) => GetErrorInfo(code.ToString(), message);
/// <summary>
/// Get error info for a response code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message = null) => ErrorMapping.GetErrorInfo(code.ToString(), message);
/// <inheritdoc /> /// <inheritdoc />
public void SetApiCredentials<T>(T credentials) where T : ApiCredentials public void SetApiCredentials<T>(T credentials) where T : ApiCredentials
{ {

View File

@ -11,7 +11,6 @@ using CryptoExchange.Net.Caching;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.RateLimiting;
using CryptoExchange.Net.RateLimiting.Interfaces; using CryptoExchange.Net.RateLimiting.Interfaces;
@ -55,7 +54,7 @@ namespace CryptoExchange.Net.Clients
/// <summary> /// <summary>
/// Request headers to be sent with each request /// Request headers to be sent with each request
/// </summary> /// </summary>
protected Dictionary<string, string> StandardRequestHeaders { get; set; } = []; protected Dictionary<string, string>? StandardRequestHeaders { get; set; }
/// <summary> /// <summary>
/// Whether parameters need to be ordered /// Whether parameters need to be ordered
@ -106,7 +105,7 @@ namespace CryptoExchange.Net.Clients
options, options,
apiOptions) apiOptions)
{ {
RequestFactory.Configure(options, httpClient); RequestFactory.Configure(options.Proxy, options.RequestTimeout, httpClient);
} }
/// <summary> /// <summary>
@ -239,7 +238,7 @@ namespace CryptoExchange.Net.Clients
additionalHeaders); additionalHeaders);
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]"))); _logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
TotalRequestsMade++; TotalRequestsMade++;
var result = await GetResponseAsync<T>(definition, request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false); var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
if (result.Error is not CancellationRequestedError) if (result.Error is not CancellationRequestedError)
{ {
var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]"; var originalData = OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]";
@ -364,58 +363,74 @@ namespace CryptoExchange.Net.Clients
ParameterCollection? bodyParameters, ParameterCollection? bodyParameters,
Dictionary<string, string>? additionalHeaders) Dictionary<string, string>? additionalHeaders)
{ {
var requestConfiguration = new RestRequestConfiguration( var uriParams = uriParameters == null ? null : CreateParameterDictionary(uriParameters);
definition, var bodyParams = bodyParameters == null ? null : CreateParameterDictionary(bodyParameters);
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);
try 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)
{ {
AuthenticationProvider?.ProcessRequest(this, requestConfiguration); try
} {
catch (Exception ex) AuthenticationProvider.AuthenticateRequest(
{ this,
throw new Exception("Failed to authenticate request, make sure your API credentials are correct", ex); 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);
}
} }
var queryString = requestConfiguration.GetQueryString(true); // Add the auth parameters to the uri, start with a new URI to be able to sort the parameters including the auth parameters
if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?")) if (uriParams != null)
queryString = $"?{queryString}"; uri = uri.SetParameters(uriParams, arraySerialization);
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString); var request = RequestFactory.Create(definition.Method, uri, requestId);
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
request.Accept = Constants.JsonContentHeader; request.Accept = Constants.JsonContentHeader;
foreach (var header in requestConfiguration.Headers) if (headers != null)
request.AddHeader(header.Key, header.Value);
foreach (var header in StandardRequestHeaders)
{ {
// Only add it if it isn't overwritten foreach (var header in headers)
if (!requestConfiguration.Headers.ContainsKey(header.Key))
request.AddHeader(header.Key, header.Value); request.AddHeader(header.Key, header.Value);
} }
if (requestConfiguration.ParameterPosition == HttpMethodParameterPosition.InBody) if (additionalHeaders != null)
{ {
var contentType = requestConfiguration.BodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader; foreach (var header in additionalHeaders)
var bodyContent = requestConfiguration.GetBodyContent(); request.AddHeader(header.Key, header.Value);
if (bodyContent != null) }
if (StandardRequestHeaders != null)
{
foreach (var header in StandardRequestHeaders)
{ {
request.SetContent(bodyContent, contentType); // Only add it if it isn't overwritten
if (additionalHeaders?.ContainsKey(header.Key) != true)
request.AddHeader(header.Key, header.Value);
} }
}
if (parameterPosition == HttpMethodParameterPosition.InBody)
{
var contentType = bodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
if (bodyParams != null && bodyParams.Count != 0)
WriteParamBody(request, bodyParams, contentType);
else 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; return request;
@ -424,13 +439,11 @@ namespace CryptoExchange.Net.Clients
/// <summary> /// <summary>
/// Executes the request and returns the result deserialized into the type parameter class /// Executes the request and returns the result deserialized into the type parameter class
/// </summary> /// </summary>
/// <param name="requestDefinition">The request definition</param>
/// <param name="request">The request object to execute</param> /// <param name="request">The request object to execute</param>
/// <param name="gate">The ratelimit gate used</param> /// <param name="gate">The ratelimit gate used</param>
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
/// <returns></returns> /// <returns></returns>
protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>( protected virtual async Task<WebCallResult<T>> GetResponseAsync<T>(
RequestDefinition requestDefinition,
IRequest request, IRequest request,
IRateLimitGate? gate, IRateLimitGate? gate,
CancellationToken cancellationToken) CancellationToken cancellationToken)
@ -443,11 +456,14 @@ namespace CryptoExchange.Net.Clients
{ {
response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false); response = await request.GetResponseAsync(cancellationToken).ConfigureAwait(false);
sw.Stop(); sw.Stop();
var statusCode = response.StatusCode;
var headers = response.ResponseHeaders;
var responseLength = response.ContentLength;
responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false); responseStream = await response.GetResponseStreamAsync().ConfigureAwait(false);
var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData; var outputOriginalData = ApiOptions.OutputOriginalData ?? ClientOptions.OutputOriginalData;
accessor = CreateAccessor(); accessor = CreateAccessor();
if (!response.IsSuccessStatusCode && !requestDefinition.TryParseOnNonSuccess) if (!response.IsSuccessStatusCode)
{ {
// Error response // Error response
var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false); var readResult = await accessor.Read(responseStream, true).ConfigureAwait(false);
@ -472,22 +488,23 @@ namespace CryptoExchange.Net.Clients
if (error.Code == null || error.Code == 0) if (error.Code == null || error.Code == 0)
error.Code = (int)response.StatusCode; error.Code = (int)response.StatusCode;
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, 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, default, error!);
} }
var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false); var valid = await accessor.Read(responseStream, outputOriginalData).ConfigureAwait(false);
if (typeof(T) == typeof(object)) if (typeof(T) == typeof(object))
// Success status code and expected empty response, assume it's correct // Success status code and expected empty response, assume it's correct
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null); return new WebCallResult<T>(statusCode, headers, sw.Elapsed, 0, accessor.OriginalDataAvailable ? accessor.GetOriginalString() : "[Data only available when OutputOriginal = true in client options]", request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, null);
if (!valid) if (!valid)
{ {
// Invalid json // Invalid json
return new WebCallResult<T>(response.StatusCode, response.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, OutputOriginalData ? accessor.GetOriginalString() : null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, valid.Error); var error = new DeserializeError("Failed to parse response: " + valid.Error!.Message, valid.Error.Exception);
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, error);
} }
// Json response received // Json response received
var parsedError = TryParseError(requestDefinition, response.ResponseHeaders, accessor); var parsedError = TryParseError(response.ResponseHeaders, accessor);
if (parsedError != null) if (parsedError != null)
{ {
if (parsedError is ServerRateLimitError rateError) if (parsedError is ServerRateLimitError rateError)
@ -500,55 +517,30 @@ namespace CryptoExchange.Net.Clients
} }
// 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.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, 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.HttpVersion, response.ResponseHeaders, sw.Elapsed, response.ContentLength, 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);
} }
catch (HttpRequestException requestException) catch (HttpRequestException requestException)
{ {
// Request exception, can't reach server for instance // Request exception, can't reach server for instance
var error = new WebError(requestException.Message, requestException); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new WebError(requestException.Message, exception: requestException));
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
} }
catch (OperationCanceledException canceledException) catch (OperationCanceledException canceledException)
{ {
if (cancellationToken != default && canceledException.CancellationToken == cancellationToken) if (cancellationToken != default && canceledException.CancellationToken == cancellationToken)
{ {
// Cancellation token canceled by caller // Cancellation token canceled by caller
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException)); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new CancellationRequestedError(canceledException));
} }
else else
{ {
// Request timed out // Request timed out
var error = new WebError($"Request timed out", exception: canceledException); return new WebCallResult<T>(null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, new WebError($"Request timed out", exception: canceledException));
error.ErrorType = ErrorType.Timeout;
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
} }
} }
catch (ArgumentException argumentException)
{
if (argumentException.Message.StartsWith("Only HTTP/"))
{
// Unsupported HTTP version error .net framework
var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version {request.HttpVersion}: " + argumentException.Message);
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
}
throw;
}
catch (NotSupportedException notSupportedException)
{
if (notSupportedException.Message.StartsWith("Request version value must be one of"))
{
// Unsupported HTTP version error dotnet code
var error = ArgumentError.Invalid(nameof(RestExchangeOptions.HttpVersion), $"Invalid HTTP version {request.HttpVersion}: " + notSupportedException.Message);
return new WebCallResult<T>(null, null, null, sw.Elapsed, null, null, request.RequestId, request.Uri.ToString(), request.Content, request.Method, request.GetHeaders(), ResultDataSource.Server, default, error);
}
throw;
}
finally finally
{ {
accessor?.Clear(); accessor?.Clear();
@ -562,11 +554,10 @@ namespace CryptoExchange.Net.Clients
/// 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="requestDefinition">Request definition</param>
/// <param name="accessor">Data accessor</param> /// <param name="accessor">Data accessor</param>
/// <param name="responseHeaders">The response headers</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 Error? TryParseError(RequestDefinition requestDefinition, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor) => null; protected virtual Error? TryParseError(KeyValuePair<string, 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.
@ -642,7 +633,7 @@ namespace CryptoExchange.Net.Clients
/// <returns></returns> /// <returns></returns>
protected virtual Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor, Exception? exception) protected virtual Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor, Exception? exception)
{ {
return new ServerError(ErrorInfo.Unknown, exception); return new ServerError(null, "Unknown request error", exception);
} }
/// <summary> /// <summary>
@ -693,21 +684,21 @@ namespace CryptoExchange.Net.Clients
{ {
base.SetOptions(options); base.SetOptions(options);
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout, ClientOptions.HttpKeepAliveInterval); RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout);
} }
internal async Task<WebCallResult<bool>> SyncTimeAsync() internal async Task<WebCallResult<bool>> SyncTimeAsync()
{ {
var timeSyncParams = GetTimeSyncInfo(); var timeSyncParams = GetTimeSyncInfo();
if (timeSyncParams == null) if (timeSyncParams == null)
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false)) if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
{ {
if (!timeSyncParams.SyncTime || DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval) if (!timeSyncParams.SyncTime || DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval)
{ {
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
} }
var localTime = DateTime.UtcNow; var localTime = DateTime.UtcNow;
@ -736,7 +727,7 @@ namespace CryptoExchange.Net.Clients
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
} }
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, true, null);
} }
private bool ShouldCache(RequestDefinition definition) private bool ShouldCache(RequestDefinition definition)

View File

@ -1,7 +1,6 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.RateLimiting;
@ -266,11 +265,11 @@ namespace CryptoExchange.Net.Clients
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
_logger.HasBeenPausedCantSubscribeAtThisMoment(socketConnection.SocketId); _logger.HasBeenPausedCantSubscribeAtThisMoment(socketConnection.SocketId);
return new CallResult<UpdateSubscription>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused"))); return new CallResult<UpdateSubscription>(new ServerError("Socket is paused"));
} }
var waitEvent = new AsyncResetEvent(false); var waitEvent = new AsyncResetEvent(false);
var subQuery = subscription.CreateSubscriptionQuery(socketConnection); var subQuery = subscription.GetSubQuery(socketConnection);
if (subQuery != null) if (subQuery != null)
{ {
// Send the request and wait for answer // Send the request and wait for answer
@ -369,7 +368,7 @@ namespace CryptoExchange.Net.Clients
if (socketConnection.PausedActivity) if (socketConnection.PausedActivity)
{ {
_logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId); _logger.HasBeenPausedCantSendQueryAtThisMoment(socketConnection.SocketId);
return new CallResult<THandlerResponse>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused"))); return new CallResult<THandlerResponse>(new ServerError("Socket is paused"));
} }
if (ct.IsCancellationRequested) if (ct.IsCancellationRequested)

View File

@ -60,12 +60,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
} }
catch (JsonException ex) catch (JsonException ex)
{ {
var info = $"Json deserialization failed: {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<object>(new DeserializeError(info, ex)); return new CallResult<object>(new DeserializeError(info, ex));
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<object>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); var info = $"Deserialize unknown Exception: {ex.Message}";
return new CallResult<object>(new DeserializeError(info, ex));
} }
} }
@ -86,12 +87,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
} }
catch (JsonException ex) catch (JsonException ex)
{ {
var info = $"Json deserialization failed: {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, ex)); return new CallResult<T>(new DeserializeError(info, ex));
} }
catch (Exception ex) catch (Exception ex)
{ {
return new CallResult<T>(new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); var info = $"Unknown exception: {ex.Message}";
return new CallResult<T>(new DeserializeError(info, ex));
} }
} }
@ -284,7 +286,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{ {
// Not a json message // Not a json message
IsValid = false; IsValid = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex));
} }
} }
@ -336,7 +338,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{ {
// Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow // Value doesn't start with `{` or `[`, prevent deserialization attempt as it's slow
IsValid = false; IsValid = false;
return new CallResult(new DeserializeError("Not a json value")); return new CallResult(new ServerError("Not a json value"));
} }
_document = JsonDocument.Parse(data); _document = JsonDocument.Parse(data);
@ -347,7 +349,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{ {
// Not a json message // Not a json message
IsValid = false; IsValid = false;
return new CallResult(new DeserializeError($"Json deserialization failed: {ex.Message}", ex)); return new CallResult(new DeserializeError("JsonError: " + ex.Message, ex));
} }
} }

View File

@ -6,11 +6,11 @@
<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>9.7.0</PackageVersion> <PackageVersion>9.3.1</PackageVersion>
<AssemblyVersion>9.7.0</AssemblyVersion> <AssemblyVersion>9.3.1</AssemblyVersion>
<FileVersion>9.7.0</FileVersion> <FileVersion>9.3.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;CryptoExchange.Net</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>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl> <RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>

View File

@ -23,14 +23,14 @@ namespace CryptoExchange.Net
{ {
if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo)) if(!_symbolInfos.TryGetValue(topicId, out var exchangeInfo))
{ {
exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol)); exchangeInfo = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => new SharedSymbol(x.TradingMode, x.BaseAsset.ToUpperInvariant(), x.QuoteAsset.ToUpperInvariant(), (x as SharedFuturesSymbol)?.DeliveryTime) { SymbolName = x.Name }));
_symbolInfos.TryAdd(topicId, exchangeInfo); _symbolInfos.TryAdd(topicId, exchangeInfo);
} }
if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60)) if (DateTime.UtcNow - exchangeInfo.UpdateTime < TimeSpan.FromMinutes(60))
return; return;
_symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol)); _symbolInfos[topicId] = new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => new SharedSymbol(x.TradingMode, x.BaseAsset.ToUpperInvariant(), x.QuoteAsset.ToUpperInvariant(), (x as SharedFuturesSymbol)?.DeliveryTime) { SymbolName = x.Name }));
} }
/// <summary> /// <summary>

View File

@ -498,8 +498,8 @@ namespace CryptoExchange.Net
services.AddTransient(x => (IBookTickerSocketClient)client(x)!); services.AddTransient(x => (IBookTickerSocketClient)client(x)!);
if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T))) if (typeof(IKlineSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IKlineSocketClient)client(x)!); services.AddTransient(x => (IKlineSocketClient)client(x)!);
if (typeof(IOrderBookSocketClient).IsAssignableFrom(typeof(T))) if (typeof(IOrderBookRestClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (IOrderBookSocketClient)client(x)!); services.AddTransient(x => (IOrderBookRestClient)client(x)!);
if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T))) if (typeof(ITickerSocketClient).IsAssignableFrom(typeof(T)))
services.AddTransient(x => (ITickerSocketClient)client(x)!); services.AddTransient(x => (ITickerSocketClient)client(x)!);
if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T))) if (typeof(ITickersSocketClient).IsAssignableFrom(typeof(T)))

View File

@ -28,10 +28,6 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
Uri Uri { get; } Uri Uri { get; }
/// <summary> /// <summary>
/// HTTP protocol version
/// </summary>
Version HttpVersion { get; }
/// <summary>
/// internal request id for tracing /// internal request id for tracing
/// </summary> /// </summary>
int RequestId { get; } int RequestId { get; }

View File

@ -1,5 +1,4 @@
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using System; using System;
using System.Net.Http; using System.Net.Http;
@ -13,21 +12,25 @@ namespace CryptoExchange.Net.Interfaces
/// <summary> /// <summary>
/// Create a request for an uri /// Create a request for an uri
/// </summary> /// </summary>
IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId); /// <param name="method"></param>
/// <param name="uri"></param>
/// <param name="requestId"></param>
/// <returns></returns>
IRequest Create(HttpMethod method, Uri uri, int requestId);
/// <summary> /// <summary>
/// Configure the requests created by this factory /// Configure the requests created by this factory
/// </summary> /// </summary>
/// <param name="options">Rest client options</param> /// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param> /// <param name="httpClient">Optional shared http client instance</param>
void Configure(RestExchangeOptions options, HttpClient? httpClient = null); /// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary> /// <summary>
/// Update settings /// Update settings
/// </summary> /// </summary>
/// <param name="proxy">Proxy to use</param> /// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param> /// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpKeepAliveInterval">Http client keep alive interval</param> void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval);
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,11 +15,6 @@ namespace CryptoExchange.Net.Interfaces
/// </summary> /// </summary>
HttpStatusCode StatusCode { get; } HttpStatusCode StatusCode { get; }
/// <summary>
/// Http protocol version
/// </summary>
Version HttpVersion { get; }
/// <summary> /// <summary>
/// Whether the status code indicates a success status /// Whether the status code indicates a success status
/// </summary> /// </summary>

View File

@ -1,8 +1,5 @@
using CryptoExchange.Net.Objects; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
namespace CryptoExchange.Net namespace CryptoExchange.Net
@ -46,58 +43,5 @@ namespace CryptoExchange.Net
return clientOrderId; return clientOrderId;
} }
/// <summary>
/// Create a new HttpMessageHandler instance
/// </summary>
public static HttpMessageHandler CreateHttpClientMessageHandler(ApiProxy? proxy, TimeSpan? keepAliveInterval)
{
#if NET5_0_OR_GREATER
var socketHandler = new SocketsHttpHandler();
try
{
if (keepAliveInterval != null && keepAliveInterval != TimeSpan.Zero)
{
socketHandler.KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always;
socketHandler.KeepAlivePingDelay = keepAliveInterval.Value;
socketHandler.KeepAlivePingTimeout = TimeSpan.FromSeconds(10);
}
socketHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
socketHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
if (proxy != null)
{
socketHandler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
return socketHandler;
#else
var httpHandler = new HttpClientHandler();
try
{
httpHandler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
httpHandler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
if (proxy != null)
{
httpHandler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
return httpHandler;
#endif
}
} }
} }

View File

@ -206,11 +206,6 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public HttpMethod? RequestMethod { get; set; } public HttpMethod? RequestMethod { get; set; }
/// <summary>
/// HTTP protocol version
/// </summary>
public Version? HttpVersion { get; set; }
/// <summary> /// <summary>
/// The headers sent with the request /// The headers sent with the request
/// </summary> /// </summary>
@ -256,7 +251,6 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public WebCallResult( public WebCallResult(
HttpStatusCode? code, HttpStatusCode? code,
Version? httpVersion,
KeyValuePair<string, string[]>[]? responseHeaders, KeyValuePair<string, string[]>[]? responseHeaders,
TimeSpan? responseTime, TimeSpan? responseTime,
string? originalData, string? originalData,
@ -268,7 +262,6 @@ namespace CryptoExchange.Net.Objects
Error? error) : base(error) Error? error) : base(error)
{ {
ResponseStatusCode = code; ResponseStatusCode = code;
HttpVersion = httpVersion;
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseTime = responseTime; ResponseTime = responseTime;
RequestId = requestId; RequestId = requestId;
@ -293,7 +286,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public WebCallResult AsError(Error error) public WebCallResult AsError(Error error)
{ {
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
} }
/// <summary> /// <summary>
@ -304,7 +297,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public WebCallResult<K> As<K>([AllowNull] K data) public WebCallResult<K> As<K>([AllowNull] K data)
{ {
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, data, Error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, data, Error);
} }
/// <summary> /// <summary>
@ -341,7 +334,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public WebCallResult<K> AsError<K>(Error error) public WebCallResult<K> AsError<K>(Error error)
{ {
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, default, error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, 0, null, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Server, default, error);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -362,11 +355,6 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public HttpMethod? RequestMethod { get; set; } public HttpMethod? RequestMethod { get; set; }
/// <summary>
/// HTTP protocol version
/// </summary>
public Version? HttpVersion { get; set; }
/// <summary> /// <summary>
/// The headers sent with the request /// The headers sent with the request
/// </summary> /// </summary>
@ -415,9 +403,21 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// Create a new result /// Create a new result
/// </summary> /// </summary>
/// <param name="code"></param>
/// <param name="responseHeaders"></param>
/// <param name="responseTime"></param>
/// <param name="responseLength"></param>
/// <param name="originalData"></param>
/// <param name="requestId"></param>
/// <param name="requestUrl"></param>
/// <param name="requestBody"></param>
/// <param name="requestMethod"></param>
/// <param name="requestHeaders"></param>
/// <param name="dataSource"></param>
/// <param name="data"></param>
/// <param name="error"></param>
public WebCallResult( public WebCallResult(
HttpStatusCode? code, HttpStatusCode? code,
Version? httpVersion,
KeyValuePair<string, string[]>[]? responseHeaders, KeyValuePair<string, string[]>[]? responseHeaders,
TimeSpan? responseTime, TimeSpan? responseTime,
long? responseLength, long? responseLength,
@ -431,7 +431,6 @@ namespace CryptoExchange.Net.Objects
[AllowNull] T data, [AllowNull] T data,
Error? error) : base(data, originalData, error) Error? error) : base(data, originalData, error)
{ {
HttpVersion = httpVersion;
ResponseStatusCode = code; ResponseStatusCode = code;
ResponseHeaders = responseHeaders; ResponseHeaders = responseHeaders;
ResponseTime = responseTime; ResponseTime = responseTime;
@ -451,7 +450,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult AsDataless() public new WebCallResult AsDataless()
{ {
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, Error);
} }
/// <summary> /// <summary>
/// Copy as a dataless result /// Copy as a dataless result
@ -459,14 +458,14 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult AsDatalessError(Error error) public new WebCallResult AsDatalessError(Error error)
{ {
return new WebCallResult(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error); return new WebCallResult(ResponseStatusCode, ResponseHeaders, ResponseTime, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, error);
} }
/// <summary> /// <summary>
/// Create a new error result /// Create a new error result
/// </summary> /// </summary>
/// <param name="error">The error</param> /// <param name="error">The error</param>
public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, default, error) { } public WebCallResult(Error? error) : this(null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, default, error) { }
/// <summary> /// <summary>
/// Copy the WebCallResult to a new data type /// Copy the WebCallResult to a new data type
@ -476,7 +475,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> As<K>([AllowNull] K data) public new WebCallResult<K> As<K>([AllowNull] K data)
{ {
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error);
} }
/// <summary> /// <summary>
@ -487,7 +486,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> AsError<K>(Error error) public new WebCallResult<K> AsError<K>(Error error)
{ {
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, default, error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, default, error);
} }
/// <summary> /// <summary>
@ -499,7 +498,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public new WebCallResult<K> AsErrorWithData<K>(Error error, K data) public new WebCallResult<K> AsErrorWithData<K>(Error error, K data)
{ {
return new WebCallResult<K>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, error); return new WebCallResult<K>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, error);
} }
/// <summary> /// <summary>
@ -570,7 +569,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
internal WebCallResult<T> Cached() internal WebCallResult<T> Cached()
{ {
return new WebCallResult<T>(ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Cache, Data, Error); return new WebCallResult<T>(ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, ResultDataSource.Cache, Data, Error);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -251,20 +251,4 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
DEX DEX
} }
/// <summary>
/// Timeout behavior for queries
/// </summary>
public enum TimeoutBehavior
{
/// <summary>
/// Fail the request
/// </summary>
Fail,
/// <summary>
/// Mark the query as successful
/// </summary>
Succeed
}
} }

View File

@ -1,5 +1,4 @@
using CryptoExchange.Net.Objects.Errors; using System;
using System;
namespace CryptoExchange.Net.Objects namespace CryptoExchange.Net.Objects
{ {
@ -8,52 +7,15 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public abstract class Error public abstract class Error
{ {
private int? _code;
/// <summary> /// <summary>
/// The int error code the server returned; or the http status code int value if there was no error code.<br /> /// The error code from the server
/// <br />
/// <i>Note:</i><br />
/// The <see cref="ErrorCode"/> property should be used for more generic error checking; it might contain a string error code if the server does not return an int code.
/// </summary> /// </summary>
public int? Code public int? Code { get; set; }
{
get
{
if (_code.HasValue)
return _code;
return int.TryParse(ErrorCode, out var r) ? r : null;
}
set
{
_code = value;
}
}
/// <summary> /// <summary>
/// The error code returned by the server /// The message for the error that occurred
/// </summary> /// </summary>
public string? ErrorCode { get; set; } public string Message { get; set; }
/// <summary>
/// The error description
/// </summary>
public string? ErrorDescription { get; set; }
/// <summary>
/// Error type
/// </summary>
public ErrorType ErrorType { get; set; }
/// <summary>
/// Whether the error is transient and can be retried
/// </summary>
public bool IsTransient { get; set; }
/// <summary>
/// The server message for the error that occurred
/// </summary>
public string? Message { get; set; }
/// <summary> /// <summary>
/// Underlying exception /// Underlying exception
@ -63,13 +25,10 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
protected Error(string? errorCode, ErrorInfo errorInfo, Exception? exception) protected Error (int? code, string message, Exception? exception)
{ {
ErrorCode = errorCode; Code = code;
ErrorType = errorInfo.ErrorType; Message = message;
Message = errorInfo.Message;
ErrorDescription = errorInfo.ErrorDescription;
IsTransient = errorInfo.IsTransient;
Exception = exception; Exception = exception;
} }
@ -79,7 +38,7 @@ namespace CryptoExchange.Net.Objects
/// <returns></returns> /// <returns></returns>
public override string ToString() public override string ToString()
{ {
return ErrorCode != null ? $"[{GetType().Name}.{ErrorType}] {ErrorCode}: {Message ?? ErrorDescription}" : $"[{GetType().Name}.{ErrorType}] {Message ?? ErrorDescription}"; return Code != null ? $"[{GetType().Name}] {Code}: {Message}" : $"[{GetType().Name}] {Message}";
} }
} }
@ -89,24 +48,19 @@ namespace CryptoExchange.Net.Objects
public class CantConnectError : Error public class CantConnectError : Error
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.UnableToConnect, false, "Can't connect to the server"); public CantConnectError() : base(null, "Can't connect to the server", null) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public CantConnectError() : base(null, _errorInfo, null) { } public CantConnectError(Exception? exception) : base(null, "Can't connect to the server", exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public CantConnectError(Exception? exception) : base(null, _errorInfo, exception) { } protected CantConnectError(int? code, string message, Exception? exception) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected CantConnectError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
} }
/// <summary> /// <summary>
@ -115,19 +69,14 @@ namespace CryptoExchange.Net.Objects
public class NoApiCredentialsError : Error public class NoApiCredentialsError : Error
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.MissingCredentials, false, "No credentials provided for private endpoint"); public NoApiCredentialsError() : base(null, "No credentials provided for private endpoint", null) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public NoApiCredentialsError() : base(null, _errorInfo, null) { } protected NoApiCredentialsError(int? code, string message, Exception? exception) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected NoApiCredentialsError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
} }
/// <summary> /// <summary>
@ -138,19 +87,12 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public ServerError(ErrorInfo errorInfo, Exception? exception = null) public ServerError(string message) : base(null, message, null) { }
: base(null, errorInfo, exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public ServerError(int errorCode, ErrorInfo errorInfo, Exception? exception = null) public ServerError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
: this(errorCode.ToString(), errorInfo, exception) { }
/// <summary>
/// ctor
/// </summary>
public ServerError(string errorCode, ErrorInfo errorInfo, Exception? exception = null) : base(errorCode, errorInfo, exception) { }
} }
/// <summary> /// <summary>
@ -159,30 +101,14 @@ namespace CryptoExchange.Net.Objects
public class WebError : Error public class WebError : Error
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.NetworkError, true, "Failed to complete the request to the server due to a network error"); public WebError(string message, Exception? exception = null) : base(null, message, exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public WebError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { } public WebError(int code, string message, Exception? exception = null) : base(code, message, exception) { }
}
/// <summary>
/// Timeout error waiting for a response from the server
/// </summary>
public class TimeoutError : Error
{
/// <summary>
/// Default error info
/// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.Timeout, false, "Failed to receive a response from the server in time");
/// <summary>
/// ctor
/// </summary>
public TimeoutError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { }
} }
/// <summary> /// <summary>
@ -191,14 +117,30 @@ namespace CryptoExchange.Net.Objects
public class DeserializeError : Error public class DeserializeError : Error
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.DeserializationFailed, false, "Failed to deserialize data"); public DeserializeError(string message, Exception? exception = null) : base(null, message, exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public DeserializeError(string? message = null, Exception? exception = null) : base(null, _errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { } protected DeserializeError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
}
/// <summary>
/// Unknown error
/// </summary>
public class UnknownError : Error
{
/// <summary>
/// ctor
/// </summary>
public UnknownError(string message, Exception? exception = null) : base(null, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected UnknownError(int? code, string message, Exception? exception = null): base(code, message, exception) { }
} }
/// <summary> /// <summary>
@ -207,28 +149,14 @@ namespace CryptoExchange.Net.Objects
public class ArgumentError : Error public class ArgumentError : Error
{ {
/// <summary> /// <summary>
/// Default error info for missing parameter /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _missingInfo = new ErrorInfo(ErrorType.MissingParameter, false, "Missing parameter"); public ArgumentError(string message) : base(null, "Invalid parameter: " + message, null) { }
/// <summary>
/// Default error info for invalid parameter
/// </summary>
protected static readonly ErrorInfo _invalidInfo = new ErrorInfo(ErrorType.InvalidParameter, false, "Invalid parameter");
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public static ArgumentError Missing(string parameterName, string? message = null) => new ArgumentError(_missingInfo with { Message = message == null ? $"{_missingInfo.Message} '{parameterName}'" : $"{_missingInfo.Message} '{parameterName}': {message}" }, null); protected ArgumentError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
public static ArgumentError Invalid(string parameterName, string message) => new ArgumentError(_invalidInfo with { Message = $"{_invalidInfo.Message} '{parameterName}': {message}" }, null);
/// <summary>
/// ctor
/// </summary>
protected ArgumentError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
} }
/// <summary> /// <summary>
@ -244,7 +172,7 @@ namespace CryptoExchange.Net.Objects
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
protected BaseRateLimitError(ErrorInfo errorInfo, Exception? exception) : base(null, errorInfo, exception) { } protected BaseRateLimitError(int? code, string message, Exception? exception) : base(code, message, exception) { }
} }
/// <summary> /// <summary>
@ -253,19 +181,15 @@ namespace CryptoExchange.Net.Objects
public class ClientRateLimitError : BaseRateLimitError public class ClientRateLimitError : BaseRateLimitError
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Client rate limit exceeded"); /// <param name="message"></param>
public ClientRateLimitError(string message) : base(null, "Client rate limit exceeded: " + message, null) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public ClientRateLimitError(string? message = null, Exception? exception = null) : base(_errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { } protected ClientRateLimitError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected ClientRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
} }
/// <summary> /// <summary>
@ -274,19 +198,14 @@ namespace CryptoExchange.Net.Objects
public class ServerRateLimitError : BaseRateLimitError public class ServerRateLimitError : BaseRateLimitError
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.RateLimitRequest, false, "Server rate limit exceeded"); public ServerRateLimitError(string? message = null, Exception? exception = null) : base(null, "Server rate limit exceeded" + (message?.Length > 0 ? " : " + message : null), exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public ServerRateLimitError(string? message = null, Exception? exception = null) : base(_errorInfo with { Message = (message?.Length > 0 ? _errorInfo.Message + ": " + message : _errorInfo.Message) }, exception) { } protected ServerRateLimitError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected ServerRateLimitError(ErrorInfo info, Exception? exception) : base(info, exception) { }
} }
/// <summary> /// <summary>
@ -295,19 +214,14 @@ namespace CryptoExchange.Net.Objects
public class CancellationRequestedError : Error public class CancellationRequestedError : Error
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.CancellationRequested, false, "Cancellation requested"); public CancellationRequestedError(Exception? exception = null) : base(null, "Cancellation requested", exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public CancellationRequestedError(Exception? exception = null) : base(null, _errorInfo, null) { } public CancellationRequestedError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected CancellationRequestedError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
} }
/// <summary> /// <summary>
@ -316,18 +230,13 @@ namespace CryptoExchange.Net.Objects
public class InvalidOperationError : Error public class InvalidOperationError : Error
{ {
/// <summary> /// <summary>
/// Default error info /// ctor
/// </summary> /// </summary>
protected static readonly ErrorInfo _errorInfo = new ErrorInfo(ErrorType.InvalidOperation, false, "Operation invalid"); public InvalidOperationError(string message, Exception? exception = null) : base(null, message, exception) { }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
public InvalidOperationError(string message) : base(null, _errorInfo with { Message = message }, null) { } protected InvalidOperationError(int? code, string message, Exception? exception = null) : base(code, message, exception) { }
/// <summary>
/// ctor
/// </summary>
protected InvalidOperationError(ErrorInfo info, Exception? exception) : base(null, info, exception) { }
} }
} }

View File

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors
{
/// <summary>
/// Error evaluator
/// </summary>
public class ErrorEvaluator
{
/// <summary>
/// Error code
/// </summary>
public string[] ErrorCodes { get; set; }
/// <summary>
/// Evaluation callback for determining the error type
/// </summary>
public Func<string, string?, ErrorInfo> ErrorTypeEvaluator { get; set; }
/// <summary>
/// ctor
/// </summary>
public ErrorEvaluator(string errorCode, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
ErrorCodes = [errorCode];
ErrorTypeEvaluator = errorTypeEvaluator;
}
/// <summary>
/// ctor
/// </summary>
public ErrorEvaluator(string[] errorCodes, Func<string, string?, ErrorInfo> errorTypeEvaluator)
{
ErrorCodes = errorCodes;
ErrorTypeEvaluator = errorTypeEvaluator;
}
}
}

View File

@ -1,58 +0,0 @@
using System;
namespace CryptoExchange.Net.Objects.Errors
{
/// <summary>
/// Error info
/// </summary>
public record ErrorInfo
{
/// <summary>
/// Unknown error info
/// </summary>
public static ErrorInfo Unknown { get; } = new ErrorInfo(ErrorType.Unknown, false, "Unknown error", []);
/// <summary>
/// The server error code
/// </summary>
public string[] ErrorCodes { get; set; }
/// <summary>
/// Error description
/// </summary>
public string? ErrorDescription { get; set; }
/// <summary>
/// The error type
/// </summary>
public ErrorType ErrorType { get; set; }
/// <summary>
/// Whether the error is transient and can be retried
/// </summary>
public bool IsTransient { get; set; }
/// <summary>
/// Server response message
/// </summary>
public string? Message { get; set; }
/// <summary>
/// ctor
/// </summary>
public ErrorInfo(ErrorType errorType, string description)
{
ErrorCodes = [];
ErrorType = errorType;
IsTransient = false;
ErrorDescription = description;
}
/// <summary>
/// ctor
/// </summary>
public ErrorInfo(ErrorType errorType, bool isTransient, string description, params string[] errorCodes)
{
ErrorCodes = errorCodes;
ErrorType = errorType;
IsTransient = isTransient;
ErrorDescription = description;
}
}
}

View File

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors
{
/// <summary>
/// Error mapping collection
/// </summary>
public class ErrorMapping
{
private Dictionary<string, ErrorEvaluator> _evaluators = new Dictionary<string, ErrorEvaluator>();
private Dictionary<string, ErrorInfo> _directMapping = new Dictionary<string, ErrorInfo>();
/// <summary>
/// ctor
/// </summary>
public ErrorMapping(ErrorInfo[] errorMappings, ErrorEvaluator[]? errorTypeEvaluators = null)
{
foreach (var item in errorMappings)
{
if (!item.ErrorCodes.Any())
throw new Exception("Error codes can't be null in error mapping");
foreach(var code in item.ErrorCodes!)
_directMapping.Add(code, item);
}
if (errorTypeEvaluators == null)
return;
foreach (var item in errorTypeEvaluators)
{
foreach(var code in item.ErrorCodes)
_evaluators.Add(code, item);
}
}
/// <summary>
/// Get error info for an error code
/// </summary>
public ErrorInfo GetErrorInfo(string code, string? message)
{
if (_directMapping.TryGetValue(code!, out var info))
return info with { Message = message };
if (_evaluators.TryGetValue(code!, out var eva))
return eva.ErrorTypeEvaluator.Invoke(code!, message) with { Message = message };
return ErrorInfo.Unknown with { Message = message };
}
}
}

View File

@ -1,162 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Errors
{
/// <summary>
/// Type of error
/// </summary>
public enum ErrorType
{
#region Library errors
/// <summary>
/// Failed to connect to server
/// </summary>
UnableToConnect,
/// <summary>
/// Failed to complete the request to the server
/// </summary>
NetworkError,
/// <summary>
/// No API credentials have been specified
/// </summary>
MissingCredentials,
/// <summary>
/// Invalid parameter value
/// </summary>
InvalidParameter,
/// <summary>
/// Missing parameter value
/// </summary>
MissingParameter,
/// <summary>
/// Cancellation requested by user
/// </summary>
CancellationRequested,
/// <summary>
/// Invalid operation requested
/// </summary>
InvalidOperation,
/// <summary>
/// Failed to deserialize data
/// </summary>
DeserializationFailed,
/// <summary>
/// Websocket is temporarily paused
/// </summary>
WebsocketPaused,
/// <summary>
/// Timeout while waiting for data from the order book subscription
/// </summary>
OrderBookTimeout,
/// <summary>
/// All orders failed for a multi-order operation
/// </summary>
AllOrdersFailed,
/// <summary>
/// Request timeout
/// </summary>
Timeout,
#endregion
#region Server errors
/// <summary>
/// Unknown error
/// </summary>
Unknown,
/// <summary>
/// Not authorized or insufficient permissions
/// </summary>
Unauthorized,
/// <summary>
/// Request rate limit error, too many requests
/// </summary>
RateLimitRequest,
/// <summary>
/// Connection rate limit error, too many connections
/// </summary>
RateLimitConnection,
/// <summary>
/// Subscription rate limit error, too many subscriptions
/// </summary>
RateLimitSubscription,
/// <summary>
/// Order rate limit error, too many orders
/// </summary>
RateLimitOrder,
/// <summary>
/// Request timestamp invalid
/// </summary>
InvalidTimestamp,
/// <summary>
/// Unknown symbol
/// </summary>
UnknownSymbol,
/// <summary>
/// Unknown asset
/// </summary>
UnknownAsset,
/// <summary>
/// Unknown order
/// </summary>
UnknownOrder,
/// <summary>
/// Duplicate subscription
/// </summary>
DuplicateSubscription,
/// <summary>
/// Invalid quantity
/// </summary>
InvalidQuantity,
/// <summary>
/// Invalid price
/// </summary>
InvalidPrice,
/// <summary>
/// Parameter(s) for stop or tp/sl order invalid
/// </summary>
InvalidStopParameters,
/// <summary>
/// Not enough balance to execute request
/// </summary>
InsufficientBalance,
/// <summary>
/// Client order id already in use
/// </summary>
DuplicateClientOrderId,
/// <summary>
/// Symbol is not currently trading
/// </summary>
UnavailableSymbol,
/// <summary>
/// Order rejected due to order configuration such as order type or time in force restrictions
/// </summary>
RejectedOrderConfiguration,
/// <summary>
/// There is no open position
/// </summary>
NoPosition,
/// <summary>
/// Max position reached
/// </summary>
MaxPosition,
/// <summary>
/// Error in the internal system
/// </summary>
SystemError,
/// <summary>
/// The target object is not in the correct state for an operation
/// </summary>
IncorrectState,
/// <summary>
/// Risk management error
/// </summary>
RiskError
#endregion
}
}

View File

@ -1,7 +1,5 @@
using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Authentication;
using System; using System;
using System.Net;
using System.Net.Http;
namespace CryptoExchange.Net.Objects.Options namespace CryptoExchange.Net.Objects.Options
{ {
@ -30,20 +28,6 @@ namespace CryptoExchange.Net.Objects.Options
/// </summary> /// </summary>
public TimeSpan CachingMaxAge { get; set; } = TimeSpan.FromSeconds(5); public TimeSpan CachingMaxAge { get; set; } = TimeSpan.FromSeconds(5);
/// <summary>
/// The HTTP protocol version to use, typically 2.0 or 1.1
/// </summary>
public Version HttpVersion { get; set; }
#if NET5_0_OR_GREATER
= new Version(2, 0);
#else
= new Version(1, 1);
#endif
/// <summary>
/// Http client keep alive interval for keeping connections open
/// </summary>
public TimeSpan? HttpKeepAliveInterval { get; set; } = TimeSpan.FromSeconds(15);
/// <summary> /// <summary>
/// Set the values of this options on the target options /// Set the values of this options on the target options
/// </summary> /// </summary>
@ -59,8 +43,6 @@ namespace CryptoExchange.Net.Objects.Options
item.RateLimitingBehaviour = RateLimitingBehaviour; item.RateLimitingBehaviour = RateLimitingBehaviour;
item.CachingEnabled = CachingEnabled; item.CachingEnabled = CachingEnabled;
item.CachingMaxAge = CachingMaxAge; item.CachingMaxAge = CachingMaxAge;
item.HttpVersion = HttpVersion;
item.HttpKeepAliveInterval = HttpKeepAliveInterval;
return item; return item;
} }
} }

View File

@ -62,11 +62,6 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public bool PreventCaching { get; set; } public bool PreventCaching { get; set; }
/// <summary>
/// Whether the response to this requests should attempted to be parsed even when the status indicates failure
/// </summary>
public bool TryParseOnNonSuccess { get; set; }
/// <summary> /// <summary>
/// Connection id /// Connection id
/// </summary> /// </summary>
@ -81,9 +76,6 @@ namespace CryptoExchange.Net.Objects
{ {
Path = path; Path = path;
Method = method; Method = method;
if (!Path.StartsWith("/"))
Path = $"/{Path}";
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -46,7 +46,6 @@ namespace CryptoExchange.Net.Objects
/// <param name="parameterPosition">Parameter position</param> /// <param name="parameterPosition">Parameter position</param>
/// <param name="arraySerialization">Array serialization type</param> /// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param> /// <param name="preventCaching">Prevent request caching</param>
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <returns></returns> /// <returns></returns>
public RequestDefinition GetOrCreate( public RequestDefinition GetOrCreate(
HttpMethod method, HttpMethod method,
@ -58,9 +57,8 @@ namespace CryptoExchange.Net.Objects
RequestBodyFormat? requestBodyFormat = null, RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null, bool? preventCaching = null)
bool? tryParseOnNonSuccess = null) => GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching);
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess);
/// <summary> /// <summary>
/// Get a definition if it is already in the cache or create a new definition and add it to the cache /// Get a definition if it is already in the cache or create a new definition and add it to the cache
@ -76,7 +74,6 @@ namespace CryptoExchange.Net.Objects
/// <param name="parameterPosition">Parameter position</param> /// <param name="parameterPosition">Parameter position</param>
/// <param name="arraySerialization">Array serialization type</param> /// <param name="arraySerialization">Array serialization type</param>
/// <param name="preventCaching">Prevent request caching</param> /// <param name="preventCaching">Prevent request caching</param>
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
/// <returns></returns> /// <returns></returns>
public RequestDefinition GetOrCreate( public RequestDefinition GetOrCreate(
string identifier, string identifier,
@ -89,8 +86,7 @@ namespace CryptoExchange.Net.Objects
RequestBodyFormat? requestBodyFormat = null, RequestBodyFormat? requestBodyFormat = null,
HttpMethodParameterPosition? parameterPosition = null, HttpMethodParameterPosition? parameterPosition = null,
ArrayParametersSerialization? arraySerialization = null, ArrayParametersSerialization? arraySerialization = null,
bool? preventCaching = null, bool? preventCaching = null)
bool? tryParseOnNonSuccess = null)
{ {
if (!_definitions.TryGetValue(identifier, out var def)) if (!_definitions.TryGetValue(identifier, out var def))
@ -104,8 +100,7 @@ namespace CryptoExchange.Net.Objects
ArraySerialization = arraySerialization, ArraySerialization = arraySerialization,
RequestBodyFormat = requestBodyFormat, RequestBodyFormat = requestBodyFormat,
ParameterPosition = parameterPosition, ParameterPosition = parameterPosition,
PreventCaching = preventCaching ?? false, PreventCaching = preventCaching ?? false
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false
}; };
_definitions.TryAdd(identifier, def); _definitions.TryAdd(identifier, def);
} }

View File

@ -1,124 +0,0 @@
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;
}
}
}

View File

@ -9,7 +9,6 @@ using System.Threading.Tasks;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Options; using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -550,7 +549,7 @@ namespace CryptoExchange.Net.OrderBook
return new CallResult<bool>(new CancellationRequestedError()); return new CallResult<bool>(new CancellationRequestedError());
if (DateTime.UtcNow - startWait > timeout) if (DateTime.UtcNow - startWait > timeout)
return new CallResult<bool>(new ServerError(new ErrorInfo(ErrorType.OrderBookTimeout, "Timeout while waiting for data"))); return new CallResult<bool>(new ServerError("Timeout while waiting for data"));
try try
{ {

View File

@ -50,9 +50,6 @@ namespace CryptoExchange.Net.Requests
/// <inheritdoc /> /// <inheritdoc />
public Uri Uri => _request.RequestUri!; public Uri Uri => _request.RequestUri!;
/// <inheritdoc />
public Version HttpVersion => _request.Version!;
/// <inheritdoc /> /// <inheritdoc />
public int RequestId { get; } public int RequestId { get; }
@ -84,9 +81,7 @@ namespace CryptoExchange.Net.Requests
/// <inheritdoc /> /// <inheritdoc />
public async Task<IResponse> GetResponseAsync(CancellationToken cancellationToken) public async Task<IResponse> GetResponseAsync(CancellationToken cancellationToken)
{ {
var response = await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); return new Response(await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false));
return new Response(response);
} }
} }
} }

View File

@ -3,7 +3,6 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
namespace CryptoExchange.Net.Requests namespace CryptoExchange.Net.Requests
{ {
@ -15,43 +14,54 @@ namespace CryptoExchange.Net.Requests
private HttpClient? _httpClient; private HttpClient? _httpClient;
/// <inheritdoc /> /// <inheritdoc />
public void Configure(RestExchangeOptions options, HttpClient? client = null) public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
{ {
if (client == null) if (client == null)
client = CreateClient(options.Proxy, options.RequestTimeout, options.HttpKeepAliveInterval); client = CreateClient(proxy, requestTimeout);
_httpClient = client; _httpClient = client;
} }
/// <inheritdoc /> /// <inheritdoc />
public IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId) public IRequest Create(HttpMethod method, Uri uri, int requestId)
{ {
if (_httpClient == null) if (_httpClient == null)
throw new InvalidOperationException("Cant create request before configuring http client"); throw new InvalidOperationException("Cant create request before configuring http client");
var requestMessage = new HttpRequestMessage(method, uri); return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId);
requestMessage.Version = httpRequestVersion;
#if NET5_0_OR_GREATER
requestMessage.VersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
#endif
return new Request(requestMessage, _httpClient, requestId);
} }
/// <inheritdoc /> /// <inheritdoc />
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval) public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout)
{ {
_httpClient = CreateClient(proxy, requestTimeout, httpKeepAliveInterval); _httpClient = CreateClient(proxy, requestTimeout);
} }
private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval) private static HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
{ {
var handler = LibraryHelpers.CreateHttpClientMessageHandler(proxy, httpKeepAliveInterval); var handler = new HttpClientHandler();
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
if (proxy != null)
{
handler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
var client = new HttpClient(handler) var client = new HttpClient(handler)
{ {
Timeout = requestTimeout Timeout = requestTimeout
}; };
return client; return client;
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
@ -19,9 +18,6 @@ namespace CryptoExchange.Net.Requests
/// <inheritdoc /> /// <inheritdoc />
public HttpStatusCode StatusCode => _response.StatusCode; public HttpStatusCode StatusCode => _response.StatusCode;
/// <inheritdoc />
public Version HttpVersion => _response.Version;
/// <inheritdoc /> /// <inheritdoc />
public bool IsSuccessStatusCode => _response.IsSuccessStatusCode; public bool IsSuccessStatusCode => _response.IsSuccessStatusCode;

View File

@ -48,7 +48,6 @@ namespace CryptoExchange.Net.SharedApis
WebCallResult<T> result, WebCallResult<T> result,
INextPageToken? nextPageToken = null) : INextPageToken? nextPageToken = null) :
base(result.ResponseStatusCode, base(result.ResponseStatusCode,
result.HttpVersion,
result.ResponseHeaders, result.ResponseHeaders,
result.ResponseTime, result.ResponseTime,
result.ResponseLength, result.ResponseLength,
@ -76,7 +75,6 @@ namespace CryptoExchange.Net.SharedApis
WebCallResult<T> result, WebCallResult<T> result,
INextPageToken? nextPageToken = null) : INextPageToken? nextPageToken = null) :
base(result.ResponseStatusCode, base(result.ResponseStatusCode,
result.HttpVersion,
result.ResponseHeaders, result.ResponseHeaders,
result.ResponseTime, result.ResponseTime,
result.ResponseLength, result.ResponseLength,
@ -102,7 +100,6 @@ namespace CryptoExchange.Net.SharedApis
string exchange, string exchange,
TradingMode[]? dataTradeModes, TradingMode[]? dataTradeModes,
HttpStatusCode? code, HttpStatusCode? code,
Version? httpVersion,
KeyValuePair<string, string[]>[]? responseHeaders, KeyValuePair<string, string[]>[]? responseHeaders,
TimeSpan? responseTime, TimeSpan? responseTime,
long? responseLength, long? responseLength,
@ -117,7 +114,6 @@ namespace CryptoExchange.Net.SharedApis
Error? error, Error? error,
INextPageToken? nextPageToken = null) : base( INextPageToken? nextPageToken = null) : base(
code, code,
httpVersion,
responseHeaders, responseHeaders,
responseTime, responseTime,
responseLength, responseLength,
@ -144,7 +140,7 @@ namespace CryptoExchange.Net.SharedApis
/// <returns></returns> /// <returns></returns>
public new ExchangeWebResult<K> As<K>([AllowNull] K data) public new ExchangeWebResult<K> As<K>([AllowNull] K data)
{ {
return new ExchangeWebResult<K>(Exchange, DataTradeMode, ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken); return new ExchangeWebResult<K>(Exchange, DataTradeMode, ResponseStatusCode, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -58,19 +58,19 @@ namespace CryptoExchange.Net.SharedApis
public virtual Error? ValidateRequest(string exchange, ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes) public virtual Error? ValidateRequest(string exchange, ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes)
{ {
if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value)) if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value))
return ArgumentError.Invalid("TradingMode", $"TradingMode.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}"); return new ArgumentError($"ApiType.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}");
foreach (var param in RequiredExchangeParameters) foreach (var param in RequiredExchangeParameters)
{ {
if (!string.IsNullOrEmpty(param.Name)) if (!string.IsNullOrEmpty(param.Name))
{ {
if (ExchangeParameters.HasValue(exchangeParameters, exchange, param.Name!, param.ValueType) != true) if (ExchangeParameters.HasValue(exchangeParameters, exchange, param.Name!, param.ValueType) != true)
return ArgumentError.Invalid(param.Name!, $"Required exchange parameter `{param.Name}` for exchange `{exchange}` is missing or has incorrect type. Expected type is {param.ValueType.Name}. Example: {param.ExampleValue}"); return new ArgumentError($"Required exchange parameter `{param.Name}` for exchange `{exchange}` is missing or has incorrect type. Expected type is {param.ValueType.Name}. Example: {param.ExampleValue}");
} }
else else
{ {
if (param.Names!.All(x => ExchangeParameters.HasValue(exchangeParameters, exchange, x, param.ValueType) != true)) if (param.Names!.All(x => ExchangeParameters.HasValue(exchangeParameters, exchange, x, param.ValueType) != true))
return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}"); return new ArgumentError($"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
} }
} }
@ -109,15 +109,6 @@ namespace CryptoExchange.Net.SharedApis
/// </summary> /// </summary>
public List<ParameterDescription> RequiredOptionalParameters { get; set; } = new List<ParameterDescription>(); public List<ParameterDescription> RequiredOptionalParameters { get; set; } = new List<ParameterDescription>();
/// <summary>
/// Whether this accepts multiple symbols (Only applicable to request requiring symbol parameters)
/// </summary>
public bool SupportsMultipleSymbols { get; set; } = false;
/// <summary>
/// The max number of symbols which can be passed in a call (Only applicable to request requiring symbol parameters)
/// </summary>
public int? MaxSymbolCount { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -140,25 +131,12 @@ namespace CryptoExchange.Net.SharedApis
if (!string.IsNullOrEmpty(param.Name)) if (!string.IsNullOrEmpty(param.Name))
{ {
if (typeof(T).GetProperty(param.Name)!.GetValue(request, null) == null) if (typeof(T).GetProperty(param.Name)!.GetValue(request, null) == null)
return ArgumentError.Invalid(param.Name!, $"Required optional parameter `{param.Name}` for exchange `{exchange}` is missing. Example: {param.ExampleValue}"); return new ArgumentError($"Required optional parameter `{param.Name}` for exchange `{exchange}` is missing. Example: {param.ExampleValue}");
} }
else else
{ {
if (param.Names!.All(x => typeof(T).GetProperty(param.Name!)!.GetValue(request, null) == null)) if (param.Names!.All(x => typeof(T).GetProperty(param.Name!)!.GetValue(request, null) == null))
return ArgumentError.Invalid(string.Join("/", param.Names!), $"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}"); return new ArgumentError($"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
}
}
if (request is SharedSymbolRequest symbolsRequest)
{
if (symbolsRequest.Symbols != null)
{
if (!SupportsMultipleSymbols)
return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Only a single symbol parameter is allowed, multiple symbols are not supported");
if (symbolsRequest.Symbols.Length > MaxSymbolCount)
return ArgumentError.Invalid(nameof(SharedSymbolRequest.Symbols), $"Max number of symbols is {MaxSymbolCount} but {symbolsRequest.Symbols.Length} were passed");
} }
} }

View File

@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, GetClosedOrdersRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, GetClosedOrdersRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (TimeFilterSupported && request.StartTime != null) if (TimeFilterSupported && request.StartTime != null)
return ArgumentError.Invalid(nameof(GetClosedOrdersRequest.StartTime), $"Time filter is not supported"); return new ArgumentError($"Time filter is not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, GetDepositsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, GetDepositsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (TimeFilterSupported && request.StartTime != null) if (TimeFilterSupported && request.StartTime != null)
return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); return new ArgumentError($"Time filter is not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -67,23 +67,23 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, GetKlinesRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, GetKlinesRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (!IsSupported(request.Interval)) if (!IsSupported(request.Interval))
return ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), "Interval not supported"); return new ArgumentError("Interval not supported");
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} klines are available"); return new ArgumentError($"Only the most recent {MaxAge} klines are available");
if (request.Limit > MaxLimit) if (request.Limit > MaxLimit)
return ArgumentError.Invalid(nameof(GetKlinesRequest.Limit), $"Only {MaxLimit} klines can be retrieved per request"); return new ArgumentError($"Only {MaxLimit} klines can be retrieved per request");
if (MaxTotalDataPoints.HasValue) if (MaxTotalDataPoints.HasValue)
{ {
if (request.Limit > MaxTotalDataPoints.Value) if (request.Limit > MaxTotalDataPoints.Value)
return ArgumentError.Invalid(nameof(GetKlinesRequest.Limit), $"Only the most recent {MaxTotalDataPoints} klines are available"); return new ArgumentError($"Only the most recent {MaxTotalDataPoints} klines are available");
if (request.StartTime.HasValue == true) if (request.StartTime.HasValue == true)
{ {
if (((request.EndTime ?? DateTime.UtcNow) - request.StartTime.Value).TotalSeconds / (int)request.Interval > MaxTotalDataPoints.Value) if (((request.EndTime ?? DateTime.UtcNow) - request.StartTime.Value).TotalSeconds / (int)request.Interval > MaxTotalDataPoints.Value)
return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxTotalDataPoints} klines are available, time filter failed"); return new ArgumentError($"Only the most recent {MaxTotalDataPoints} klines are available, time filter failed");
} }
} }

View File

@ -49,13 +49,13 @@ namespace CryptoExchange.Net.SharedApis
return null; return null;
if (MaxLimit.HasValue && request.Limit.Value > MaxLimit) if (MaxLimit.HasValue && request.Limit.Value > MaxLimit)
return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Max limit is {MaxLimit}"); return new ArgumentError($"Max limit is {MaxLimit}");
if (MinLimit.HasValue && request.Limit.Value < MinLimit) if (MinLimit.HasValue && request.Limit.Value < MinLimit)
return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Min limit is {MaxLimit}"); return new ArgumentError($"Min limit is {MaxLimit}");
if (SupportedLimits != null && !SupportedLimits.Contains(request.Limit.Value)) if (SupportedLimits != null && !SupportedLimits.Contains(request.Limit.Value))
return ArgumentError.Invalid(nameof(GetOrderBookRequest.Limit), $"Limit should be one of " + string.Join(", ", SupportedLimits)); return new ArgumentError($"Limit should be one of " + string.Join(", ", SupportedLimits));
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
public Error? Validate(GetRecentTradesRequest request) public Error? Validate(GetRecentTradesRequest request)
{ {
if (request.Limit > MaxLimit) if (request.Limit > MaxLimit)
return ArgumentError.Invalid(nameof(GetRecentTradesRequest.Limit), $"Only the most recent {MaxLimit} trades are available"); return new ArgumentError($"Only the most recent {MaxLimit} trades are available");
return null; return null;
} }

View File

@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, GetTradeHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, GetTradeHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
return ArgumentError.Invalid(nameof(GetTradeHistoryRequest.StartTime), $"Only the most recent {MaxAge} trades are available"); return new ArgumentError($"Only the most recent {MaxAge} trades are available");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -25,7 +25,7 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, GetWithdrawalsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, GetWithdrawalsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (TimeFilterSupported && request.StartTime != null) if (TimeFilterSupported && request.StartTime != null)
return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.StartTime), $"Time filter is not supported"); return new ArgumentError($"Time filter is not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -36,16 +36,16 @@ namespace CryptoExchange.Net.SharedApis
SharedQuantitySupport quantitySupport) SharedQuantitySupport quantitySupport)
{ {
if (!SupportsTpSl && (request.StopLossPrice != null || request.TakeProfitPrice != null)) if (!SupportsTpSl && (request.StopLossPrice != null || request.TakeProfitPrice != null))
return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.StopLossPrice) + " / " + nameof(PlaceFuturesOrderRequest.TakeProfitPrice), "Tp/Sl parameters not supported"); return new ArgumentError("Tp/Sl parameters not supported");
if (request.OrderType == SharedOrderType.Other) if (request.OrderType == SharedOrderType.Other)
throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType)); throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType));
if (!supportedOrderTypes.Contains(request.OrderType)) if (!supportedOrderTypes.Contains(request.OrderType))
return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.OrderType), "Order type not supported"); return new ArgumentError("Order type not supported");
if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value)) if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value))
return ArgumentError.Invalid(nameof(PlaceFuturesOrderRequest.TimeInForce), "Order time in force not supported"); return new ArgumentError("Order time in force not supported");
var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity); var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity);
if (quantityError != null) if (quantityError != null)

View File

@ -34,10 +34,10 @@ namespace CryptoExchange.Net.SharedApis
throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType)); throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType));
if (!supportedOrderTypes.Contains(request.OrderType)) if (!supportedOrderTypes.Contains(request.OrderType))
return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.OrderType), "Order type not supported"); return new ArgumentError("Order type not supported");
if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value)) if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value))
return ArgumentError.Invalid(nameof(PlaceSpotOrderRequest.TimeInForce), "Order time in force not supported"); return new ArgumentError("Order time in force not supported");
var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity); var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity);
if (quantityError != null) if (quantityError != null)

View File

@ -60,7 +60,7 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, SubscribeKlineRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, SubscribeKlineRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (!IsSupported(request.Interval)) if (!IsSupported(request.Interval))
return ArgumentError.Invalid(nameof(SubscribeKlineRequest.Interval), "Interval not supported"); return new ArgumentError("Interval not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -29,7 +29,7 @@ namespace CryptoExchange.Net.SharedApis
public override Error? ValidateRequest(string exchange, SubscribeOrderBookRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) public override Error? ValidateRequest(string exchange, SubscribeOrderBookRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{ {
if (request.Limit != null && !SupportedLimits.Contains(request.Limit.Value)) if (request.Limit != null && !SupportedLimits.Contains(request.Limit.Value))
return ArgumentError.Invalid(nameof(SubscribeOrderBookRequest.Limit), "Limit not supported"); return new ArgumentError("Limit not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
} }

View File

@ -84,16 +84,16 @@ namespace CryptoExchange.Net.SharedApis
return null; return null;
if (supportedType == SharedQuantityType.BaseAndQuoteAsset && quantity != null && quantity.QuantityInBaseAsset == null && quantity.QuantityInQuoteAsset == null) if (supportedType == SharedQuantityType.BaseAndQuoteAsset && quantity != null && quantity.QuantityInBaseAsset == null && quantity.QuantityInQuoteAsset == null)
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in base or quote asset"); return new ArgumentError($"Quantity for {side}.{type} required in base or quote asset");
if (supportedType == SharedQuantityType.QuoteAsset && quantity != null && quantity.QuantityInQuoteAsset == null) if (supportedType == SharedQuantityType.QuoteAsset && quantity != null && quantity.QuantityInQuoteAsset == null)
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in quote asset"); return new ArgumentError($"Quantity for {side}.{type} required in quote asset");
if (supportedType == SharedQuantityType.BaseAsset && quantity != null && quantity.QuantityInBaseAsset == null && quantity.QuantityInContracts == null) if (supportedType == SharedQuantityType.BaseAsset && quantity != null && quantity.QuantityInBaseAsset == null && quantity.QuantityInContracts == null)
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in base asset"); return new ArgumentError($"Quantity for {side}.{type} required in base asset");
if (supportedType == SharedQuantityType.Contracts && quantity != null && quantity.QuantityInContracts == null) if (supportedType == SharedQuantityType.Contracts && quantity != null && quantity.QuantityInContracts == null)
return ArgumentError.Invalid("Quantity", $"Quantity for {side}.{type} required in contracts"); return new ArgumentError($"Quantity for {side}.{type} required in contracts");
return null; return null;
} }

View File

@ -1,27 +1,14 @@
using System; namespace CryptoExchange.Net.SharedApis
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Symbol request /// Symbol request
/// </summary> /// </summary>
public record SharedSymbolRequest : SharedRequest public record SharedSymbolRequest : SharedRequest
{ {
/// <summary>
/// Trading mode
/// </summary>
public TradingMode TradingMode { get; }
/// <summary> /// <summary>
/// The symbol /// The symbol
/// </summary> /// </summary>
public SharedSymbol? Symbol { get; set; } public SharedSymbol Symbol { get; set; }
/// <summary>
/// Symbols
/// </summary>
public SharedSymbol[]? Symbols { get; set; }
/// <summary> /// <summary>
/// ctor /// ctor
@ -29,22 +16,6 @@ namespace CryptoExchange.Net.SharedApis
public SharedSymbolRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) public SharedSymbolRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters)
{ {
Symbol = symbol; Symbol = symbol;
TradingMode = symbol.TradingMode;
}
/// <summary>
/// ctor
/// </summary>
public SharedSymbolRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters)
{
if (!symbols.Any())
throw new ArgumentException("Empty symbol list");
if (symbols.GroupBy(x => x.TradingMode).Count() > 1)
throw new ArgumentException("All symbols in the symbol list should have the same trading mode");
Symbols = symbols.ToArray();
TradingMode = Symbols.First().TradingMode;
} }
} }
} }

View File

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis
namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Request to subscribe to book ticker updates /// Request to subscribe to book ticker updates
@ -15,22 +13,5 @@ namespace CryptoExchange.Net.SharedApis
public SubscribeBookTickerRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) public SubscribeBookTickerRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters)
{ {
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeBookTickerRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null) : base(symbols, exchangeParameters)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
public SubscribeBookTickerRequest(params SharedSymbol[] symbols) : base(symbols, null)
{
}
} }
} }

View File

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis
namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Request to subscribe to kline/candlestick updates /// Request to subscribe to kline/candlestick updates
@ -22,26 +20,5 @@ namespace CryptoExchange.Net.SharedApis
{ {
Interval = interval; Interval = interval;
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
/// <param name="interval">Kline interval</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeKlineRequest(IEnumerable<SharedSymbol> symbols, SharedKlineInterval interval, ExchangeParameters? exchangeParameters = null) : base(symbols, exchangeParameters)
{
Interval = interval;
}
/// <summary>
/// ctor
/// </summary>
/// <param name="interval">Kline interval</param>
/// <param name="symbols">The symbols to subscribe to</param>
public SubscribeKlineRequest(SharedKlineInterval interval, params SharedSymbol[] symbols) : base(symbols, null)
{
Interval = interval;
}
} }
} }

View File

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis
namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Request to subscribe to order book snapshot updates /// Request to subscribe to order book snapshot updates
@ -22,22 +20,5 @@ namespace CryptoExchange.Net.SharedApis
{ {
Limit = limit; Limit = limit;
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeOrderBookRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null) : base(symbols, exchangeParameters)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
public SubscribeOrderBookRequest(params SharedSymbol[] symbols) : base(symbols, null)
{
}
} }
} }

View File

@ -1,7 +1,4 @@
using System.Collections; namespace CryptoExchange.Net.SharedApis
using System.Collections.Generic;
namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Request to subscribe to ticker updates /// Request to subscribe to ticker updates
@ -16,22 +13,5 @@ namespace CryptoExchange.Net.SharedApis
public SubscribeTickerRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) public SubscribeTickerRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters)
{ {
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeTickerRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null) : base(symbols, exchangeParameters)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
public SubscribeTickerRequest(params SharedSymbol[] symbols) : base(symbols, null)
{
}
} }
} }

View File

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace CryptoExchange.Net.SharedApis
namespace CryptoExchange.Net.SharedApis
{ {
/// <summary> /// <summary>
/// Request to subscribe to trade updates /// Request to subscribe to trade updates
@ -15,22 +13,5 @@ namespace CryptoExchange.Net.SharedApis
public SubscribeTradeRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) public SubscribeTradeRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters)
{ {
} }
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public SubscribeTradeRequest(IEnumerable<SharedSymbol> symbols, ExchangeParameters? exchangeParameters = null) : base(symbols, exchangeParameters)
{
}
/// <summary>
/// ctor
/// </summary>
/// <param name="symbols">The symbols to subscribe to</param>
public SubscribeTradeRequest(params SharedSymbol[] symbols) : base(symbols, null)
{
}
} }
} }

View File

@ -30,8 +30,5 @@ namespace CryptoExchange.Net.SharedApis
public SharedFuturesSymbol(TradingMode symbolType, string baseAsset, string quoteAsset, string symbol, bool trading) : base(baseAsset, quoteAsset, symbol, trading, symbolType) public SharedFuturesSymbol(TradingMode symbolType, string baseAsset, string quoteAsset, string symbol, bool trading) : base(baseAsset, quoteAsset, symbol, trading, symbolType)
{ {
} }
/// <inheritdoc />
public override SharedSymbol SharedSymbol => new SharedSymbol(TradingMode, BaseAsset.ToUpperInvariant(), QuoteAsset.ToUpperInvariant(), DeliveryTime) { SymbolName = Name };
} }
} }

View File

@ -69,11 +69,5 @@
Name = symbol; Name = symbol;
Trading = trading; Trading = trading;
} }
/// <summary>
/// The SharedSymbol of this symbol
/// </summary>
/// <returns></returns>
public virtual SharedSymbol SharedSymbol => new SharedSymbol(TradingMode, BaseAsset.ToUpperInvariant(), QuoteAsset.ToUpperInvariant()) { SymbolName = Name };
} }
} }

View File

@ -1,7 +1,6 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Logging.Extensions; using CryptoExchange.Net.Logging.Extensions;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.Objects.Sockets;
using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.RateLimiting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -253,11 +252,6 @@ namespace CryptoExchange.Net.Sockets
await (OnConnectRateLimited?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false); await (OnConnectRateLimited?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
return new CallResult(new ServerRateLimitError(we.Message, we)); return new CallResult(new ServerRateLimitError(we.Message, we));
} }
if (_socket.HttpStatusCode == HttpStatusCode.Unauthorized)
{
return new CallResult(new ServerError(new ErrorInfo(ErrorType.Unauthorized, "Server returned status code `401` when `101` was expected")));
}
#else #else
// ClientWebSocket.HttpStatusCode is only available in .NET6+ https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket.httpstatuscode?view=net-8.0 // ClientWebSocket.HttpStatusCode is only available in .NET6+ https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket.httpstatuscode?view=net-8.0
// Try to read 429 from the message instead // Try to read 429 from the message instead

View File

@ -47,7 +47,7 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public static MessageMatcher Create<T>(string value) public static MessageMatcher Create<T>(string value)
{ {
return new MessageMatcher(new MessageHandlerLink<T>(MessageLinkType.Full, value, (con, msg) => new CallResult<T>(default, msg.OriginalData, null))); return new MessageMatcher(new MessageHandlerLink<T>(MessageLinkType.Full, value, (con, msg) => CallResult.SuccessResult));
} }
/// <summary> /// <summary>

View File

@ -29,11 +29,6 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public TimeSpan? RequestTimeout { get; set; } public TimeSpan? RequestTimeout { get; set; }
/// <summary>
/// What should happen if the query times out
/// </summary>
public TimeoutBehavior TimeoutBehavior { get; set; } = TimeoutBehavior.Fail;
/// <summary> /// <summary>
/// The number of required responses. Can be more than 1 when for example subscribing multiple symbols streams in a single request, /// The number of required responses. Can be more than 1 when for example subscribing multiple symbols streams in a single request,
/// and each symbol receives it's own confirmation response /// and each symbol receives it's own confirmation response
@ -188,12 +183,15 @@ namespace CryptoExchange.Net.Sockets
/// <inheritdoc /> /// <inheritdoc />
public override async Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink check) public override async Task<CallResult> Handle(SocketConnection connection, DataEvent<object> message, MessageHandlerLink check)
{ {
if (!PreCheckMessage(connection, message)) if (!PreCheckMessage(message))
return CallResult.SuccessResult; return CallResult.SuccessResult;
CurrentResponses++; CurrentResponses++;
if (CurrentResponses == RequiredResponses) if (CurrentResponses == RequiredResponses)
{
Completed = true;
Response = message.Data; Response = message.Data;
}
if (Result?.Success != false) if (Result?.Success != false)
// If an error result is already set don't override that // If an error result is already set don't override that
@ -201,7 +199,6 @@ namespace CryptoExchange.Net.Sockets
if (CurrentResponses == RequiredResponses) if (CurrentResponses == RequiredResponses)
{ {
Completed = true;
_event.Set(); _event.Set();
if (ContinueAwaiter != null) if (ContinueAwaiter != null)
await ContinueAwaiter.WaitAsync().ConfigureAwait(false); await ContinueAwaiter.WaitAsync().ConfigureAwait(false);
@ -213,7 +210,9 @@ namespace CryptoExchange.Net.Sockets
/// <summary> /// <summary>
/// Validate if a message is actually processable by this query /// Validate if a message is actually processable by this query
/// </summary> /// </summary>
public virtual bool PreCheckMessage(SocketConnection connection, DataEvent<object> message) => true; /// <param name="message"></param>
/// <returns></returns>
public virtual bool PreCheckMessage(DataEvent<object> message) => true;
/// <inheritdoc /> /// <inheritdoc />
public override void Timeout() public override void Timeout()
@ -222,11 +221,7 @@ namespace CryptoExchange.Net.Sockets
return; return;
Completed = true; Completed = true;
if (TimeoutBehavior == TimeoutBehavior.Fail) Result = new CallResult<THandlerResponse>(new CancellationRequestedError(null, "Query timeout", null));
Result = new CallResult<THandlerResponse>(new TimeoutError());
else
Result = new CallResult<THandlerResponse>(default, null, default);
ContinueAwaiter?.Set(); ContinueAwaiter?.Set();
_event.Set(); _event.Set();
} }

View File

@ -202,18 +202,6 @@ namespace CryptoExchange.Net.Sockets
} }
} }
/// <summary>
/// The number of current pending requests
/// </summary>
public int PendingRequests
{
get
{
lock (_listenersLock)
return _listeners.OfType<Query>().Where(x => !x.Completed).Count();
}
}
private bool _pausedActivity; private bool _pausedActivity;
private readonly object _listenersLock; private readonly object _listenersLock;
private readonly List<IMessageProcessor> _listeners; private readonly List<IMessageProcessor> _listeners;
@ -489,7 +477,7 @@ namespace CryptoExchange.Net.Sockets
if (!accessor.IsValid && !ApiClient.ProcessUnparsableMessages) if (!accessor.IsValid && !ApiClient.ProcessUnparsableMessages)
{ {
_logger.FailedToParse(SocketId, result.Error!.Message ?? result.Error!.ErrorDescription!); _logger.FailedToParse(SocketId, result.Error!.Message);
return; return;
} }
@ -531,10 +519,7 @@ namespace CryptoExchange.Net.Sockets
{ {
// If this message is for this listener then it is automatically confirmed, even if the subscription is not (yet) confirmed // If this message is for this listener then it is automatically confirmed, even if the subscription is not (yet) confirmed
subscriptionProcessor.Confirmed = true; subscriptionProcessor.Confirmed = true;
if (subscriptionProcessor.SubscriptionQuery?.TimeoutBehavior == TimeoutBehavior.Succeed) // This doesn't trigger a waiting subscribe query, should probably also somehow set the wait event for that
// If this subscription has a query waiting for a timeout (success if there is no error response)
// then time it out now as the data is being received, so we assume it's successful
subscriptionProcessor.SubscriptionQuery.Timeout();
} }
// 5. Deserialize the message // 5. Deserialize the message
@ -780,7 +765,7 @@ namespace CryptoExchange.Net.Sockets
public virtual async Task<CallResult> SendAndWaitQueryAsync(Query query, AsyncResetEvent? continueEvent = null, CancellationToken ct = default) public virtual async Task<CallResult> SendAndWaitQueryAsync(Query query, AsyncResetEvent? continueEvent = null, CancellationToken ct = default)
{ {
await SendAndWaitIntAsync(query, continueEvent, ct).ConfigureAwait(false); await SendAndWaitIntAsync(query, continueEvent, ct).ConfigureAwait(false);
return query.Result ?? new CallResult(new TimeoutError()); return query.Result ?? new CallResult(new ServerError("Timeout"));
} }
/// <summary> /// <summary>
@ -794,7 +779,7 @@ namespace CryptoExchange.Net.Sockets
public virtual async Task<CallResult<THandlerResponse>> SendAndWaitQueryAsync<THandlerResponse>(Query<THandlerResponse> query, AsyncResetEvent? continueEvent = null, CancellationToken ct = default) public virtual async Task<CallResult<THandlerResponse>> SendAndWaitQueryAsync<THandlerResponse>(Query<THandlerResponse> query, AsyncResetEvent? continueEvent = null, CancellationToken ct = default)
{ {
await SendAndWaitIntAsync(query, continueEvent, ct).ConfigureAwait(false); await SendAndWaitIntAsync(query, continueEvent, ct).ConfigureAwait(false);
return query.TypedResult ?? new CallResult<THandlerResponse>(new TimeoutError()); return query.TypedResult ?? new CallResult<THandlerResponse>(new ServerError("Timeout"));
} }
private async Task SendAndWaitIntAsync(Query query, AsyncResetEvent? continueEvent, CancellationToken ct = default) private async Task SendAndWaitIntAsync(Query query, AsyncResetEvent? continueEvent, CancellationToken ct = default)
@ -879,7 +864,7 @@ namespace CryptoExchange.Net.Sockets
{ {
if (ApiClient.MessageSendSizeLimit != null && data.Length > ApiClient.MessageSendSizeLimit.Value) if (ApiClient.MessageSendSizeLimit != null && data.Length > ApiClient.MessageSendSizeLimit.Value)
{ {
var info = $"Message to send exceeds the max server message size ({data.Length} vs {ApiClient.MessageSendSizeLimit.Value} bytes). Split the request into batches to keep below this limit"; var info = $"Message to send exceeds the max server message size ({ApiClient.MessageSendSizeLimit.Value} bytes). Split the request into batches to keep below this limit";
_logger.LogWarning("[Sckt {SocketId}] [Req {RequestId}] {Info}", SocketId, requestId, info); _logger.LogWarning("[Sckt {SocketId}] [Req {RequestId}] {Info}", SocketId, requestId, info);
return new CallResult(new InvalidOperationError(info)); return new CallResult(new InvalidOperationError(info));
} }
@ -914,7 +899,7 @@ namespace CryptoExchange.Net.Sockets
{ {
if (ApiClient.MessageSendSizeLimit != null && data.Length > ApiClient.MessageSendSizeLimit.Value) if (ApiClient.MessageSendSizeLimit != null && data.Length > ApiClient.MessageSendSizeLimit.Value)
{ {
var info = $"Message to send exceeds the max server message size ({data.Length} vs {ApiClient.MessageSendSizeLimit.Value} bytes). Split the request into batches to keep below this limit"; var info = $"Message to send exceeds the max server message size ({ApiClient.MessageSendSizeLimit.Value} bytes). Split the request into batches to keep below this limit";
_logger.LogWarning("[Sckt {SocketId}] [Req {RequestId}] {Info}", SocketId, requestId, info); _logger.LogWarning("[Sckt {SocketId}] [Req {RequestId}] {Info}", SocketId, requestId, info);
return new CallResult(new InvalidOperationError(info)); return new CallResult(new InvalidOperationError(info));
} }
@ -1011,7 +996,7 @@ namespace CryptoExchange.Net.Sockets
return result; return result;
} }
var subQuery = subscription.CreateSubscriptionQuery(this); var subQuery = subscription.GetSubQuery(this);
if (subQuery == null) if (subQuery == null)
{ {
subscription.IsResubscribing = false; subscription.IsResubscribing = false;
@ -1046,7 +1031,7 @@ namespace CryptoExchange.Net.Sockets
internal async Task UnsubscribeAsync(Subscription subscription) internal async Task UnsubscribeAsync(Subscription subscription)
{ {
var unsubscribeRequest = subscription.CreateUnsubscriptionQuery(this); var unsubscribeRequest = subscription.GetUnsubQuery();
if (unsubscribeRequest == null) if (unsubscribeRequest == null)
return; return;
@ -1059,7 +1044,7 @@ namespace CryptoExchange.Net.Sockets
if (!_socket.IsOpen) if (!_socket.IsOpen)
return new CallResult(new WebError("Socket is not connected")); return new CallResult(new WebError("Socket is not connected"));
var subQuery = subscription.CreateSubscriptionQuery(this); var subQuery = subscription.GetSubQuery(this);
if (subQuery == null) if (subQuery == null)
return CallResult.SuccessResult; return CallResult.SuccessResult;

View File

@ -80,16 +80,6 @@ namespace CryptoExchange.Net.Sockets
/// </summary> /// </summary>
public string? Topic { get; set; } public string? Topic { get; set; }
/// <summary>
/// The subscribe query for this subscription
/// </summary>
public Query? SubscriptionQuery { get; private set; }
/// <summary>
/// The unsubscribe query for this subscription
/// </summary>
public Query? UnsubscriptionQuery { get; private set; }
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -101,21 +91,11 @@ namespace CryptoExchange.Net.Sockets
Id = ExchangeHelpers.NextId(); Id = ExchangeHelpers.NextId();
} }
/// <summary>
/// Create a new subscription query
/// </summary>
public Query? CreateSubscriptionQuery(SocketConnection connection)
{
var query = GetSubQuery(connection);
SubscriptionQuery = query;
return query;
}
/// <summary> /// <summary>
/// Get the subscribe query to send when subscribing /// Get the subscribe query to send when subscribing
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected abstract Query? GetSubQuery(SocketConnection connection); public abstract Query? GetSubQuery(SocketConnection connection);
/// <summary> /// <summary>
/// Handle a subscription query response /// Handle a subscription query response
@ -129,21 +109,11 @@ namespace CryptoExchange.Net.Sockets
/// <param name="message"></param> /// <param name="message"></param>
public virtual void HandleUnsubQueryResponse(object message) { } public virtual void HandleUnsubQueryResponse(object message) { }
/// <summary>
/// Create a new unsubscription query
/// </summary>
public Query? CreateUnsubscriptionQuery(SocketConnection connection)
{
var query = GetUnsubQuery(connection);
UnsubscriptionQuery = query;
return query;
}
/// <summary> /// <summary>
/// Get the unsubscribe query to send when unsubscribing /// Get the unsubscribe query to send when unsubscribing
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
protected abstract Query? GetUnsubQuery(SocketConnection connection); public abstract Query? GetUnsubQuery();
/// <inheritdoc /> /// <inheritdoc />
public virtual CallResult<object> Deserialize(IMessageAccessor message, Type type) => message.Deserialize(type); public virtual CallResult<object> Deserialize(IMessageAccessor message, Type type) => message.Deserialize(type);

View File

@ -22,9 +22,9 @@ namespace CryptoExchange.Net.Sockets
} }
/// <inheritdoc /> /// <inheritdoc />
protected override Query? GetSubQuery(SocketConnection connection) => null; public override Query? GetSubQuery(SocketConnection connection) => null;
/// <inheritdoc /> /// <inheritdoc />
protected override Query? GetUnsubQuery(SocketConnection connection) => null; public override Query? GetUnsubQuery() => null;
} }
} }

View File

@ -21,8 +21,6 @@ namespace CryptoExchange.Net.Testing.Implementations
public Uri Uri { get; set; } public Uri Uri { get; set; }
public Version HttpVersion { get; set; }
public int RequestId { get; set; } public int RequestId { get; set; }
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

View File

@ -1,6 +1,5 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using System; using System;
using System.Net.Http; using System.Net.Http;
@ -15,11 +14,11 @@ namespace CryptoExchange.Net.Testing.Implementations
_request = request; _request = request;
} }
public void Configure(RestExchangeOptions options, HttpClient? client) public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null)
{ {
} }
public IRequest Create(Version httpRequestVersion, HttpMethod method, Uri uri, int requestId) public IRequest Create(HttpMethod method, Uri uri, int requestId)
{ {
_request.Method = method; _request.Method = method;
_request.Uri = uri; _request.Uri = uri;
@ -27,6 +26,6 @@ namespace CryptoExchange.Net.Testing.Implementations
return _request; return _request;
} }
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout, TimeSpan? httpKeepAliveInterval) {} public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {}
} }
} }

View File

@ -1,5 +1,4 @@
using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Interfaces;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net; using System.Net;
@ -12,7 +11,6 @@ namespace CryptoExchange.Net.Testing.Implementations
private readonly Stream _response; private readonly Stream _response;
public HttpStatusCode StatusCode { get; } public HttpStatusCode StatusCode { get; }
public Version HttpVersion { get; }
public bool IsSuccessStatusCode { get; } public bool IsSuccessStatusCode { get; }
@ -23,7 +21,6 @@ namespace CryptoExchange.Net.Testing.Implementations
public TestResponse(HttpStatusCode code, Stream response) public TestResponse(HttpStatusCode code, Stream response)
{ {
StatusCode = code; StatusCode = code;
HttpVersion = new Version(2, 0);
IsSuccessStatusCode = code == HttpStatusCode.OK; IsSuccessStatusCode = code == HttpStatusCode.OK;
_response = response; _response = response;
} }

View File

@ -128,27 +128,23 @@ namespace CryptoExchange.Net.Testing
var uriParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InUri ? client.CreateParameterDictionary(parameters) : null; var uriParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InUri ? client.CreateParameterDictionary(parameters) : null;
var bodyParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InBody ? client.CreateParameterDictionary(parameters) : null; var bodyParams = client.ParameterPositions[method] == HttpMethodParameterPosition.InBody ? client.CreateParameterDictionary(parameters) : null;
var requestDefinition = new RestRequestConfiguration( var headers = new Dictionary<string, string>();
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.TimeProvider = new TestAuthTimeProvider(time ?? new DateTime(2024, 01, 01, 0, 0, 0, DateTimeKind.Utc));
authProvider.ProcessRequest( authProvider.AuthenticateRequest(
client, client,
requestDefinition new Uri(host.AppendPath(path)),
method,
ref uriParams,
ref bodyParams,
ref headers,
true,
client.ArraySerialization,
client.ParameterPositions[method],
client.RequestBodyFormat
); );
var signature = getSignature(requestDefinition.QueryParameters, requestDefinition.BodyParameters, requestDefinition.Headers); var signature = getSignature(uriParams, bodyParams, headers);
if (!string.Equals(signature, expectedSignature, compareCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase)) if (!string.Equals(signature, expectedSignature, compareCase ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase))
throw new Exception($"Signatures do not match. Expected: {expectedSignature}, Actual: {signature}"); throw new Exception($"Signatures do not match. Expected: {expectedSignature}, Actual: {signature}");

View File

@ -184,7 +184,7 @@ namespace CryptoExchange.Net.Trackers.Klines
if (!subResult) if (!subResult)
{ {
_logger.KlineTrackerStartFailed(SymbolName, subResult.Error!.Message ?? subResult.Error!.ErrorDescription!, subResult.Error.Exception); _logger.KlineTrackerStartFailed(SymbolName, subResult.Error!.Message, subResult.Error.Exception);
Status = SyncStatus.Disconnected; Status = SyncStatus.Disconnected;
return subResult; return subResult;
} }

View File

@ -207,7 +207,7 @@ namespace CryptoExchange.Net.Trackers.Trades
if (!subResult) if (!subResult)
{ {
_logger.TradeTrackerStartFailed(SymbolName, subResult.Error!.Message ?? subResult.Error!.ErrorDescription!, subResult.Error.Exception); _logger.TradeTrackerStartFailed(SymbolName, subResult.Error!.Message, subResult.Error.Exception);
Status = SyncStatus.Disconnected; Status = SyncStatus.Disconnected;
return subResult; return subResult;
} }

View File

@ -1,4 +1,4 @@
<Router AppAssembly="@typeof(Program).Assembly"> <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found> </Found>

View File

@ -5,29 +5,28 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Binance.Net" Version="11.3.0" /> <PackageReference Include="Binance.Net" Version="11.1.0" />
<PackageReference Include="Bitfinex.Net" Version="9.3.0" /> <PackageReference Include="Bitfinex.Net" Version="9.1.0" />
<PackageReference Include="BitMart.Net" Version="2.4.1" /> <PackageReference Include="BitMart.Net" Version="2.1.0" />
<PackageReference Include="Bybit.Net" Version="5.4.0" /> <PackageReference Include="Bybit.Net" Version="5.1.0" />
<PackageReference Include="CoinEx.Net" Version="9.3.0" /> <PackageReference Include="CoinEx.Net" Version="9.1.0" />
<PackageReference Include="CoinW.Net" Version="1.0.1" /> <PackageReference Include="CryptoCom.Net" Version="2.1.0" />
<PackageReference Include="CryptoCom.Net" Version="2.4.0" /> <PackageReference Include="DeepCoin.Net" Version="2.1.0" />
<PackageReference Include="DeepCoin.Net" Version="2.3.0" /> <PackageReference Include="GateIo.Net" Version="2.1.0" />
<PackageReference Include="GateIo.Net" Version="2.4.0" /> <PackageReference Include="HyperLiquid.Net" Version="2.1.1" />
<PackageReference Include="HyperLiquid.Net" Version="2.4.0" /> <PackageReference Include="JK.BingX.Net" Version="2.1.0" />
<PackageReference Include="JK.BingX.Net" Version="2.3.0" /> <PackageReference Include="JK.Bitget.Net" Version="2.1.0" />
<PackageReference Include="JK.Bitget.Net" Version="2.3.0" /> <PackageReference Include="JK.Mexc.Net" Version="3.1.0" />
<PackageReference Include="JK.Mexc.Net" Version="3.3.0" /> <PackageReference Include="JK.OKX.Net" Version="3.1.0" />
<PackageReference Include="JK.OKX.Net" Version="3.3.1" /> <PackageReference Include="JKorf.BitMEX.Net" Version="2.1.0" />
<PackageReference Include="JKorf.BitMEX.Net" Version="2.3.0" /> <PackageReference Include="JKorf.Coinbase.Net" Version="2.1.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="2.3.0" /> <PackageReference Include="JKorf.HTX.Net" Version="7.1.0" />
<PackageReference Include="JKorf.HTX.Net" Version="7.3.0" /> <PackageReference Include="KrakenExchange.Net" Version="6.1.0" />
<PackageReference Include="KrakenExchange.Net" Version="6.3.1" /> <PackageReference Include="Kucoin.Net" Version="7.1.0" />
<PackageReference Include="Kucoin.Net" Version="7.3.0" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Toobit.Net" Version="1.2.1" /> <PackageReference Include="Toobit.Net" Version="1.0.1" />
<PackageReference Include="WhiteBit.Net" Version="2.4.0" /> <PackageReference Include="WhiteBit.Net" Version="2.1.0" />
<PackageReference Include="XT.Net" Version="2.3.1" /> <PackageReference Include="XT.Net" Version="2.1.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -8,7 +8,6 @@
@inject IBybitRestClient bybitClient @inject IBybitRestClient bybitClient
@inject ICoinbaseRestClient coinbaseClient @inject ICoinbaseRestClient coinbaseClient
@inject ICoinExRestClient coinexClient @inject ICoinExRestClient coinexClient
@inject ICoinWRestClient coinWClient
@inject ICryptoComRestClient cryptocomClient @inject ICryptoComRestClient cryptocomClient
@inject IDeepCoinRestClient deepCoinClient @inject IDeepCoinRestClient deepCoinClient
@inject IGateIoRestClient gateioClient @inject IGateIoRestClient gateioClient
@ -42,7 +41,6 @@
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT"); var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT"); var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
var coinexTask = coinexClient.SpotApiV2.ExchangeData.GetTickersAsync(["BTCUSDT"]); var coinexTask = coinexClient.SpotApiV2.ExchangeData.GetTickersAsync(["BTCUSDT"]);
var coinWTask = coinWClient.SpotApi.ExchangeData.GetTickersAsync();
var cryptocomTask = cryptocomClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT"); var cryptocomTask = cryptocomClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT");
var deepCoinTask = deepCoinClient.ExchangeApi.ExchangeData.GetTickersAsync(DeepCoin.Net.Enums.SymbolType.Spot); var deepCoinTask = deepCoinClient.ExchangeApi.ExchangeData.GetTickersAsync(DeepCoin.Net.Enums.SymbolType.Spot);
var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT"); var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
@ -85,9 +83,6 @@
if (coinexTask.Result.Success) if (coinexTask.Result.Success)
_prices.Add("CoinEx", coinexTask.Result.Data.Single().LastPrice); _prices.Add("CoinEx", coinexTask.Result.Data.Single().LastPrice);
if (coinWTask.Result.Success)
_prices.Add("CoinW", coinWTask.Result.Data.Single(x => x.Symbol == "BTC_USDT").LastPrice);
if (cryptocomTask.Result.Success) if (cryptocomTask.Result.Success)
_prices.Add("CryptoCom", cryptocomTask.Result.Data.First().LastPrice ?? 0); _prices.Add("CryptoCom", cryptocomTask.Result.Data.First().LastPrice ?? 0);

View File

@ -8,7 +8,6 @@
@inject IBybitSocketClient bybitSocketClient @inject IBybitSocketClient bybitSocketClient
@inject ICoinbaseSocketClient coinbaseSocketClient @inject ICoinbaseSocketClient coinbaseSocketClient
@inject ICoinExSocketClient coinExSocketClient @inject ICoinExSocketClient coinExSocketClient
@inject ICoinWSocketClient coinWSocketClient
@inject ICryptoComSocketClient cryptocomSocketClient @inject ICryptoComSocketClient cryptocomSocketClient
@inject IDeepCoinSocketClient deepCoinSocketClient @inject IDeepCoinSocketClient deepCoinSocketClient
@inject IGateIoSocketClient gateioSocketClient @inject IGateIoSocketClient gateioSocketClient
@ -50,7 +49,6 @@
bitmexSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH_XBT", data => UpdateData("BitMEX", data.Data.LastPrice ?? 0)), bitmexSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH_XBT", data => UpdateData("BitMEX", data.Data.LastPrice ?? 0)),
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)), bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
coinExSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync(["ETHBTC"], data => UpdateData("CoinEx", data.Data.First().LastPrice)), coinExSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync(["ETHBTC"], data => UpdateData("CoinEx", data.Data.First().LastPrice)),
coinWSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CoinW", data.Data.LastPrice)),
coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice ?? 0)), coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice ?? 0)),
cryptocomSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CryptoCom", data.Data.LastPrice ?? 0)), cryptocomSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CryptoCom", data.Data.LastPrice ?? 0)),
deepCoinSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH-BTC", data => UpdateData("DeepCoin", data.Data.LastPrice ?? 0)), deepCoinSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH-BTC", data => UpdateData("DeepCoin", data.Data.LastPrice ?? 0)),

View File

@ -9,7 +9,6 @@
@using BitMEX.Net.Interfaces; @using BitMEX.Net.Interfaces;
@using Bybit.Net.Interfaces @using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces @using CoinEx.Net.Interfaces
@using CoinW.Net.Interfaces
@using Coinbase.Net.Interfaces @using Coinbase.Net.Interfaces
@using CryptoExchange.Net.Authentication @using CryptoExchange.Net.Authentication
@using CryptoExchange.Net.Interfaces @using CryptoExchange.Net.Interfaces
@ -35,7 +34,6 @@
@inject IBybitOrderBookFactory bybitFactory @inject IBybitOrderBookFactory bybitFactory
@inject ICoinbaseOrderBookFactory coinbaseFactory @inject ICoinbaseOrderBookFactory coinbaseFactory
@inject ICoinExOrderBookFactory coinExFactory @inject ICoinExOrderBookFactory coinExFactory
@inject ICoinWOrderBookFactory coinWFactory
@inject ICryptoComOrderBookFactory cryptocomFactory @inject ICryptoComOrderBookFactory cryptocomFactory
@inject IDeepCoinOrderBookFactory deepCoinFactory @inject IDeepCoinOrderBookFactory deepCoinFactory
@inject IGateIoOrderBookFactory gateioFactory @inject IGateIoOrderBookFactory gateioFactory
@ -90,7 +88,6 @@
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) }, { "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) }, { "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") }, { "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
{ "CoinW", coinWFactory.CreateSpot("ETH_BTC") },
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") }, { "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") }, { "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
// DeepCoin does not support the ETH/BTC pair // DeepCoin does not support the ETH/BTC pair
@ -118,7 +115,7 @@
public void Dispose() public void Dispose()
{ {
_timer?.Stop(); _timer.Stop();
_timer.Dispose(); _timer.Dispose();
foreach (var book in _books.Where(b => b.Value.Status != CryptoExchange.Net.Objects.OrderBookStatus.Disconnected)) foreach (var book in _books.Where(b => b.Value.Status != CryptoExchange.Net.Objects.OrderBookStatus.Disconnected))
// It's not necessary to wait for this // It's not necessary to wait for this

View File

@ -9,7 +9,6 @@
@using BitMart.Net.Interfaces; @using BitMart.Net.Interfaces;
@using Bybit.Net.Interfaces @using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces @using CoinEx.Net.Interfaces
@using CoinW.Net.Interfaces
@using Coinbase.Net.Interfaces @using Coinbase.Net.Interfaces
@using CryptoExchange.Net.Interfaces @using CryptoExchange.Net.Interfaces
@using CryptoCom.Net.Interfaces @using CryptoCom.Net.Interfaces
@ -36,7 +35,6 @@
@inject IBybitTrackerFactory bybitFactory @inject IBybitTrackerFactory bybitFactory
@inject ICoinbaseTrackerFactory coinbaseFactory @inject ICoinbaseTrackerFactory coinbaseFactory
@inject ICoinExTrackerFactory coinExFactory @inject ICoinExTrackerFactory coinExFactory
@inject ICoinWTrackerFactory coinWFactory
@inject ICryptoComTrackerFactory cryptocomFactory @inject ICryptoComTrackerFactory cryptocomFactory
@inject IDeepCoinTrackerFactory deepCoinFactory @inject IDeepCoinTrackerFactory deepCoinFactory
@inject IGateIoTrackerFactory gateioFactory @inject IGateIoTrackerFactory gateioFactory
@ -84,7 +82,6 @@
{ bybitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) }, { bybitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ coinbaseFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) }, { coinbaseFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ coinExFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) }, { coinExFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ coinWFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ cryptocomFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) }, { cryptocomFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ deepCoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) }, { deepCoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
{ gateioFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) }, { gateioFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
@ -124,8 +121,8 @@
public void Dispose() public void Dispose()
{ {
_timer?.Stop(); _timer.Stop();
_timer?.Dispose(); _timer.Dispose();
foreach (var tracker in _trackers.Where(b => b.Status != CryptoExchange.Net.Objects.SyncStatus.Disconnected)) foreach (var tracker in _trackers.Where(b => b.Status != CryptoExchange.Net.Objects.SyncStatus.Disconnected))
// It's not necessary to wait for this // It's not necessary to wait for this
_ = tracker.StopAsync(); _ = tracker.StopAsync();

View File

@ -41,7 +41,6 @@ namespace BlazorClient
services.AddBybit(); services.AddBybit();
services.AddCoinbase(); services.AddCoinbase();
services.AddCoinEx(); services.AddCoinEx();
services.AddCoinW();
services.AddCryptoCom(); services.AddCryptoCom();
services.AddDeepCoin(); services.AddDeepCoin();
services.AddGateIo(); services.AddGateIo();

View File

@ -17,7 +17,6 @@
@using Bybit.Net.Interfaces.Clients; @using Bybit.Net.Interfaces.Clients;
@using Coinbase.Net.Interfaces.Clients; @using Coinbase.Net.Interfaces.Clients;
@using CoinEx.Net.Interfaces.Clients; @using CoinEx.Net.Interfaces.Clients;
@using CoinW.Net.Interfaces.Clients;
@using CryptoCom.Net.Interfaces.Clients; @using CryptoCom.Net.Interfaces.Clients;
@using DeepCoin.Net.Interfaces.Clients; @using DeepCoin.Net.Interfaces.Clients;
@using GateIo.Net.Interfaces.Clients; @using GateIo.Net.Interfaces.Clients;

View File

@ -21,7 +21,6 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
|![Bybit](https://raw.githubusercontent.com/JKorf/Bybit.Net/refs/heads/main/ByBit.Net/Icon/icon.png)|Bybit|CEX|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square)](https://www.nuget.org/packages/Bybit.Net)|[Link](https://partner.bybit.com/b/jkorf)|-| |![Bybit](https://raw.githubusercontent.com/JKorf/Bybit.Net/refs/heads/main/ByBit.Net/Icon/icon.png)|Bybit|CEX|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square)](https://www.nuget.org/packages/Bybit.Net)|[Link](https://partner.bybit.com/b/jkorf)|-|
|![Coinbase](https://raw.githubusercontent.com/JKorf/Coinbase.Net/refs/heads/main/Coinbase.Net/Icon/icon.png)|Coinbase|CEX|[JKorf/Coinbase.Net](https://github.com/JKorf/Coinbase.Net)|[![Nuget version](https://img.shields.io/nuget/v/JKorf.Coinbase.Net.svg?style=flat-square)](https://www.nuget.org/packages/JKorf.Coinbase.Net)|[Link](https://advanced.coinbase.com/join/T6H54H8)|-| |![Coinbase](https://raw.githubusercontent.com/JKorf/Coinbase.Net/refs/heads/main/Coinbase.Net/Icon/icon.png)|Coinbase|CEX|[JKorf/Coinbase.Net](https://github.com/JKorf/Coinbase.Net)|[![Nuget version](https://img.shields.io/nuget/v/JKorf.Coinbase.Net.svg?style=flat-square)](https://www.nuget.org/packages/JKorf.Coinbase.Net)|[Link](https://advanced.coinbase.com/join/T6H54H8)|-|
|![CoinEx](https://raw.githubusercontent.com/JKorf/CoinEx.Net/refs/heads/master/CoinEx.Net/Icon/icon.png)|CoinEx|CEX|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinEx.Net)|[Link](https://www.coinex.com/register?rc=rbtnp)|20%| |![CoinEx](https://raw.githubusercontent.com/JKorf/CoinEx.Net/refs/heads/master/CoinEx.Net/Icon/icon.png)|CoinEx|CEX|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinEx.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinEx.Net)|[Link](https://www.coinex.com/register?rc=rbtnp)|20%|
|![CoinW](https://raw.githubusercontent.com/JKorf/CoinW.Net/refs/heads/main/CoinW.Net/Icon/icon.png)|CoinW|CEX|[JKorf/CoinW.Net](https://github.com/JKorf/CoinW.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinW.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinW.Net)|[Link](https://www.coinw.com/register?rc=rbtnp)|-|
|![CoinGecko](https://raw.githubusercontent.com/JKorf/CoinGecko.Net/refs/heads/main/CoinGecko.Net/Icon/icon.png)|CoinGecko|-|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinGecko.Net)|-|-| |![CoinGecko](https://raw.githubusercontent.com/JKorf/CoinGecko.Net/refs/heads/main/CoinGecko.Net/Icon/icon.png)|CoinGecko|-|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[![Nuget version](https://img.shields.io/nuget/v/CoinGecko.net.svg?style=flat-square)](https://www.nuget.org/packages/CoinGecko.Net)|-|-|
|![Crypto.com](https://raw.githubusercontent.com/JKorf/CryptoCom.Net/refs/heads/main/CryptoCom.Net/Icon/icon.png)|Crypto.com|CEX|[JKorf/CryptoCom.Net](https://github.com/JKorf/CryptoCom.Net)|[![Nuget version](https://img.shields.io/nuget/v/CryptoCom.net.svg?style=flat-square)](https://www.nuget.org/packages/CryptoCom.Net)|[Link](https://crypto.com/exch/26ge92xbkn)|-| |![Crypto.com](https://raw.githubusercontent.com/JKorf/CryptoCom.Net/refs/heads/main/CryptoCom.Net/Icon/icon.png)|Crypto.com|CEX|[JKorf/CryptoCom.Net](https://github.com/JKorf/CryptoCom.Net)|[![Nuget version](https://img.shields.io/nuget/v/CryptoCom.net.svg?style=flat-square)](https://www.nuget.org/packages/CryptoCom.Net)|[Link](https://crypto.com/exch/26ge92xbkn)|-|
|![DeepCoin](https://raw.githubusercontent.com/JKorf/DeepCoin.Net/refs/heads/main/DeepCoin.Net/Icon/icon.png)|DeepCoin|CEX|[JKorf/DeepCoin.Net](https://github.com/JKorf/DeepCoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/DeepCoin.net.svg?style=flat-square)](https://www.nuget.org/packages/DeepCoin.Net)|[Link](https://s.deepcoin.com/jddhfca)|-| |![DeepCoin](https://raw.githubusercontent.com/JKorf/DeepCoin.Net/refs/heads/main/DeepCoin.Net/Icon/icon.png)|DeepCoin|CEX|[JKorf/DeepCoin.Net](https://github.com/JKorf/DeepCoin.Net)|[![Nuget version](https://img.shields.io/nuget/v/DeepCoin.net.svg?style=flat-square)](https://www.nuget.org/packages/DeepCoin.Net)|[Link](https://s.deepcoin.com/jddhfca)|-|
@ -38,10 +37,6 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
Any of these can be installed independently or install [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) which includes all exchange API's. Any of these can be installed independently or install [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) which includes all exchange API's.
### Full demo application
A full demo application is available using the [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) library:
https://github.com/JKorf/CryptoManager.Net
## Discord ## Discord
[![Nuget version](https://img.shields.io/discord/847020490588422145?style=for-the-badge)](https://discord.gg/MSpeEtSY8t) [![Nuget version](https://img.shields.io/discord/847020490588422145?style=for-the-badge)](https://discord.gg/MSpeEtSY8t)
A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries. A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries.
@ -63,32 +58,6 @@ 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 9.7.0 - 01 Sep 2025
* Added LibraryHelpers.CreateHttpClientMessageHandle to standardize HttpMessageHandler creation
* Added REST client option for selecting HTTP protocol version
* Added REST client option for HTTP client keep alive interval
* Added HttpVersion to WebCallResult responses
* Updated request logic to default to using HTTP version 2.0 for dotnet core
* Version 9.6.0 - 25 Aug 2025
* Added support for parsing REST response even though status indicates error
* Added better support for subscriptions without subscribe confirmation
* Added check in websocket for receiving 401 unauthorized http response status when 101 was expected
* Removed obsolete attribute on Error.Code property, updated the description
* 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
* Version 9.3.1 - 29 Jul 2025 * Version 9.3.1 - 29 Jul 2025
* Added BaseAndQuoteAssetAndContracts value to SharedQuantityType enum * Added BaseAndQuoteAssetAndContracts value to SharedQuantityType enum
* Added Id property to SharedPosition model * Added Id property to SharedPosition model