Skip to content

Commit 0443882

Browse files
authored
[UNTESTED] [Binance] sapi endpoint, cleanup (#655)
* [Binance] sapi endpoint, cleanup * [Binance] refactor namespaces * [Binance] OnGetCurrenciesAsync
1 parent bf604b5 commit 0443882

File tree

7 files changed

+201
-289
lines changed

7 files changed

+201
-289
lines changed

src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs

Lines changed: 62 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,9 @@ namespace ExchangeSharp.BinanceGroup
2222
{
2323
public abstract class BinanceGroupCommon : ExchangeAPI
2424
{
25-
public abstract string BaseUrlPrivate { get; set; }
26-
public abstract string WithdrawalUrlPrivate { get; set; }
27-
/// <summary>
28-
/// base address for APIs used by the Binance website and not published in the API docs
29-
/// </summary>
30-
public abstract string BaseWebUrl { get; set; }
25+
public string BaseUrlApi => $"{BaseUrl}/api/v3";
3126

32-
public const string GetCurrenciesUrl = "/assetWithdraw/getAllAsset.html";
27+
public string BaseUrlSApi => $"{BaseUrl}/sapi/v1";
3328

3429
protected async Task<string> GetWebSocketStreamUrlForSymbolsAsync(string suffix, params string[] marketSymbols)
3530
{
@@ -90,6 +85,25 @@ public async Task<IEnumerable<ExchangeOrderResult>> GetMyTradesAsync(string? mar
9085
return await OnGetMyTradesAsync(marketSymbol, afterDate);
9186
}
9287

88+
protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnGetCurrenciesAsync()
89+
{
90+
var result = await MakeJsonRequestAsync<List<Currency>>("/capital/config/getall", BaseUrlSApi);
91+
92+
return result.ToDictionary(x => x.Coin.ToUpper(), x => {
93+
var network = x.NetworkList.FirstOrDefault(x => x.IsDefault);
94+
return new ExchangeCurrency
95+
{
96+
Name = x.Coin,
97+
FullName = x.Name,
98+
DepositEnabled = network?.DepositEnable ?? x.DepositAllEnable,
99+
WithdrawalEnabled = network?.WithdrawEnable ?? x.WithdrawAllEnable,
100+
MinConfirmations = network?.MinConfirm ?? 0,
101+
MinWithdrawalSize = decimal.Parse(network?.WithdrawMin ?? "0"),
102+
TxFee = decimal.Parse(network?.WithdrawFee ?? "0")
103+
};
104+
});
105+
}
106+
93107
protected override async Task<IEnumerable<string>> OnGetMarketSymbolsAsync()
94108
{
95109
List<string> symbols = new List<string>();
@@ -189,30 +203,6 @@ protected internal override async Task<IEnumerable<ExchangeMarket>> OnGetMarketS
189203
return markets;
190204
}
191205

192-
protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnGetCurrenciesAsync()
193-
{
194-
// https://www.binance.com/assetWithdraw/getAllAsset.html
195-
Dictionary<string, ExchangeCurrency> allCoins = new Dictionary<string, ExchangeCurrency>(StringComparer.OrdinalIgnoreCase);
196-
197-
List<Currency> currencies = await MakeJsonRequestAsync<List<Currency>>(GetCurrenciesUrl, BaseWebUrl);
198-
foreach (Currency coin in currencies)
199-
{
200-
allCoins[coin.AssetCode] = new ExchangeCurrency
201-
{
202-
CoinType = coin.ParentCode,
203-
DepositEnabled = coin.EnableCharge,
204-
FullName = coin.AssetName,
205-
MinConfirmations = coin.ConfirmTimes.ConvertInvariant<int>(),
206-
Name = coin.AssetCode,
207-
TxFee = coin.TransactionFee,
208-
WithdrawalEnabled = coin.EnableWithdraw,
209-
MinWithdrawalSize = coin.MinProductWithdraw.ConvertInvariant<decimal>(),
210-
};
211-
}
212-
213-
return allCoins;
214-
}
215-
216206
protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
217207
{
218208
JToken obj = await MakeJsonRequestAsync<JToken>("/ticker/24hr?symbol=" + marketSymbol);
@@ -517,7 +507,7 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(strin
517507

518508
protected override async Task<Dictionary<string, decimal>> OnGetAmountsAsync()
519509
{
520-
JToken token = await MakeJsonRequestAsync<JToken>("/account", BaseUrlPrivate, await GetNoncePayloadAsync());
510+
JToken token = await MakeJsonRequestAsync<JToken>("/account", BaseUrlApi, await GetNoncePayloadAsync());
521511
Dictionary<string, decimal> balances = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase);
522512
foreach (JToken balance in token["balances"])
523513
{
@@ -532,7 +522,7 @@ protected override async Task<Dictionary<string, decimal>> OnGetAmountsAsync()
532522

533523
protected override async Task<Dictionary<string, decimal>> OnGetAmountsAvailableToTradeAsync()
534524
{
535-
JToken token = await MakeJsonRequestAsync<JToken>("/account", BaseUrlPrivate, await GetNoncePayloadAsync());
525+
JToken token = await MakeJsonRequestAsync<JToken>("/account", BaseUrlApi, await GetNoncePayloadAsync());
536526
Dictionary<string, decimal> balances = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase);
537527
foreach (JToken balance in token["balances"])
538528
{
@@ -578,7 +568,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
578568
}
579569
order.ExtraParameters.CopyTo(payload);
580570

581-
JToken? token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlPrivate, payload, "POST");
571+
JToken? token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlApi, payload, "POST");
582572
if (token is null)
583573
{
584574
return null;
@@ -600,13 +590,13 @@ protected override async Task<ExchangeOrderResult> OnGetOrderDetailsAsync(string
600590
else
601591
payload["orderId"] = orderId;
602592

603-
JToken token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlPrivate, payload);
593+
JToken token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlApi, payload);
604594
ExchangeOrderResult result = ParseOrder(token);
605595

606596
// Add up the fees from each trade in the order
607597
Dictionary<string, object> feesPayload = await GetNoncePayloadAsync();
608598
feesPayload["symbol"] = marketSymbol!;
609-
JToken feesToken = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlPrivate, feesPayload);
599+
JToken feesToken = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlApi, feesPayload);
610600
ParseFees(feesToken, result);
611601

612602
return result;
@@ -641,7 +631,7 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDe
641631
{
642632
payload["symbol"] = marketSymbol!;
643633
}
644-
JToken token = await MakeJsonRequestAsync<JToken>("/openOrders", BaseUrlPrivate, payload);
634+
JToken token = await MakeJsonRequestAsync<JToken>("/openOrders", BaseUrlApi, payload);
645635
foreach (JToken order in token)
646636
{
647637
orders.Add(ParseOrder(order));
@@ -704,74 +694,13 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetCompletedOr
704694
{
705695
payload["startTime"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds();
706696
}
707-
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlPrivate, payload);
697+
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlApi, payload);
708698
foreach (JToken trade in token)
709699
{
710700
trades.Add(ParseTrade(trade, marketSymbol!));
711701
}
712702
}
713703
return trades;
714-
715-
//old way
716-
717-
//List<ExchangeOrderResult> orders = new List<ExchangeOrderResult>();
718-
//if (string.IsNullOrWhiteSpace(marketSymbol))
719-
//{
720-
// orders.AddRange(await GetCompletedOrdersForAllSymbolsAsync(afterDate));
721-
//}
722-
//else
723-
//{
724-
// Dictionary<string, object> payload = await GetNoncePayloadAsync();
725-
// payload["symbol"] = marketSymbol;
726-
// if (afterDate != null)
727-
// {
728-
// payload["startTime"] = Math.Round(afterDate.Value.UnixTimestampFromDateTimeMilliseconds());
729-
// }
730-
// JToken token = await MakeJsonRequestAsync<JToken>("/allOrders", BaseUrlPrivate, payload);
731-
// foreach (JToken order in token)
732-
// {
733-
// orders.Add(ParseOrder(order));
734-
// }
735-
//}
736-
//return orders;
737-
}
738-
739-
private async Task<IEnumerable<ExchangeOrderResult>> GetMyTradesForAllSymbols(DateTime? afterDate)
740-
{
741-
// TODO: This is a HACK, Binance API needs to add a single API call to get all orders for all symbols, terrible...
742-
List<ExchangeOrderResult> trades = new List<ExchangeOrderResult>();
743-
Exception? ex = null;
744-
string? failedSymbol = null;
745-
Parallel.ForEach((await GetMarketSymbolsAsync()).Where(s => s.IndexOf("BTC", StringComparison.OrdinalIgnoreCase) >= 0), async (s) =>
746-
{
747-
try
748-
{
749-
foreach (ExchangeOrderResult trade in (await GetMyTradesAsync(s, afterDate)))
750-
{
751-
lock (trades)
752-
{
753-
trades.Add(trade);
754-
}
755-
}
756-
}
757-
catch (Exception _ex)
758-
{
759-
failedSymbol = s;
760-
ex = _ex;
761-
}
762-
});
763-
764-
if (ex != null)
765-
{
766-
throw new APIException("Failed to get my trades for symbol " + failedSymbol, ex);
767-
}
768-
769-
// sort timestamp desc
770-
trades.Sort((o1, o2) =>
771-
{
772-
return o2.OrderDate.CompareTo(o1.OrderDate);
773-
});
774-
return trades;
775704
}
776705

777706
private async Task<IEnumerable<ExchangeOrderResult>> OnGetMyTradesAsync(string? marketSymbol = null, DateTime? afterDate = null)
@@ -789,7 +718,7 @@ private async Task<IEnumerable<ExchangeOrderResult>> OnGetMyTradesAsync(string?
789718
{
790719
payload["timestamp"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds();
791720
}
792-
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlPrivate, payload);
721+
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlApi, payload);
793722
foreach (JToken trade in token)
794723
{
795724
trades.Add(ParseTrade(trade, marketSymbol!));
@@ -807,7 +736,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string? marketS
807736
}
808737
payload["symbol"] = marketSymbol!;
809738
payload["orderId"] = orderId;
810-
_ = await MakeJsonRequestAsync<JToken>("/order", BaseUrlPrivate, payload, "DELETE");
739+
_ = await MakeJsonRequestAsync<JToken>("/order", BaseUrlApi, payload, "DELETE");
811740
}
812741

813742
/// <summary>A withdrawal request. Fee is automatically subtracted from the amount.</summary>
@@ -829,17 +758,21 @@ protected override async Task<ExchangeWithdrawalResponse> OnWithdrawAsync(Exchan
829758
}
830759

831760
Dictionary<string, object> payload = await GetNoncePayloadAsync();
832-
payload["asset"] = withdrawalRequest.Currency;
761+
payload["coin"] = withdrawalRequest.Currency;
833762
payload["address"] = withdrawalRequest.Address;
834763
payload["amount"] = withdrawalRequest.Amount;
835-
payload["name"] = withdrawalRequest.Description ?? "apiwithdrawal"; // Contrary to what the API docs say, name is required
764+
765+
if (!string.IsNullOrWhiteSpace(withdrawalRequest.Description))
766+
{
767+
payload["name"] = withdrawalRequest.Description;
768+
}
836769

837770
if (!string.IsNullOrWhiteSpace(withdrawalRequest.AddressTag))
838771
{
839772
payload["addressTag"] = withdrawalRequest.AddressTag;
840773
}
841774

842-
JToken response = await MakeJsonRequestAsync<JToken>("/withdraw.html", WithdrawalUrlPrivate, payload, "POST");
775+
JToken response = await MakeJsonRequestAsync<JToken>("/capital/withdraw/apply", BaseUrlSApi, payload, "POST");
843776
ExchangeWithdrawalResponse withdrawalResponse = new ExchangeWithdrawalResponse
844777
{
845778
Id = response["id"].ToStringInvariant(),
@@ -1081,14 +1014,14 @@ protected override async Task<ExchangeDepositDetails> OnGetDepositAddressAsync(s
10811014
*/
10821015

10831016
Dictionary<string, object> payload = await GetNoncePayloadAsync();
1084-
payload["asset"] = currency;
1017+
payload["coin"] = currency;
10851018

1086-
JToken response = await MakeJsonRequestAsync<JToken>("/depositAddress.html", WithdrawalUrlPrivate, payload);
1019+
JToken response = await MakeJsonRequestAsync<JToken>("/capital/deposit/address", BaseUrlSApi, payload);
10871020
ExchangeDepositDetails depositDetails = new ExchangeDepositDetails
10881021
{
1089-
Currency = response["asset"].ToStringInvariant(),
1022+
Currency = response["coin"].ToStringInvariant(),
10901023
Address = response["address"].ToStringInvariant(),
1091-
AddressTag = response["addressTag"].ToStringInvariant()
1024+
AddressTag = response["tag"].ToStringInvariant()
10921025
};
10931026

10941027
return depositDetails;
@@ -1100,44 +1033,33 @@ protected override async Task<ExchangeDepositDetails> OnGetDepositAddressAsync(s
11001033
protected override async Task<IEnumerable<ExchangeTransaction>> OnGetDepositHistoryAsync(string currency)
11011034
{
11021035
// TODO: API supports searching on status, startTime, endTime
1103-
Dictionary<string, object> payload = await GetNoncePayloadAsync();
1036+
var payload = await GetNoncePayloadAsync();
1037+
11041038
if (!string.IsNullOrWhiteSpace(currency))
11051039
{
1106-
payload["asset"] = currency;
1040+
payload["coin"] = currency;
11071041
}
11081042

1109-
JToken response = await MakeJsonRequestAsync<JToken>("/depositHistory.html", WithdrawalUrlPrivate, payload);
1043+
var response = await MakeJsonRequestAsync<List<HistoryRecord>>("/capital/deposit/hisrec", BaseUrlSApi, payload);
11101044
var transactions = new List<ExchangeTransaction>();
1111-
foreach (JToken token in response["depositList"])
1045+
1046+
foreach (var item in response)
11121047
{
1113-
var transaction = new ExchangeTransaction
1114-
{
1115-
Timestamp = token["insertTime"].ConvertInvariant<double>().UnixTimeStampToDateTimeMilliseconds(),
1116-
Amount = token["amount"].ConvertInvariant<decimal>(),
1117-
Currency = token["asset"].ToStringUpperInvariant(),
1118-
Address = token["address"].ToStringInvariant(),
1119-
AddressTag = token["addressTag"].ToStringInvariant(),
1120-
BlockchainTxId = token["txId"].ToStringInvariant()
1121-
};
1122-
int status = token["status"].ConvertInvariant<int>();
1123-
switch (status)
1048+
transactions.Add(new ExchangeTransaction
11241049
{
1125-
case 0:
1126-
transaction.Status = TransactionStatus.Processing;
1127-
break;
1128-
1129-
case 1:
1130-
transaction.Status = TransactionStatus.Complete;
1131-
break;
1132-
1133-
default:
1134-
// If new states are added, see https://github.com/binance-exchange/binance-official-api-docs/blob/master/wapi-api.md
1135-
transaction.Status = TransactionStatus.Unknown;
1136-
transaction.Notes = "Unknown transaction status: " + status;
1137-
break;
1138-
}
1139-
1140-
transactions.Add(transaction);
1050+
Timestamp = item.InsertTime.UnixTimeStampToDateTimeMilliseconds(),
1051+
Amount = decimal.Parse(item.Amount),
1052+
Currency = item.Coin.ToUpperInvariant(),
1053+
Address = item.Address,
1054+
AddressTag = item.AddressTag,
1055+
BlockchainTxId = item.TxId,
1056+
Status = item.Status switch
1057+
{
1058+
0 => TransactionStatus.Processing,
1059+
1 => TransactionStatus.Complete,
1060+
_ => TransactionStatus.Unknown
1061+
}
1062+
});
11411063
}
11421064

11431065
return transactions;
@@ -1170,7 +1092,7 @@ protected override async Task<IWebSocket> OnUserDataWebSocketAsync(Action<object
11701092

11711093
public async Task<string> GetListenKeyAsync()
11721094
{
1173-
JToken response = await MakeJsonRequestAsync<JToken>("/userDataStream", BaseUrl, null, "POST");
1095+
JToken response = await MakeJsonRequestAsync<JToken>("/userDataStream", BaseUrlApi, null, "POST");
11741096
var listenKey = response["listenKey"].ToStringInvariant();
11751097
return listenKey;
11761098
}

src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceAPI.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@ namespace ExchangeSharp
1616
{
1717
public sealed class ExchangeBinanceAPI : BinanceGroupCommon
1818
{
19-
public override string BaseUrl { get; set; } = "https://api.binance.com/api/v1";
19+
public override string BaseUrl { get; set; } = "https://api.binance.com";
2020
public override string BaseUrlWebSocket { get; set; } = "wss://stream.binance.com:9443";
21-
public override string BaseUrlPrivate { get; set; } = "https://api.binance.com/api/v3";
22-
public override string WithdrawalUrlPrivate { get; set; } = "https://api.binance.com/wapi/v3";
23-
public override string BaseWebUrl { get; set; } = "https://www.binance.com";
2421
}
2522

2623
public partial class ExchangeName { public const string Binance = "Binance"; }

src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceJerseyAPI.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@ namespace ExchangeSharp
1616
{
1717
public class ExchangeBinanceJerseyAPI : BinanceGroupCommon
1818
{
19-
public override string BaseUrl { get; set; } = "https://api.binance.je/api/v1";
19+
public override string BaseUrl { get; set; } = "https://api.binance.je";
2020
public override string BaseUrlWebSocket { get; set; } = "wss://stream.binance.je:9443";
21-
public override string BaseUrlPrivate { get; set; } = "https://api.binance.je/api/v3";
22-
public override string WithdrawalUrlPrivate { get; set; } = "https://api.binance.je/wapi/v3";
23-
public override string BaseWebUrl { get; set; } = "https://www.binance.je";
2421
}
2522

2623
public partial class ExchangeName { public const string BinanceJersey = "BinanceJersey"; }

src/ExchangeSharp/API/Exchanges/BinanceGroup/ExchangeBinanceUSAPI.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@ namespace ExchangeSharp
1616
{
1717
public sealed class ExchangeBinanceUSAPI : BinanceGroupCommon
1818
{
19-
public override string BaseUrl { get; set; } = "https://api.binance.us/api/v1";
19+
public override string BaseUrl { get; set; } = "https://api.binance.us";
2020
public override string BaseUrlWebSocket { get; set; } = "wss://stream.binance.us:9443";
21-
public override string BaseUrlPrivate { get; set; } = "https://api.binance.us/api/v3";
22-
public override string WithdrawalUrlPrivate { get; set; } = "https://api.binance.us/wapi/v3";
23-
public override string BaseWebUrl { get; set; } = "https://www.binance.us";
2421
}
2522

2623
public partial class ExchangeName { public const string BinanceUS = "BinanceUS"; }

0 commit comments

Comments
 (0)