From 318ee78f9403a0345b7f198a9a040267bc6b40b6 Mon Sep 17 00:00:00 2001 From: Tomas Bezouska Date: Mon, 20 Sep 2021 12:49:08 +0200 Subject: [PATCH 1/4] [Binance] sapi endpoint, cleanup --- .../BinanceGroup/BinanceGroupCommon.cs | 184 +++++------------- .../BinanceGroup/ExchangeBinanceAPI.cs | 5 +- .../BinanceGroup/ExchangeBinanceJerseyAPI.cs | 5 +- .../BinanceGroup/ExchangeBinanceUSAPI.cs | 5 +- .../Exchanges/BinanceGroup/HistoryRecord.cs | 40 ++++ .../Exchanges/BinanceGroup/Models/Currency.cs | 154 --------------- 6 files changed, 87 insertions(+), 306 deletions(-) create mode 100644 src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs delete mode 100644 src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs index 392a0019e..6f9a0259b 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs @@ -10,6 +10,7 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #nullable enable +using ExchangeSharp.API.Exchanges.BinanceGroup; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -22,14 +23,9 @@ namespace ExchangeSharp.BinanceGroup { public abstract class BinanceGroupCommon : ExchangeAPI { - public abstract string BaseUrlPrivate { get; set; } - public abstract string WithdrawalUrlPrivate { get; set; } - /// - /// base address for APIs used by the Binance website and not published in the API docs - /// - public abstract string BaseWebUrl { get; set; } + public string BaseUrlApi => $"{BaseUrl}/api/v3"; - public const string GetCurrenciesUrl = "/assetWithdraw/getAllAsset.html"; + public string BaseUrlUrlSApi => $"{BaseUrl}/sapi/v1"; protected async Task GetWebSocketStreamUrlForSymbolsAsync(string suffix, params string[] marketSymbols) { @@ -189,30 +185,6 @@ protected internal override async Task> OnGetMarketS return markets; } - protected override async Task> OnGetCurrenciesAsync() - { - // https://www.binance.com/assetWithdraw/getAllAsset.html - Dictionary allCoins = new Dictionary(StringComparer.OrdinalIgnoreCase); - - List currencies = await MakeJsonRequestAsync>(GetCurrenciesUrl, BaseWebUrl); - foreach (Currency coin in currencies) - { - allCoins[coin.AssetCode] = new ExchangeCurrency - { - CoinType = coin.ParentCode, - DepositEnabled = coin.EnableCharge, - FullName = coin.AssetName, - MinConfirmations = coin.ConfirmTimes.ConvertInvariant(), - Name = coin.AssetCode, - TxFee = coin.TransactionFee, - WithdrawalEnabled = coin.EnableWithdraw, - MinWithdrawalSize = coin.MinProductWithdraw.ConvertInvariant(), - }; - } - - return allCoins; - } - protected override async Task OnGetTickerAsync(string marketSymbol) { JToken obj = await MakeJsonRequestAsync("/ticker/24hr?symbol=" + marketSymbol); @@ -517,7 +489,7 @@ protected override async Task> OnGetCandlesAsync(strin protected override async Task> OnGetAmountsAsync() { - JToken token = await MakeJsonRequestAsync("/account", BaseUrlPrivate, await GetNoncePayloadAsync()); + JToken token = await MakeJsonRequestAsync("/account", BaseUrlApi, await GetNoncePayloadAsync()); Dictionary balances = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (JToken balance in token["balances"]) { @@ -532,7 +504,7 @@ protected override async Task> OnGetAmountsAsync() protected override async Task> OnGetAmountsAvailableToTradeAsync() { - JToken token = await MakeJsonRequestAsync("/account", BaseUrlPrivate, await GetNoncePayloadAsync()); + JToken token = await MakeJsonRequestAsync("/account", BaseUrlApi, await GetNoncePayloadAsync()); Dictionary balances = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (JToken balance in token["balances"]) { @@ -577,7 +549,7 @@ protected override async Task OnPlaceOrderAsync(ExchangeOrd } order.ExtraParameters.CopyTo(payload); - JToken? token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "POST"); + JToken? token = await MakeJsonRequestAsync("/order", BaseUrlApi, payload, "POST"); if (token is null) { return null; @@ -599,13 +571,13 @@ protected override async Task OnGetOrderDetailsAsync(string else payload["orderId"] = orderId; - JToken token = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload); + JToken token = await MakeJsonRequestAsync("/order", BaseUrlApi, payload); ExchangeOrderResult result = ParseOrder(token); // Add up the fees from each trade in the order Dictionary feesPayload = await GetNoncePayloadAsync(); feesPayload["symbol"] = marketSymbol!; - JToken feesToken = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, feesPayload); + JToken feesToken = await MakeJsonRequestAsync("/myTrades", BaseUrlApi, feesPayload); ParseFees(feesToken, result); return result; @@ -640,7 +612,7 @@ protected override async Task> OnGetOpenOrderDe { payload["symbol"] = marketSymbol!; } - JToken token = await MakeJsonRequestAsync("/openOrders", BaseUrlPrivate, payload); + JToken token = await MakeJsonRequestAsync("/openOrders", BaseUrlApi, payload); foreach (JToken order in token) { orders.Add(ParseOrder(order)); @@ -703,74 +675,13 @@ protected override async Task> OnGetCompletedOr { payload["startTime"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds(); } - JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, payload); + JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlApi, payload); foreach (JToken trade in token) { trades.Add(ParseTrade(trade, marketSymbol!)); } } return trades; - - //old way - - //List orders = new List(); - //if (string.IsNullOrWhiteSpace(marketSymbol)) - //{ - // orders.AddRange(await GetCompletedOrdersForAllSymbolsAsync(afterDate)); - //} - //else - //{ - // Dictionary payload = await GetNoncePayloadAsync(); - // payload["symbol"] = marketSymbol; - // if (afterDate != null) - // { - // payload["startTime"] = Math.Round(afterDate.Value.UnixTimestampFromDateTimeMilliseconds()); - // } - // JToken token = await MakeJsonRequestAsync("/allOrders", BaseUrlPrivate, payload); - // foreach (JToken order in token) - // { - // orders.Add(ParseOrder(order)); - // } - //} - //return orders; - } - - private async Task> GetMyTradesForAllSymbols(DateTime? afterDate) - { - // TODO: This is a HACK, Binance API needs to add a single API call to get all orders for all symbols, terrible... - List trades = new List(); - Exception? ex = null; - string? failedSymbol = null; - Parallel.ForEach((await GetMarketSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) => - { - try - { - foreach (ExchangeOrderResult trade in (await GetMyTradesAsync(s, afterDate))) - { - lock (trades) - { - trades.Add(trade); - } - } - } - catch (Exception _ex) - { - failedSymbol = s; - ex = _ex; - } - }); - - if (ex != null) - { - throw new APIException("Failed to get my trades for symbol " + failedSymbol, ex); - } - - // sort timestamp desc - trades.Sort((o1, o2) => - { - return o2.OrderDate.CompareTo(o1.OrderDate); - }); - return trades; } private async Task> OnGetMyTradesAsync(string? marketSymbol = null, DateTime? afterDate = null) @@ -788,7 +699,7 @@ private async Task> OnGetMyTradesAsync(string? { payload["timestamp"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds(); } - JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlPrivate, payload); + JToken token = await MakeJsonRequestAsync("/myTrades", BaseUrlApi, payload); foreach (JToken trade in token) { trades.Add(ParseTrade(trade, marketSymbol!)); @@ -806,7 +717,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string? marketS } payload["symbol"] = marketSymbol!; payload["orderId"] = orderId; - _ = await MakeJsonRequestAsync("/order", BaseUrlPrivate, payload, "DELETE"); + _ = await MakeJsonRequestAsync("/order", BaseUrlApi, payload, "DELETE"); } /// A withdrawal request. Fee is automatically subtracted from the amount. @@ -828,17 +739,21 @@ protected override async Task OnWithdrawAsync(Exchan } Dictionary payload = await GetNoncePayloadAsync(); - payload["asset"] = withdrawalRequest.Currency; + payload["coin"] = withdrawalRequest.Currency; payload["address"] = withdrawalRequest.Address; payload["amount"] = withdrawalRequest.Amount; - payload["name"] = withdrawalRequest.Description ?? "apiwithdrawal"; // Contrary to what the API docs say, name is required + + if (!string.IsNullOrWhiteSpace(withdrawalRequest.Description)) + { + payload["name"] = withdrawalRequest.Description; + } if (!string.IsNullOrWhiteSpace(withdrawalRequest.AddressTag)) { payload["addressTag"] = withdrawalRequest.AddressTag; } - JToken response = await MakeJsonRequestAsync("/withdraw.html", WithdrawalUrlPrivate, payload, "POST"); + JToken response = await MakeJsonRequestAsync("/capital/withdraw/apply", BaseUrlUrlSApi, payload, "POST"); ExchangeWithdrawalResponse withdrawalResponse = new ExchangeWithdrawalResponse { Id = response["id"].ToStringInvariant(), @@ -1079,14 +994,14 @@ protected override async Task OnGetDepositAddressAsync(s */ Dictionary payload = await GetNoncePayloadAsync(); - payload["asset"] = currency; + payload["coin"] = currency; - JToken response = await MakeJsonRequestAsync("/depositAddress.html", WithdrawalUrlPrivate, payload); + JToken response = await MakeJsonRequestAsync("/capital/deposit/address", BaseUrlUrlSApi, payload); ExchangeDepositDetails depositDetails = new ExchangeDepositDetails { - Currency = response["asset"].ToStringInvariant(), + Currency = response["coin"].ToStringInvariant(), Address = response["address"].ToStringInvariant(), - AddressTag = response["addressTag"].ToStringInvariant() + AddressTag = response["tag"].ToStringInvariant() }; return depositDetails; @@ -1098,44 +1013,33 @@ protected override async Task OnGetDepositAddressAsync(s protected override async Task> OnGetDepositHistoryAsync(string currency) { // TODO: API supports searching on status, startTime, endTime - Dictionary payload = await GetNoncePayloadAsync(); + var payload = await GetNoncePayloadAsync(); + if (!string.IsNullOrWhiteSpace(currency)) { - payload["asset"] = currency; + payload["coin"] = currency; } - JToken response = await MakeJsonRequestAsync("/depositHistory.html", WithdrawalUrlPrivate, payload); + var response = await MakeJsonRequestAsync>("/capital/deposit/hisrec", BaseUrlUrlSApi, payload); var transactions = new List(); - foreach (JToken token in response["depositList"]) + + foreach (var item in response) { - var transaction = new ExchangeTransaction + transactions.Add(new ExchangeTransaction { - Timestamp = token["insertTime"].ConvertInvariant().UnixTimeStampToDateTimeMilliseconds(), - Amount = token["amount"].ConvertInvariant(), - Currency = token["asset"].ToStringUpperInvariant(), - Address = token["address"].ToStringInvariant(), - AddressTag = token["addressTag"].ToStringInvariant(), - BlockchainTxId = token["txId"].ToStringInvariant() - }; - int status = token["status"].ConvertInvariant(); - switch (status) - { - case 0: - transaction.Status = TransactionStatus.Processing; - break; - - case 1: - transaction.Status = TransactionStatus.Complete; - break; - - default: - // If new states are added, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md - transaction.Status = TransactionStatus.Unknown; - transaction.Notes = "Unknown transaction status: " + status; - break; - } - - transactions.Add(transaction); + Timestamp = item.InsertTime.UnixTimeStampToDateTimeMilliseconds(), + Amount = decimal.Parse(item.Amount), + Currency = item.Coin.ToUpperInvariant(), + Address = item.Address, + AddressTag = item.AddressTag, + BlockchainTxId = item.TxId, + Status = item.Status switch + { + 0 => TransactionStatus.Processing, + 1 => TransactionStatus.Complete, + _ => TransactionStatus.Unknown + } + }); } return transactions; @@ -1168,7 +1072,7 @@ protected override async Task OnUserDataWebSocketAsync(Action GetListenKeyAsync() { - JToken response = await MakeJsonRequestAsync("/userDataStream", BaseUrl, null, "POST"); + JToken response = await MakeJsonRequestAsync("/userDataStream", BaseUrlApi, null, "POST"); var listenKey = response["listenKey"].ToStringInvariant(); return listenKey; } diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceAPI.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceAPI.cs index 26183265c..81221609e 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceAPI.cs @@ -16,11 +16,8 @@ namespace ExchangeSharp { public sealed class ExchangeBinanceAPI : BinanceGroupCommon { - public override string BaseUrl { get; set; } = "https://api.binance.com/api/v1"; + public override string BaseUrl { get; set; } = "https://api.binance.com"; public override string BaseUrlWebSocket { get; set; } = "wss://stream.binance.com:9443"; - public override string BaseUrlPrivate { get; set; } = "https://api.binance.com/api/v3"; - public override string WithdrawalUrlPrivate { get; set; } = "https://api.binance.com/wapi/v3"; - public override string BaseWebUrl { get; set; } = "https://www.binance.com"; } public partial class ExchangeName { public const string Binance = "Binance"; } diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceJerseyAPI.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceJerseyAPI.cs index c46b08237..3f53b4eea 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceJerseyAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceJerseyAPI.cs @@ -16,11 +16,8 @@ namespace ExchangeSharp { public class ExchangeBinanceJerseyAPI : BinanceGroupCommon { - public override string BaseUrl { get; set; } = "https://api.binance.je/api/v1"; + public override string BaseUrl { get; set; } = "https://api.binance.je"; public override string BaseUrlWebSocket { get; set; } = "wss://stream.binance.je:9443"; - public override string BaseUrlPrivate { get; set; } = "https://api.binance.je/api/v3"; - public override string WithdrawalUrlPrivate { get; set; } = "https://api.binance.je/wapi/v3"; - public override string BaseWebUrl { get; set; } = "https://www.binance.je"; } public partial class ExchangeName { public const string BinanceJersey = "BinanceJersey"; } diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceUSAPI.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceUSAPI.cs index ca3cfff2a..24ddace38 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceUSAPI.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceUSAPI.cs @@ -16,11 +16,8 @@ namespace ExchangeSharp { public sealed class ExchangeBinanceUSAPI : BinanceGroupCommon { - public override string BaseUrl { get; set; } = "https://api.binance.us/api/v1"; + public override string BaseUrl { get; set; } = "https://api.binance.us"; public override string BaseUrlWebSocket { get; set; } = "wss://stream.binance.us:9443"; - public override string BaseUrlPrivate { get; set; } = "https://api.binance.us/api/v3"; - public override string WithdrawalUrlPrivate { get; set; } = "https://api.binance.us/wapi/v3"; - public override string BaseWebUrl { get; set; } = "https://www.binance.us"; } public partial class ExchangeName { public const string BinanceUS = "BinanceUS"; } diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs new file mode 100644 index 000000000..244e9e1fe --- /dev/null +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace ExchangeSharp.API.Exchanges.BinanceGroup +{ + public class HistoryRecord + { + [JsonProperty("amount")] + public string Amount { get; set; } + + [JsonProperty("coin")] + public string Coin { get; set; } + + [JsonProperty("network")] + public string Network { get; set; } + + [JsonProperty("status")] + public int Status { get; set; } + + [JsonProperty("address")] + public string Address { get; set; } + + [JsonProperty("addressTag")] + public string AddressTag { get; set; } + + [JsonProperty("txId")] + public string TxId { get; set; } + + [JsonProperty("insertTime")] + public long InsertTime { get; set; } + + [JsonProperty("transferType")] + public int TransferType { get; set; } + + [JsonProperty("unlockConfirm")] + public string UnlockConfirm { get; set; } + + [JsonProperty("confirmTimes")] + public string ConfirmTimes { get; set; } + } +} diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs deleted file mode 100644 index de77ad525..000000000 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs +++ /dev/null @@ -1,154 +0,0 @@ -/* -MIT LICENSE - -Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ -#nullable enable -namespace ExchangeSharp.BinanceGroup -{ - using Newtonsoft.Json; - - internal class Currency - { - [JsonProperty("id")] - public string? Id { get; set; } - - [JsonProperty("assetCode")] - public string? AssetCode { get; set; } - - [JsonProperty("assetName")] - public string? AssetName { get; set; } - - [JsonProperty("unit")] - public string? Unit { get; set; } - - [JsonProperty("transactionFee")] - public decimal TransactionFee { get; set; } - - [JsonProperty("commissionRate")] - public decimal CommissionRate { get; set; } - - [JsonProperty("freeAuditWithdrawAmt")] - public decimal FreeAuditWithdrawAmt { get; set; } - - [JsonProperty("freeUserChargeAmount")] - public long FreeUserChargeAmount { get; set; } - - [JsonProperty("minProductWithdraw")] - public string? MinProductWithdraw { get; set; } - - [JsonProperty("withdrawIntegerMultiple")] - public string? WithdrawIntegerMultiple { get; set; } - - [JsonProperty("confirmTimes")] - public string? ConfirmTimes { get; set; } - - [JsonProperty("chargeLockConfirmTimes")] - public string? ChargeLockConfirmTimes { get; set; } - - [JsonProperty("url")] - public string? Url { get; set; } - - [JsonProperty("addressUrl")] - public string? AddressUrl { get; set; } - - [JsonProperty("blockUrl")] - public string? BlockUrl { get; set; } - - [JsonProperty("enableCharge")] - public bool EnableCharge { get; set; } - - [JsonProperty("enableWithdraw")] - public bool EnableWithdraw { get; set; } - - [JsonProperty("regEx")] - public string? RegEx { get; set; } - - [JsonProperty("regExTag")] - public string? RegExTag { get; set; } - - [JsonProperty("gas")] - public decimal Gas { get; set; } - - [JsonProperty("parentCode")] - public string? ParentCode { get; set; } - - [JsonProperty("isLegalMoney")] - public bool IsLegalMoney { get; set; } - - [JsonProperty("reconciliationAmount")] - public decimal ReconciliationAmount { get; set; } - - [JsonProperty("seqNum")] - public string? SeqNum { get; set; } - - [JsonProperty("chineseName")] - public string? ChineseName { get; set; } - - [JsonProperty("cnLink")] - public string? CnLink { get; set; } - - [JsonProperty("enLink")] - public string? EnLink { get; set; } - - [JsonProperty("logoUrl")] - public string? LogoUrl { get; set; } - - [JsonProperty("fullLogoUrl")] - public string? FullLogoUrl { get; set; } - - [JsonProperty("forceStatus")] - public bool ForceStatus { get; set; } - - [JsonProperty("resetAddressStatus")] - public bool ResetAddressStatus { get; set; } - - [JsonProperty("chargeDescCn")] - public object? ChargeDescCn { get; set; } - - [JsonProperty("chargeDescEn")] - public object? ChargeDescEn { get; set; } - - [JsonProperty("assetLabel")] - public object? AssetLabel { get; set; } - - [JsonProperty("sameAddress")] - public bool SameAddress { get; set; } - - [JsonProperty("depositTipStatus")] - public bool DepositTipStatus { get; set; } - - [JsonProperty("dynamicFeeStatus")] - public bool DynamicFeeStatus { get; set; } - - [JsonProperty("depositTipEn")] - public object? DepositTipEn { get; set; } - - [JsonProperty("depositTipCn")] - public object? DepositTipCn { get; set; } - - [JsonProperty("assetLabelEn")] - public object? AssetLabelEn { get; set; } - - [JsonProperty("supportMarket")] - public object? SupportMarket { get; set; } - - [JsonProperty("feeReferenceAsset")] - public string? FeeReferenceAsset { get; set; } - - [JsonProperty("feeRate")] - public decimal? FeeRate { get; set; } - - [JsonProperty("feeDigit")] - public int? FeeDigit { get; set; } - - [JsonProperty("legalMoney")] - public bool LegalMoney { get; set; } - } -} From 42fef110fd695300e76229973e0b5202c1b4559c Mon Sep 17 00:00:00 2001 From: Tomas Bezouska Date: Mon, 20 Sep 2021 13:12:14 +0200 Subject: [PATCH 2/4] [Binance] refactor namespaces --- .../API/Exchanges/BinanceGroup/BinanceGroupCommon.cs | 1 - .../API/Exchanges/BinanceGroup/{ => Models}/HistoryRecord.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) rename src/ExchangeSharp/API/Exchanges/BinanceGroup/{ => Models}/HistoryRecord.cs (94%) diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs index 6f9a0259b..5abe19f49 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs @@ -10,7 +10,6 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #nullable enable -using ExchangeSharp.API.Exchanges.BinanceGroup; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/HistoryRecord.cs similarity index 94% rename from src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs rename to src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/HistoryRecord.cs index 244e9e1fe..2a91b68ca 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/HistoryRecord.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/HistoryRecord.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json; -namespace ExchangeSharp.API.Exchanges.BinanceGroup +namespace ExchangeSharp.BinanceGroup { public class HistoryRecord { From 5fe6870badc8b60ca8a346856692784a2846a171 Mon Sep 17 00:00:00 2001 From: Tomas Bezouska Date: Tue, 21 Sep 2021 09:25:05 +0200 Subject: [PATCH 3/4] [Binance] OnGetCurrenciesAsync --- .../BinanceGroup/BinanceGroupCommon.cs | 27 ++++++-- .../Exchanges/BinanceGroup/Models/Currency.cs | 49 ++++++++++++++ .../BinanceGroup/Models/CurrencyNetwork.cs | 64 +++++++++++++++++++ 3 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs create mode 100644 src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/CurrencyNetwork.cs diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs index 5abe19f49..3693c145e 100644 --- a/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs @@ -24,7 +24,7 @@ public abstract class BinanceGroupCommon : ExchangeAPI { public string BaseUrlApi => $"{BaseUrl}/api/v3"; - public string BaseUrlUrlSApi => $"{BaseUrl}/sapi/v1"; + public string BaseUrlSApi => $"{BaseUrl}/sapi/v1"; protected async Task GetWebSocketStreamUrlForSymbolsAsync(string suffix, params string[] marketSymbols) { @@ -85,6 +85,25 @@ public async Task> GetMyTradesAsync(string? mar return await OnGetMyTradesAsync(marketSymbol, afterDate); } + protected override async Task> OnGetCurrenciesAsync() + { + var result = await MakeJsonRequestAsync>("/capital/config/getall", BaseUrlSApi); + + return result.ToDictionary(x => x.Coin.ToUpper(), x => { + var network = x.NetworkList.FirstOrDefault(x => x.IsDefault); + return new ExchangeCurrency + { + Name = x.Coin, + FullName = x.Name, + DepositEnabled = network?.DepositEnable ?? x.DepositAllEnable, + WithdrawalEnabled = network?.WithdrawEnable ?? x.WithdrawAllEnable, + MinConfirmations = network?.MinConfirm ?? 0, + MinWithdrawalSize = decimal.Parse(network?.WithdrawMin ?? "0"), + TxFee = decimal.Parse(network?.WithdrawFee ?? "0") + }; + }); + } + protected override async Task> OnGetMarketSymbolsAsync() { List symbols = new List(); @@ -752,7 +771,7 @@ protected override async Task OnWithdrawAsync(Exchan payload["addressTag"] = withdrawalRequest.AddressTag; } - JToken response = await MakeJsonRequestAsync("/capital/withdraw/apply", BaseUrlUrlSApi, payload, "POST"); + JToken response = await MakeJsonRequestAsync("/capital/withdraw/apply", BaseUrlSApi, payload, "POST"); ExchangeWithdrawalResponse withdrawalResponse = new ExchangeWithdrawalResponse { Id = response["id"].ToStringInvariant(), @@ -995,7 +1014,7 @@ protected override async Task OnGetDepositAddressAsync(s Dictionary payload = await GetNoncePayloadAsync(); payload["coin"] = currency; - JToken response = await MakeJsonRequestAsync("/capital/deposit/address", BaseUrlUrlSApi, payload); + JToken response = await MakeJsonRequestAsync("/capital/deposit/address", BaseUrlSApi, payload); ExchangeDepositDetails depositDetails = new ExchangeDepositDetails { Currency = response["coin"].ToStringInvariant(), @@ -1019,7 +1038,7 @@ protected override async Task> OnGetDepositHist payload["coin"] = currency; } - var response = await MakeJsonRequestAsync>("/capital/deposit/hisrec", BaseUrlUrlSApi, payload); + var response = await MakeJsonRequestAsync>("/capital/deposit/hisrec", BaseUrlSApi, payload); var transactions = new List(); foreach (var item in response) diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs new file mode 100644 index 000000000..5c36e73b0 --- /dev/null +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/Currency.cs @@ -0,0 +1,49 @@ +using Newtonsoft.Json; + +namespace ExchangeSharp.BinanceGroup +{ + public class Currency + { + [JsonProperty("coin")] + public string Coin { get; set; } + + [JsonProperty("depositAllEnable")] + public bool DepositAllEnable { get; set; } + + [JsonProperty("free")] + public string Free { get; set; } + + [JsonProperty("freeze")] + public string Freeze { get; set; } + + [JsonProperty("ipoable")] + public string Ipoable { get; set; } + + [JsonProperty("ipoing")] + public string Ipoing { get; set; } + + [JsonProperty("isLegalMoney")] + public bool IsLegalMoney { get; set; } + + [JsonProperty("locked")] + public string Locked { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("storage")] + public string Storage { get; set; } + + [JsonProperty("trading")] + public bool Trading { get; set; } + + [JsonProperty("withdrawAllEnable")] + public bool WithdrawAllEnable { get; set; } + + [JsonProperty("withdrawing")] + public string Withdrawing { get; set; } + + [JsonProperty("networkList")] + public CurrencyNetwork[] NetworkList { get; set; } + } +} diff --git a/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/CurrencyNetwork.cs b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/CurrencyNetwork.cs new file mode 100644 index 000000000..de777865a --- /dev/null +++ b/src/ExchangeSharp/API/Exchanges/BinanceGroup/Models/CurrencyNetwork.cs @@ -0,0 +1,64 @@ +using Newtonsoft.Json; + +namespace ExchangeSharp.BinanceGroup +{ + public class CurrencyNetwork + { + [JsonProperty("addressRegex")] + public string AddressRegex { get; set; } + + [JsonProperty("coin")] + public string Coin { get; set; } + + [JsonProperty("depositDesc")] + public string DepositDesc { get; set; } + + [JsonProperty("depositEnable")] + public bool DepositEnable { get; set; } + + [JsonProperty("isDefault")] + public bool IsDefault { get; set; } + + [JsonProperty("memoRegex")] + public string MemoRegex { get; set; } + + [JsonProperty("minConfirm")] + public int MinConfirm { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("network")] + public string Network { get; set; } + + [JsonProperty("resetAddressStatus")] + public bool ResetAddressStatus { get; set; } + + [JsonProperty("specialTips")] + public string SpecialTips { get; set; } + + [JsonProperty("unLockConfirm")] + public int UnLockConfirm { get; set; } + + [JsonProperty("withdrawDesc")] + public string WithdrawDesc { get; set; } + + [JsonProperty("withdrawEnable")] + public bool WithdrawEnable { get; set; } + + [JsonProperty("withdrawFee")] + public string WithdrawFee { get; set; } + + [JsonProperty("withdrawIntegerMultiple")] + public string WithdrawIntegerMultiple { get; set; } + + [JsonProperty("withdrawMax")] + public string WithdrawMax { get; set; } + + [JsonProperty("withdrawMin")] + public string WithdrawMin { get; set; } + + [JsonProperty("sameAddress")] + public bool SameAddress { get; set; } + } +} From ec9b0a68365aca82d1a3d3b0d4da15d57a5f8ca2 Mon Sep 17 00:00:00 2001 From: Tomas Bezouska Date: Tue, 21 Sep 2021 18:39:13 +0200 Subject: [PATCH 4/4] DepositAddress + Withdraw option for testing --- .../Options/DepositAddressOption.cs | 26 +++++++++++++ .../Options/Interfaces/IOptionWithAddress.cs | 13 +++++++ .../Options/Interfaces/IOptionWithAmount.cs | 10 +++++ .../Options/Interfaces/IOptionWithCurrency.cs | 10 +++++ .../Options/WithdrawOption.cs | 39 +++++++++++++++++++ src/ExchangeSharpConsole/Program.cs | 4 +- 6 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/ExchangeSharpConsole/Options/DepositAddressOption.cs create mode 100644 src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAddress.cs create mode 100644 src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAmount.cs create mode 100644 src/ExchangeSharpConsole/Options/Interfaces/IOptionWithCurrency.cs create mode 100644 src/ExchangeSharpConsole/Options/WithdrawOption.cs diff --git a/src/ExchangeSharpConsole/Options/DepositAddressOption.cs b/src/ExchangeSharpConsole/Options/DepositAddressOption.cs new file mode 100644 index 000000000..92ff2f2f8 --- /dev/null +++ b/src/ExchangeSharpConsole/Options/DepositAddressOption.cs @@ -0,0 +1,26 @@ +using CommandLine; +using ExchangeSharpConsole.Options.Interfaces; +using System; +using System.Threading.Tasks; + +namespace ExchangeSharpConsole.Options +{ + [Verb("deposit-address", HelpText = "Get a deposit address for given currency")] + public class DepositAddressOption : BaseOption, IOptionPerExchange, IOptionWithCurrency + { + public string ExchangeName { get; set; } + + public string Currency { get; set; } + + public override async Task RunCommand() + { + using var api = GetExchangeInstance(ExchangeName); + + Authenticate(api); + + var address = await api.GetDepositAddressAsync(Currency); + + Console.WriteLine($"Address: {address}"); + } + } +} diff --git a/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAddress.cs b/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAddress.cs new file mode 100644 index 000000000..cd061ea10 --- /dev/null +++ b/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAddress.cs @@ -0,0 +1,13 @@ +using CommandLine; + +namespace ExchangeSharpConsole.Options.Interfaces +{ + public interface IOptionWithAddress + { + [Option('d', "address", Required = true, HelpText = "Crypto address")] + string Address { get; set; } + + [Option('t', "address-tag", Required = false, HelpText = "Tag describing the address")] + string Tag { get; set; } + } +} diff --git a/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAmount.cs b/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAmount.cs new file mode 100644 index 000000000..9b31bae19 --- /dev/null +++ b/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithAmount.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace ExchangeSharpConsole.Options.Interfaces +{ + public interface IOptionWithCurrencyAmount : IOptionWithCurrency + { + [Option('a', "amount", Required = true, HelpText = "Amount of the currency.")] + decimal Amount { get; set; } + } +} diff --git a/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithCurrency.cs b/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithCurrency.cs new file mode 100644 index 000000000..53fed0d8b --- /dev/null +++ b/src/ExchangeSharpConsole/Options/Interfaces/IOptionWithCurrency.cs @@ -0,0 +1,10 @@ +using CommandLine; + +namespace ExchangeSharpConsole.Options.Interfaces +{ + public interface IOptionWithCurrency + { + [Option('c', "currency", Required = true, HelpText = "Currency, e.g. BTC.")] + string Currency { get; set; } + } +} diff --git a/src/ExchangeSharpConsole/Options/WithdrawOption.cs b/src/ExchangeSharpConsole/Options/WithdrawOption.cs new file mode 100644 index 000000000..86372eb98 --- /dev/null +++ b/src/ExchangeSharpConsole/Options/WithdrawOption.cs @@ -0,0 +1,39 @@ +using CommandLine; +using ExchangeSharp; +using ExchangeSharpConsole.Options.Interfaces; +using System; +using System.Threading.Tasks; + +namespace ExchangeSharpConsole.Options +{ + [Verb("withdraw", HelpText = "Withdraw given amount in given currency to target address")] + public class WithdrawOption : BaseOption, IOptionPerExchange, IOptionWithAddress, IOptionWithCurrencyAmount + { + public string ExchangeName { get; set; } + + public string Address { get; set; } + + public string Tag { get; set; } + + public decimal Amount { get; set; } + + public string Currency { get; set; } + + public override async Task RunCommand() + { + using var api = GetExchangeInstance(ExchangeName); + + Authenticate(api); + + var result = await api.WithdrawAsync(new ExchangeWithdrawalRequest + { + Address = Address, + AddressTag = Tag, + Amount = Amount, + Currency = Currency + }); + + Console.WriteLine($"Withdrawal successful: {result.Success}, id: {result.Id}, optional message: {result.Message}"); + } + } +} diff --git a/src/ExchangeSharpConsole/Program.cs b/src/ExchangeSharpConsole/Program.cs index e8d95ac3e..ac9381594 100644 --- a/src/ExchangeSharpConsole/Program.cs +++ b/src/ExchangeSharpConsole/Program.cs @@ -18,6 +18,7 @@ public partial class Program typeof(CancelOrderOption), typeof(CandlesOption), typeof(ConvertOption), + typeof(DepositAddressOption), typeof(ExampleOption), typeof(ExportOption), typeof(InteractiveOption), @@ -37,7 +38,8 @@ public partial class Program typeof(WebSocketsPositionsOption), typeof(WebSocketsTickersOption), typeof(WebSocketsTradesOption), - typeof(WebSocketsCandlesOption) + typeof(WebSocketsCandlesOption), + typeof(WithdrawOption) }; public Program()