Skip to content

Commit 3011825

Browse files
authored
CryptoExchange.Net update v9.5.0 (#137)
* Added improved error parsing * Updated rest request sending too prevent duplicate parameter serialization * Updated restClient.SpotApi.Trading.PlaceMultipleOrderAsync to return CallResult objects allowing for improved error handling * Fixed restClient.UsdtFuturesApi.Account.GetTradingStatusAsync endpoint deserialization
1 parent 39b9ea4 commit 3011825

33 files changed

+433
-172
lines changed

HTX.Net.UnitTests/Endpoints/UsdtMarginSwap/Account/GetTradingStatus.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ GET
33
true
44
{
55
"status": "ok",
6-
"data": [
6+
"data":
77
{
88
"is_disable": 1,
99
"order_price_types": "limit,post_only,FOK,IOC",
@@ -26,6 +26,6 @@ true
2626
"is_active": 1
2727
}
2828
}
29-
],
29+
,
3030
"ts": 158797866555
3131
}

HTX.Net.UnitTests/HTXRestIntegrationTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using HTX.Net.Enums;
1313
using Microsoft.Extensions.Options;
1414
using HTX.Net.SymbolOrderBooks;
15+
using CryptoExchange.Net.Objects.Errors;
1516

1617
namespace HTX.Net.UnitTests
1718
{
@@ -46,7 +47,7 @@ public async Task TestErrorResponseParsing()
4647
var result = await CreateClient().SpotApi.ExchangeData.GetKlinesAsync("TSTTST", Enums.KlineInterval.OneDay, default);
4748

4849
Assert.That(result.Success, Is.False);
49-
Assert.That(result.Error.Message, Contains.Substring("invalid-parameter"));
50+
Assert.That(result.Error.ErrorType, Is.EqualTo(ErrorType.UnknownSymbol));
5051
}
5152

5253
[Test]

HTX.Net.UnitTests/RestRequestTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public async Task ValidateSpotTradingCalls()
134134
});
135135
var tester = new RestRequestValidator<HTXRestClient>(client, "Endpoints/Spot/Trading", "https://api.huobi.pro", IsAuthenticated, nestedPropertyForCompare: "data");
136136
await tester.ValidateAsync(client => client.SpotApi.Trading.PlaceOrderAsync(1, "ETHUSDT", Enums.OrderSide.Buy, Enums.OrderType.IOC, 1), "PlaceOrder");
137-
await tester.ValidateAsync(client => client.SpotApi.Trading.PlaceMultipleOrderAsync(new [] { new HTXOrderRequest { } }), "PlaceMultipleOrder");
137+
await tester.ValidateAsync(client => client.SpotApi.Trading.PlaceMultipleOrderAsync(new [] { new HTXOrderRequest { } }), "PlaceMultipleOrder", skipResponseValidation: true);
138138
await tester.ValidateAsync(client => client.SpotApi.Trading.PlaceMarginOrderAsync(1, "ETHUSDT", Enums.OrderSide.Buy, Enums.OrderType.IOC, Enums.MarginPurpose.AutomaticLoan, Enums.SourceType.C2CMargin), "PlaceMarginOrder");
139139
await tester.ValidateAsync(client => client.SpotApi.Trading.CancelOrderAsync(1), "CancelOrder");
140140
await tester.ValidateAsync(client => client.SpotApi.Trading.CancelOrderByClientOrderIdAsync("1"), "CancelOrderByClientOrderId");

HTX.Net/Clients/SpotApi/HTXRestClientSpotApi.cs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using CryptoExchange.Net.Clients;
22
using CryptoExchange.Net.Converters.MessageParsing;
3+
using CryptoExchange.Net.Objects.Errors;
34
using CryptoExchange.Net.SharedApis;
4-
using HTX.Net.Enums;
55
using HTX.Net.Interfaces.Clients.SpotApi;
66
using HTX.Net.Objects.Internal;
77
using HTX.Net.Objects.Options;
8+
using System;
89

910
namespace HTX.Net.Clients.SpotApi
1011
{
@@ -16,6 +17,8 @@ internal partial class HTXRestClientSpotApi : RestApiClient, IHTXRestClientSpotA
1617

1718
internal static TimeSyncState _timeSyncState = new TimeSyncState("Spot Api");
1819

20+
protected override ErrorMapping ErrorMapping => HTXErrors.SpotMapping;
21+
1922
/// <inheritdoc />
2023
public string ExchangeName => "HTX";
2124

@@ -75,7 +78,7 @@ internal async Task<WebCallResult<T>> SendToAddressAsync<T>(string baseAddress,
7578
return result.AsError<T>(result.Error!);
7679

7780
if (result.Data.Code != 200)
78-
return result.AsError<T>(new ServerError(result.Data.Code, result.Data.Message));
81+
return result.AsError<T>(new ServerError(result.Data.Code, GetErrorInfo(result.Data.Code, result.Data.Message)));
7982

8083
return result.As(result.Data.Data);
8184
}
@@ -90,7 +93,7 @@ internal async Task<WebCallResult<T>> SendToAddressAsync<T>(string baseAddress,
9093
return result.AsError<(T, DateTime)>(result.Error!);
9194

9295
if (result.Data.ErrorCode != null)
93-
return result.AsError<(T, DateTime)>(new ServerError($"{result.Data.ErrorCode}-{result.Data.ErrorMessage}"));
96+
return result.AsError<(T, DateTime)>(new ServerError(result.Data.ErrorCode, GetErrorInfo(result.Data.ErrorCode, result.Data.ErrorMessage)));
9497

9598
return result.As((result.Data.Data, result.Data.Timestamp));
9699
}
@@ -105,7 +108,7 @@ internal async Task<WebCallResult> SendBasicToAddressAsync(string baseAddress, R
105108
return result.AsDatalessError(result.Error!);
106109

107110
if (!string.IsNullOrEmpty(result.Data.ErrorCode))
108-
return result.AsDatalessError(new ServerError($"{result.Data.ErrorCode}, {result.Data.ErrorMessage}"));
111+
return result.AsDatalessError(new ServerError(result.Data.ErrorCode!, GetErrorInfo(result.Data.ErrorCode!, result.Data.ErrorMessage)));
109112

110113
return result.AsDataless();
111114

@@ -121,7 +124,7 @@ internal async Task<WebCallResult<T>> SendBasicToAddressAsync<T>(string baseAddr
121124
return result.AsError<T>(result.Error!);
122125

123126
if (!string.IsNullOrEmpty(result.Data.ErrorCode))
124-
return result.AsError<T>(new ServerError($"{result.Data.ErrorCode}, {result.Data.ErrorMessage}"));
127+
return result.AsError<T>(new ServerError(result.Data.ErrorCode!, GetErrorInfo(result.Data.ErrorCode!, result.Data.ErrorMessage)));
125128

126129
return result.As(result.Data.Data);
127130
}
@@ -130,32 +133,32 @@ internal async Task<WebCallResult<T>> SendBasicToAddressAsync<T>(string baseAddr
130133
protected override Error ParseErrorResponse(int httpStatusCode, KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor, Exception? exception)
131134
{
132135
if (!accessor.IsValid)
133-
return new ServerError(null, "Unknown request error", exception: exception);
136+
return new ServerError(ErrorInfo.Unknown, exception: exception);
134137

135138
var code = accessor.GetValue<string>(MessagePath.Get().Property("err-code"));
136139
var msg = accessor.GetValue<string>(MessagePath.Get().Property("err-msg"));
137140

138141
if (code == null || msg == null)
139-
return new ServerError(null, "Unknown request error", exception: exception);
142+
return new ServerError(ErrorInfo.Unknown, exception: exception);
140143

141-
return new ServerError(null, $"{code}, {msg}", exception);
144+
return new ServerError(code!, GetErrorInfo(code, msg), exception);
142145
}
143146

144147
/// <inheritdoc />
145148
protected override Error? TryParseError(KeyValuePair<string, string[]>[] responseHeaders, IMessageAccessor accessor)
146149
{
147150
if (!accessor.IsValid)
148-
return new ServerError(accessor.GetOriginalString());
151+
return new ServerError(ErrorInfo.Unknown);
149152

150153
var code = accessor.GetValue<int?>(MessagePath.Get().Property("code"));
151154
var errCode = accessor.GetValue<string>(MessagePath.Get().Property("err-code"));
152155
var msg = accessor.GetValue<string>(MessagePath.Get().Property("message")) ?? accessor.GetValue<string>(MessagePath.Get().Property("err-msg"));
153156

154157
if (code > 0 && code != 200)
155-
return new ServerError(code!.Value, msg!);
158+
return new ServerError(code.Value!, GetErrorInfo(code.Value!, msg));
156159

157160
if (!string.IsNullOrEmpty(errCode))
158-
return new ServerError($"{errCode}: {msg}");
161+
return new ServerError(errCode!, GetErrorInfo(errCode!, msg));
159162

160163
return null;
161164
}

HTX.Net/Clients/SpotApi/HTXRestClientSpotApiAccount.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using HTX.Net.Interfaces.Clients.SpotApi;
44
using HTX.Net.Objects.Internal;
55
using CryptoExchange.Net.RateLimiting.Guards;
6+
using CryptoExchange.Net.Objects.Errors;
67

78
namespace HTX.Net.Clients.SpotApi
89
{
@@ -347,7 +348,7 @@ public async Task<WebCallResult<HTXWithdrawDeposit>> GetWithdrawalByClientOrderI
347348
new SingleLimitGuard(20, TimeSpan.FromSeconds(2), RateLimitWindowType.Sliding, keySelector: SingleLimitGuard.PerApiKey));
348349
var result = await _baseClient.SendBasicAsync<HTXWithdrawDeposit>(request, parameters, ct).ConfigureAwait(false);
349350
if (result.Data == null)
350-
return new WebCallResult<HTXWithdrawDeposit>(new ServerError("Not found"));
351+
return new WebCallResult<HTXWithdrawDeposit>(new ServerError(new ErrorInfo(ErrorType.Unknown, "Not found")));
351352

352353
return result;
353354
}

HTX.Net/Clients/SpotApi/HTXRestClientSpotApiShared.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CryptoExchange.Net.SharedApis;
33
using HTX.Net.Enums;
44
using HTX.Net.Objects.Models;
5+
using CryptoExchange.Net.Objects.Errors;
56

67
namespace HTX.Net.Clients.SpotApi
78
{
@@ -31,7 +32,7 @@ async Task<ExchangeWebResult<SharedKline[]>> IKlineRestClient.GetKlinesAsync(Get
3132
{
3233
var interval = (Enums.KlineInterval)request.Interval;
3334
if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval))
34-
return new ExchangeWebResult<SharedKline[]>(Exchange, new ArgumentError("Interval not supported"));
35+
return new ExchangeWebResult<SharedKline[]>(Exchange, ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), "Interval not supported"));
3536

3637
var validationError = ((IKlineRestClient)this).GetKlinesOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes);
3738
if (validationError != null)
@@ -47,7 +48,7 @@ async Task<ExchangeWebResult<SharedKline[]>> IKlineRestClient.GetKlinesAsync(Get
4748
{
4849
// Not available via the API
4950
var cutoff = DateTime.UtcNow.AddSeconds(-(int)request.Interval * apiLimit);
50-
return new ExchangeWebResult<SharedKline[]>(Exchange, new ArgumentError($"Time filter outside of supported range. Can only request the most recent {apiLimit} klines i.e. data later than {cutoff} at this interval"));
51+
return new ExchangeWebResult<SharedKline[]>(Exchange, ArgumentError.Invalid(nameof(GetKlinesRequest.Limit), $"Time filter outside of supported range. Can only request the most recent {apiLimit} klines i.e. data later than {cutoff} at this interval"));
5152
}
5253

5354
// Pagination not supported, no time filter available
@@ -310,7 +311,7 @@ async Task<ExchangeWebResult<SharedSpotOrder>> ISpotOrderRestClient.GetSpotOrder
310311
return new ExchangeWebResult<SharedSpotOrder>(Exchange, validationError);
311312

312313
if (!long.TryParse(request.OrderId, out var orderId))
313-
return new ExchangeWebResult<SharedSpotOrder>(Exchange, new ArgumentError("Invalid order id"));
314+
return new ExchangeWebResult<SharedSpotOrder>(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest), "Invalid order id"));
314315

315316
var order = await Trading.GetOrderAsync(orderId).ConfigureAwait(false);
316317
if (!order)
@@ -421,7 +422,7 @@ async Task<ExchangeWebResult<SharedUserTrade[]>> ISpotOrderRestClient.GetSpotOrd
421422
return new ExchangeWebResult<SharedUserTrade[]>(Exchange, validationError);
422423

423424
if (!long.TryParse(request.OrderId, out var orderId))
424-
return new ExchangeWebResult<SharedUserTrade[]>(Exchange, new ArgumentError("Invalid order id"));
425+
return new ExchangeWebResult<SharedUserTrade[]>(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest), "Invalid order id"));
425426

426427
var order = await Trading.GetOrderTradesAsync(orderId).ConfigureAwait(false);
427428
if (!order)
@@ -498,7 +499,7 @@ async Task<ExchangeWebResult<SharedId>> ISpotOrderRestClient.CancelSpotOrderAsyn
498499
return new ExchangeWebResult<SharedId>(Exchange, validationError);
499500

500501
if (!long.TryParse(request.OrderId, out var orderId))
501-
return new ExchangeWebResult<SharedId>(Exchange, new ArgumentError("Invalid order id"));
502+
return new ExchangeWebResult<SharedId>(Exchange, ArgumentError.Invalid(nameof(CancelOrderRequest), "Invalid order id"));
502503

503504
var order = await Trading.CancelOrderAsync(orderId).ConfigureAwait(false);
504505
if (!order)
@@ -607,7 +608,7 @@ async Task<ExchangeWebResult<SharedAsset>> IAssetsRestClient.GetAssetAsync(GetAs
607608

608609
var asset = assets.Data.SingleOrDefault();
609610
if (asset == null)
610-
return assets.AsExchangeError<SharedAsset>(Exchange, new ServerError("Asset not found"));
611+
return assets.AsExchangeError<SharedAsset>(Exchange, new ServerError(new ErrorInfo(ErrorType.UnknownAsset, "Asset not found")));
611612

612613
return assets.AsExchangeResult(Exchange, TradingMode.Spot, new SharedAsset(asset.Asset.ToUpperInvariant())
613614
{
@@ -792,13 +793,11 @@ async Task<ExchangeWebResult<SharedId>> IWithdrawRestClient.WithdrawAsync(Withdr
792793
return new ExchangeWebResult<SharedId>(Exchange, validationError);
793794

794795
var fee = ExchangeParameters.GetValue<decimal?>(request.ExchangeParameters, Exchange, "withdrawFee");
795-
if (fee == null)
796-
return new ExchangeWebResult<SharedId>(Exchange, new ArgumentError("HTX requires withdrawal fee parameter. Please pass it as exchangeParameter `fee`"));
797796

798797
// Get data
799798
var withdrawal = await Account.WithdrawAsync(
800799
asset: request.Asset,
801-
fee: fee.Value,
800+
fee: fee!.Value,
802801
address: request.Address,
803802
quantity: request.Quantity,
804803
network: request.Network,
@@ -887,7 +886,7 @@ async Task<ExchangeWebResult<SharedSpotTriggerOrder>> ISpotTriggerOrderRestClien
887886
return new ExchangeWebResult<SharedSpotTriggerOrder>(Exchange, validationError);
888887

889888
if (!long.TryParse(request.OrderId, out var orderId))
890-
return new ExchangeWebResult<SharedSpotTriggerOrder>(Exchange, new ArgumentError("Invalid order id"));
889+
return new ExchangeWebResult<SharedSpotTriggerOrder>(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest), "Invalid order id"));
891890

892891
var order = await Trading.GetOrderAsync(orderId, ct: ct).ConfigureAwait(false);
893892
if (!order)
@@ -932,7 +931,7 @@ async Task<ExchangeWebResult<SharedId>> ISpotTriggerOrderRestClient.CancelSpotTr
932931
return new ExchangeWebResult<SharedId>(Exchange, validationError);
933932

934933
if (!long.TryParse(request.OrderId, out var orderId))
935-
return new ExchangeWebResult<SharedId>(Exchange, new ArgumentError("Invalid order id"));
934+
return new ExchangeWebResult<SharedId>(Exchange, ArgumentError.Invalid(nameof(CancelOrderRequest), "Invalid order id"));
936935

937936
var order = await Trading.CancelOrderAsync(
938937
orderId,

HTX.Net/Clients/SpotApi/HTXRestClientSpotApiTrading.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using HTX.Net.Objects.Models;
33
using HTX.Net.Interfaces.Clients.SpotApi;
44
using CryptoExchange.Net.RateLimiting.Guards;
5+
using CryptoExchange.Net.Objects.Errors;
56

67
namespace HTX.Net.Clients.SpotApi
78
{
@@ -62,7 +63,7 @@ public async Task<WebCallResult<long>> PlaceOrderAsync(
6263
#region Place Multiple Order
6364

6465
/// <inheritdoc />
65-
public async Task<WebCallResult<HTXBatchPlaceResult[]>> PlaceMultipleOrderAsync(
66+
public async Task<WebCallResult<CallResult<HTXBatchPlaceResult>[]>> PlaceMultipleOrderAsync(
6667
IEnumerable<HTXOrderRequest> orders,
6768
CancellationToken ct = default)
6869
{
@@ -93,8 +94,23 @@ public async Task<WebCallResult<HTXBatchPlaceResult[]>> PlaceMultipleOrderAsync(
9394

9495
var request = _definitions.GetOrCreate(HttpMethod.Post, "v1/order/batch-orders", HTXExchange.RateLimiter.EndpointLimit, 1, true,
9596
new SingleLimitGuard(20, TimeSpan.FromSeconds(2), RateLimitWindowType.Sliding, keySelector: SingleLimitGuard.PerApiKey));
96-
var result = await _baseClient.SendBasicAsync<HTXBatchPlaceResult[]>(request, orderParameters, ct).ConfigureAwait(false);
97-
return result;
97+
var response = await _baseClient.SendBasicAsync<HTXBatchPlaceResult[]>(request, orderParameters, ct).ConfigureAwait(false);
98+
99+
if (!response.Success)
100+
return response.As<CallResult<HTXBatchPlaceResult>[]>(default);
101+
102+
var result = new List<CallResult<HTXBatchPlaceResult>>();
103+
foreach (var item in response.Data)
104+
{
105+
result.Add(!string.IsNullOrEmpty(item.ErrorCode)
106+
? new CallResult<HTXBatchPlaceResult>(new ServerError(item.ErrorCode!, _baseClient.GetErrorInfo(item.ErrorCode!, item.ErrorMessage)))
107+
: new CallResult<HTXBatchPlaceResult>(item));
108+
}
109+
110+
if (result.All(x => !x.Success))
111+
return response.AsErrorWithData(new ServerError(new ErrorInfo(ErrorType.AllOrdersFailed, false, "All orders failed")), result.ToArray());
112+
113+
return response.As(result.ToArray());
98114
}
99115

100116
#endregion

0 commit comments

Comments
 (0)