Skip to content

[UNTESTED] [Binance] sapi endpoint, cleanup #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 62 additions & 140 deletions src/ExchangeSharp/API/Exchanges/BinanceGroup/BinanceGroupCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,9 @@ namespace ExchangeSharp.BinanceGroup
{
public abstract class BinanceGroupCommon : ExchangeAPI
{
public abstract string BaseUrlPrivate { get; set; }
public abstract string WithdrawalUrlPrivate { get; set; }
/// <summary>
/// base address for APIs used by the Binance website and not published in the API docs
/// </summary>
public abstract string BaseWebUrl { get; set; }
public string BaseUrlApi => $"{BaseUrl}/api/v3";

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

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

protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnGetCurrenciesAsync()
{
var result = await MakeJsonRequestAsync<List<Currency>>("/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<IEnumerable<string>> OnGetMarketSymbolsAsync()
{
List<string> symbols = new List<string>();
Expand Down Expand Up @@ -189,30 +203,6 @@ protected internal override async Task<IEnumerable<ExchangeMarket>> OnGetMarketS
return markets;
}

protected override async Task<IReadOnlyDictionary<string, ExchangeCurrency>> OnGetCurrenciesAsync()
{
// https://www.binance.com/assetWithdraw/getAllAsset.html
Dictionary<string, ExchangeCurrency> allCoins = new Dictionary<string, ExchangeCurrency>(StringComparer.OrdinalIgnoreCase);

List<Currency> currencies = await MakeJsonRequestAsync<List<Currency>>(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<int>(),
Name = coin.AssetCode,
TxFee = coin.TransactionFee,
WithdrawalEnabled = coin.EnableWithdraw,
MinWithdrawalSize = coin.MinProductWithdraw.ConvertInvariant<decimal>(),
};
}

return allCoins;
}

protected override async Task<ExchangeTicker> OnGetTickerAsync(string marketSymbol)
{
JToken obj = await MakeJsonRequestAsync<JToken>("/ticker/24hr?symbol=" + marketSymbol);
Expand Down Expand Up @@ -517,7 +507,7 @@ protected override async Task<IEnumerable<MarketCandle>> OnGetCandlesAsync(strin

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

protected override async Task<Dictionary<string, decimal>> OnGetAmountsAvailableToTradeAsync()
{
JToken token = await MakeJsonRequestAsync<JToken>("/account", BaseUrlPrivate, await GetNoncePayloadAsync());
JToken token = await MakeJsonRequestAsync<JToken>("/account", BaseUrlApi, await GetNoncePayloadAsync());
Dictionary<string, decimal> balances = new Dictionary<string, decimal>(StringComparer.OrdinalIgnoreCase);
foreach (JToken balance in token["balances"])
{
Expand Down Expand Up @@ -577,7 +567,7 @@ protected override async Task<ExchangeOrderResult> OnPlaceOrderAsync(ExchangeOrd
}
order.ExtraParameters.CopyTo(payload);

JToken? token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlPrivate, payload, "POST");
JToken? token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlApi, payload, "POST");
if (token is null)
{
return null;
Expand All @@ -599,13 +589,13 @@ protected override async Task<ExchangeOrderResult> OnGetOrderDetailsAsync(string
else
payload["orderId"] = orderId;

JToken token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlPrivate, payload);
JToken token = await MakeJsonRequestAsync<JToken>("/order", BaseUrlApi, payload);
ExchangeOrderResult result = ParseOrder(token);

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

return result;
Expand Down Expand Up @@ -640,7 +630,7 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetOpenOrderDe
{
payload["symbol"] = marketSymbol!;
}
JToken token = await MakeJsonRequestAsync<JToken>("/openOrders", BaseUrlPrivate, payload);
JToken token = await MakeJsonRequestAsync<JToken>("/openOrders", BaseUrlApi, payload);
foreach (JToken order in token)
{
orders.Add(ParseOrder(order));
Expand Down Expand Up @@ -703,74 +693,13 @@ protected override async Task<IEnumerable<ExchangeOrderResult>> OnGetCompletedOr
{
payload["startTime"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds();
}
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlPrivate, payload);
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlApi, payload);
foreach (JToken trade in token)
{
trades.Add(ParseTrade(trade, marketSymbol!));
}
}
return trades;

//old way

//List<ExchangeOrderResult> orders = new List<ExchangeOrderResult>();
//if (string.IsNullOrWhiteSpace(marketSymbol))
//{
// orders.AddRange(await GetCompletedOrdersForAllSymbolsAsync(afterDate));
//}
//else
//{
// Dictionary<string, object> payload = await GetNoncePayloadAsync();
// payload["symbol"] = marketSymbol;
// if (afterDate != null)
// {
// payload["startTime"] = Math.Round(afterDate.Value.UnixTimestampFromDateTimeMilliseconds());
// }
// JToken token = await MakeJsonRequestAsync<JToken>("/allOrders", BaseUrlPrivate, payload);
// foreach (JToken order in token)
// {
// orders.Add(ParseOrder(order));
// }
//}
//return orders;
}

private async Task<IEnumerable<ExchangeOrderResult>> 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<ExchangeOrderResult> trades = new List<ExchangeOrderResult>();
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<IEnumerable<ExchangeOrderResult>> OnGetMyTradesAsync(string? marketSymbol = null, DateTime? afterDate = null)
Expand All @@ -788,7 +717,7 @@ private async Task<IEnumerable<ExchangeOrderResult>> OnGetMyTradesAsync(string?
{
payload["timestamp"] = afterDate.Value.UnixTimestampFromDateTimeMilliseconds();
}
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlPrivate, payload);
JToken token = await MakeJsonRequestAsync<JToken>("/myTrades", BaseUrlApi, payload);
foreach (JToken trade in token)
{
trades.Add(ParseTrade(trade, marketSymbol!));
Expand All @@ -806,7 +735,7 @@ protected override async Task OnCancelOrderAsync(string orderId, string? marketS
}
payload["symbol"] = marketSymbol!;
payload["orderId"] = orderId;
_ = await MakeJsonRequestAsync<JToken>("/order", BaseUrlPrivate, payload, "DELETE");
_ = await MakeJsonRequestAsync<JToken>("/order", BaseUrlApi, payload, "DELETE");
}

/// <summary>A withdrawal request. Fee is automatically subtracted from the amount.</summary>
Expand All @@ -828,17 +757,21 @@ protected override async Task<ExchangeWithdrawalResponse> OnWithdrawAsync(Exchan
}

Dictionary<string, object> 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<JToken>("/withdraw.html", WithdrawalUrlPrivate, payload, "POST");
JToken response = await MakeJsonRequestAsync<JToken>("/capital/withdraw/apply", BaseUrlSApi, payload, "POST");
ExchangeWithdrawalResponse withdrawalResponse = new ExchangeWithdrawalResponse
{
Id = response["id"].ToStringInvariant(),
Expand Down Expand Up @@ -1079,14 +1012,14 @@ protected override async Task<ExchangeDepositDetails> OnGetDepositAddressAsync(s
*/

Dictionary<string, object> payload = await GetNoncePayloadAsync();
payload["asset"] = currency;
payload["coin"] = currency;

JToken response = await MakeJsonRequestAsync<JToken>("/depositAddress.html", WithdrawalUrlPrivate, payload);
JToken response = await MakeJsonRequestAsync<JToken>("/capital/deposit/address", BaseUrlSApi, 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;
Expand All @@ -1098,44 +1031,33 @@ protected override async Task<ExchangeDepositDetails> OnGetDepositAddressAsync(s
protected override async Task<IEnumerable<ExchangeTransaction>> OnGetDepositHistoryAsync(string currency)
{
// TODO: API supports searching on status, startTime, endTime
Dictionary<string, object> payload = await GetNoncePayloadAsync();
var payload = await GetNoncePayloadAsync();

if (!string.IsNullOrWhiteSpace(currency))
{
payload["asset"] = currency;
payload["coin"] = currency;
}

JToken response = await MakeJsonRequestAsync<JToken>("/depositHistory.html", WithdrawalUrlPrivate, payload);
var response = await MakeJsonRequestAsync<List<HistoryRecord>>("/capital/deposit/hisrec", BaseUrlSApi, payload);
var transactions = new List<ExchangeTransaction>();
foreach (JToken token in response["depositList"])

foreach (var item in response)
{
var transaction = new ExchangeTransaction
{
Timestamp = token["insertTime"].ConvertInvariant<double>().UnixTimeStampToDateTimeMilliseconds(),
Amount = token["amount"].ConvertInvariant<decimal>(),
Currency = token["asset"].ToStringUpperInvariant(),
Address = token["address"].ToStringInvariant(),
AddressTag = token["addressTag"].ToStringInvariant(),
BlockchainTxId = token["txId"].ToStringInvariant()
};
int status = token["status"].ConvertInvariant<int>();
switch (status)
transactions.Add(new ExchangeTransaction
{
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;
Expand Down Expand Up @@ -1168,7 +1090,7 @@ protected override async Task<IWebSocket> OnUserDataWebSocketAsync(Action<object

public async Task<string> GetListenKeyAsync()
{
JToken response = await MakeJsonRequestAsync<JToken>("/userDataStream", BaseUrl, null, "POST");
JToken response = await MakeJsonRequestAsync<JToken>("/userDataStream", BaseUrlApi, null, "POST");
var listenKey = response["listenKey"].ToStringInvariant();
return listenKey;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"; }
Expand Down
Loading