From f87506b490477fce6b5f7cff19b77f5beee6f7b5 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Wed, 15 Oct 2025 13:21:00 +0200 Subject: [PATCH] Added ITransferRestClient, updated Shared IBalanceRestClient to use SharedAccountType --- .../SystemTextJson/DateTimeConverter.cs | 4 +- CryptoExchange.Net/ExtensionMethods.cs | 34 ++++++- .../SharedApis/Enums/SharedAccountType.cs | 63 ++++++++++++ .../Interfaces/Rest/IBalanceRestClient.cs | 2 +- .../Interfaces/Rest/ITransferRestClient.cs | 25 +++++ .../Options/Endpoints/GetBalancesOptions.cs | 96 +++++++++++++++++++ .../Options/Endpoints/TransferOptions.cs | 42 ++++++++ .../Models/Rest/GetBalancesRequest.cs | 18 +++- .../SharedApis/Models/Rest/TransferRequest.cs | 61 ++++++++++++ .../ResponseModels/SharedBalance.cs | 1 + .../Comparers/SystemTextJsonComparer.cs | 2 +- 11 files changed, 339 insertions(+), 9 deletions(-) create mode 100644 CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs create mode 100644 CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs create mode 100644 CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs create mode 100644 CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs create mode 100644 CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs diff --git a/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs index b3438b1..5180641 100644 --- a/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs +++ b/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs @@ -88,13 +88,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson } /// - /// Parse a long value to datetime + /// Parse a double value to datetime /// public static DateTime ParseFromDouble(double value) => ParseFromDecimal((decimal)value); /// - /// Parse a long value to datetime + /// Parse a decimal value to datetime /// public static DateTime ParseFromDecimal(decimal value) { diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs index dbe6cf9..702b3f5 100644 --- a/CryptoExchange.Net/ExtensionMethods.cs +++ b/CryptoExchange.Net/ExtensionMethods.cs @@ -399,7 +399,7 @@ namespace CryptoExchange.Net /// /// Whether the trading mode is linear /// - public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear; + public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear; /// /// Whether the trading mode is inverse @@ -416,6 +416,36 @@ namespace CryptoExchange.Net /// public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear; + /// + /// Whether the account type is a futures account + /// + public static bool IsFuturesAccount(this SharedAccountType type) => + type == SharedAccountType.PerpetualLinearFutures + || type == SharedAccountType.DeliveryLinearFutures + || type == SharedAccountType.PerpetualInverseFutures + || type == SharedAccountType.DeliveryInverseFutures; + + /// + /// Whether the account type is a margin account + /// + public static bool IsMarginAccount(this SharedAccountType type) => + type == SharedAccountType.CrossMargin + || type == SharedAccountType.IsolatedMargin; + + /// + /// Map a TradingMode value to a SharedAccountType enum value + /// + public static SharedAccountType ToAccountType(this TradingMode mode) + { + if (mode == TradingMode.Spot) return SharedAccountType.Spot; + if (mode == TradingMode.PerpetualLinear) return SharedAccountType.PerpetualLinearFutures; + if (mode == TradingMode.PerpetualInverse) return SharedAccountType.PerpetualInverseFutures; + if (mode == TradingMode.DeliveryInverse) return SharedAccountType.DeliveryInverseFutures; + if (mode == TradingMode.DeliveryLinear) return SharedAccountType.DeliveryLinearFutures; + + throw new ArgumentException(nameof(mode), "Unmapped trading mode"); + } + /// /// Register rest client interfaces /// @@ -445,6 +475,8 @@ namespace CryptoExchange.Net services.AddTransient(x => (IFeeRestClient)client(x)!); if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T))) services.AddTransient(x => (IBookTickerRestClient)client(x)!); + if (typeof(ITransferRestClient).IsAssignableFrom(typeof(T))) + services.AddTransient(x => (ITransferRestClient)client(x)!); if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T))) services.AddTransient(x => (ISpotOrderRestClient)client(x)!); diff --git a/CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs b/CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs new file mode 100644 index 0000000..e3bb862 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Account type + /// + public enum SharedAccountType + { + /// + /// Unified account, combined account for multiple different types of trading + /// + Unified, + + /// + /// Funding account, where withdrawals and deposits are made from and to + /// + Funding, + + /// + /// Spot trading account + /// + Spot, + + /// + /// Cross margin account + /// + CrossMargin, + /// + /// Isolated margin account + /// + IsolatedMargin, + + /// + /// Perpetual linear futures account + /// + PerpetualLinearFutures, + /// + /// Delivery linear futures account + /// + DeliveryLinearFutures, + /// + /// Perpetual inverse futures account + /// + PerpetualInverseFutures, + /// + /// Delivery inverse futures account + /// + DeliveryInverseFutures, + + /// + /// Option account + /// + Option, + + /// + /// Other + /// + Other + } +} diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs index 8983c61..8190fc4 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs @@ -12,7 +12,7 @@ namespace CryptoExchange.Net.SharedApis /// /// Balances request options /// - EndpointOptions GetBalancesOptions { get; } + GetBalancesOptions GetBalancesOptions { get; } /// /// Get balances for the user diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs new file mode 100644 index 0000000..23eabab --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs @@ -0,0 +1,25 @@ +using CryptoExchange.Net.Objects; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Client for transferring funds between account types + /// + public interface ITransferRestClient : ISharedClient + { + /// + /// Transfer request options + /// + TransferOptions TransferOptions { get; } + + /// + /// Transfer funds between account types + /// + /// Request info + /// Cancellation token + Task> TransferAsync(TransferRequest request, CancellationToken ct = default); + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs new file mode 100644 index 0000000..1355cbb --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs @@ -0,0 +1,96 @@ +using CryptoExchange.Net.Objects; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting a transfer + /// + public class GetBalancesOptions : EndpointOptions + { + /// + /// Supported account types + /// + public AccountTypeFilter[] SupportedAccountTypes { get; set; } + + /// + /// ctor + /// + public GetBalancesOptions(params AccountTypeFilter[] accountTypes) : base(true) + { + SupportedAccountTypes = accountTypes; + } + + /// + /// Validate a request + /// + public Error? ValidateRequest( + string exchange, + GetBalancesRequest request, + TradingMode[] supportedApiTypes) + { + if (request.AccountType != null && !IsValid(request.AccountType.Value)) + return ArgumentError.Invalid(nameof(request.AccountType), "Invalid AccountType"); + + return base.ValidateRequest(exchange, request, null, supportedApiTypes); + } + + /// + /// Is the account type valid for this client + /// + /// + /// + public bool IsValid(SharedAccountType accountType) + { + if (accountType == SharedAccountType.Funding) + return SupportedAccountTypes.Contains(AccountTypeFilter.Funding); + + if (accountType == SharedAccountType.Spot) + return SupportedAccountTypes.Contains(AccountTypeFilter.Spot); + + if (accountType == SharedAccountType.PerpetualLinearFutures + || accountType == SharedAccountType.PerpetualInverseFutures + || accountType == SharedAccountType.DeliveryLinearFutures + || accountType == SharedAccountType.DeliveryInverseFutures) + { + return SupportedAccountTypes.Contains(AccountTypeFilter.Futures); + } + + + if (accountType == SharedAccountType.CrossMargin + || accountType == SharedAccountType.IsolatedMargin) + { + return SupportedAccountTypes.Contains(AccountTypeFilter.Margin); + } + + return SupportedAccountTypes.Contains(AccountTypeFilter.Option); + } + } + + /// + /// Account type filter + /// + public enum AccountTypeFilter + { + /// + /// Funding account + /// + Funding, + /// + /// Spot account + /// + Spot, + /// + /// Futures account + /// + Futures, + /// + /// Margin account + /// + Margin, + /// + /// Option account + /// + Option + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs new file mode 100644 index 0000000..c0abf9e --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs @@ -0,0 +1,42 @@ +using CryptoExchange.Net.Objects; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting a transfer + /// + public class TransferOptions : EndpointOptions + { + /// + /// Supported account types + /// + public SharedAccountType[] SupportedAccountTypes { get; set; } + + /// + /// ctor + /// + public TransferOptions(SharedAccountType[] accountTypes) : base(true) + { + SupportedAccountTypes = accountTypes; + } + + /// + /// Validate a request + /// + public new Error? ValidateRequest( + string exchange, + TransferRequest request, + TradingMode? tradingMode, + TradingMode[] supportedApiTypes) + { + if (!SupportedAccountTypes.Contains(request.FromAccountType)) + return ArgumentError.Invalid(nameof(request.FromAccountType), "Invalid FromAccountType"); + + if (!SupportedAccountTypes.Contains(request.ToAccountType)) + return ArgumentError.Invalid(nameof(request.FromAccountType), "Invalid ToAccountType"); + + return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs index 2cab40b..50a4852 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs @@ -8,18 +8,28 @@ namespace CryptoExchange.Net.SharedApis public record GetBalancesRequest : SharedRequest { /// - /// Trading mode + /// Account type /// - public TradingMode? TradingMode { get; set; } + public SharedAccountType? AccountType { get; set; } /// /// ctor /// /// Trading mode /// Exchange specific parameters - public GetBalancesRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + public GetBalancesRequest(TradingMode tradingMode, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) { - TradingMode = tradingMode; + AccountType = tradingMode.ToAccountType(); + } + + /// + /// ctor + /// + /// Account type + /// Exchange specific parameters + public GetBalancesRequest(SharedAccountType? accountType = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + { + AccountType = accountType; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs new file mode 100644 index 0000000..2b5908b --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs @@ -0,0 +1,61 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Request to transfer funds between account types + /// + public record TransferRequest : SharedRequest + { + /// + /// Asset + /// + public string Asset { get; set; } + /// + /// Quantity + /// + public decimal Quantity { get; set; } + /// + /// From symbol + /// + public string? FromSymbol { get; set; } + /// + /// To symbol + /// + public string? ToSymbol { get; set; } + + /// + /// From account type + /// + public SharedAccountType FromAccountType { get; set; } + /// + /// To account type + /// + public SharedAccountType ToAccountType { get; set; } + + /// + /// ctor + /// + /// The asset to transfer + /// Quantity to transfer + /// From account type + /// To account type + /// From symbol + /// To symbol + /// Exchange specific parameters + public TransferRequest( + string asset, + decimal quantity, + SharedAccountType fromAccount, + SharedAccountType toAccount, + string? fromSymbol = null, + string? toSymbol = null, + ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + { + Asset = asset; + Quantity = quantity; + FromAccountType = fromAccount; + ToAccountType = toAccount; + FromSymbol = fromSymbol; + ToSymbol = toSymbol; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs b/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs index ee622d1..23ea5dd 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs +++ b/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs @@ -17,6 +17,7 @@ /// Total quantity /// public decimal Total { get; set; } + /// /// Isolated margin symbol, only applicable for isolated margin futures /// diff --git a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs index eaa61d6..b851882 100644 --- a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs +++ b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs @@ -415,7 +415,7 @@ namespace CryptoExchange.Net.Testing.Comparers var value = jsonValue.GetDecimal(); if (objectValue is DateTime time) { - if (time != DateTimeConverter.ParseFromDouble((double)value)) + if (time != DateTimeConverter.ParseFromDecimal(value)) throw new Exception($"{method}: {property} not equal: {DateTimeConverter.ParseFromDouble((double)value!)} vs {time}"); } else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true)