@jmparsons/ccxt
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 100+ exchanges
900 lines (865 loc) • 36.8 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ExchangeNotAvailable, InsufficientFunds, OrderNotFound, InvalidOrder, DDoSProtection, InvalidNonce, AuthenticationError } = require ('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class binance extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'binance',
'name': 'Binance',
'countries': 'JP', // Japan
'rateLimit': 500,
// new metainfo interface
'has': {
'fetchDepositAddress': true,
'CORS': false,
'fetchBidsAsks': true,
'fetchTickers': true,
'fetchOHLCV': true,
'fetchMyTrades': true,
'fetchOrder': true,
'fetchOrders': true,
'fetchOpenOrders': true,
'fetchClosedOrders': true,
'withdraw': true,
'fetchFundingFees': true,
},
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'4h': '4h',
'6h': '6h',
'8h': '8h',
'12h': '12h',
'1d': '1d',
'3d': '3d',
'1w': '1w',
'1M': '1M',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/29604020-d5483cdc-87ee-11e7-94c7-d1a8d9169293.jpg',
'api': {
'web': 'https://www.binance.com',
'wapi': 'https://api.binance.com/wapi/v3',
'public': 'https://api.binance.com/api/v1',
'private': 'https://api.binance.com/api/v3',
'v3': 'https://api.binance.com/api/v3',
'v1': 'https://api.binance.com/api/v1',
},
'www': 'https://www.binance.com',
'doc': 'https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md',
'fees': [
'https://binance.zendesk.com/hc/en-us/articles/115000429332',
'https://support.binance.com/hc/en-us/articles/115000583311',
],
},
'api': {
'web': {
'get': [
'exchange/public/product',
],
},
'wapi': {
'post': [
'withdraw',
],
'get': [
'depositHistory',
'withdrawHistory',
'depositAddress',
'accountStatus',
'systemStatus',
'withdrawFee',
],
},
'v3': {
'get': [
'ticker/price',
'ticker/bookTicker',
],
},
'public': {
'get': [
'exchangeInfo',
'ping',
'time',
'depth',
'aggTrades',
'klines',
'ticker/24hr',
'ticker/allPrices',
'ticker/allBookTickers',
'ticker/price',
'ticker/bookTicker',
'exchangeInfo',
],
'put': [ 'userDataStream' ],
'post': [ 'userDataStream' ],
'delete': [ 'userDataStream' ],
},
'private': {
'get': [
'order',
'openOrders',
'allOrders',
'account',
'myTrades',
],
'post': [
'order',
'order/test',
],
'delete': [
'order',
],
},
},
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'taker': 0.001,
'maker': 0.001,
},
// should be deleted, these are outdated and inaccurate
'funding': {
'tierBased': false,
'percentage': false,
'withdraw': {
'ADA': 1.0,
'ADX': 4.7,
'AION': 1.9,
'AMB': 11.4,
'APPC': 6.5,
'ARK': 0.1,
'ARN': 3.1,
'AST': 10.0,
'BAT': 18.0,
'BCD': 1.0,
'BCH': 0.001,
'BCPT': 10.2,
'BCX': 1.0,
'BNB': 0.7,
'BNT': 1.5,
'BQX': 1.6,
'BRD': 6.4,
'BTC': 0.001,
'BTG': 0.001,
'BTM': 5.0,
'BTS': 1.0,
'CDT': 67.0,
'CMT': 37.0,
'CND': 47.0,
'CTR': 5.4,
'DASH': 0.002,
'DGD': 0.06,
'DLT': 11.7,
'DNT': 51.0,
'EDO': 2.5,
'ELF': 6.5,
'ENG': 2.1,
'ENJ': 42.0,
'EOS': 1.0,
'ETC': 0.01,
'ETF': 1.0,
'ETH': 0.01,
'EVX': 2.5,
'FUEL': 45.0,
'FUN': 85.0,
'GAS': 0,
'GTO': 20.0,
'GVT': 0.53,
'GXS': 0.3,
'HCC': 0.0005,
'HSR': 0.0001,
'ICN': 3.5,
'ICX': 1.3,
'INS': 1.5,
'IOTA': 0.5,
'KMD': 0.002,
'KNC': 2.6,
'LEND': 54.0,
'LINK': 12.8,
'LLT': 54.0,
'LRC': 9.1,
'LSK': 0.1,
'LTC': 0.01,
'LUN': 0.29,
'MANA': 74.0,
'MCO': 0.86,
'MDA': 4.7,
'MOD': 2.0,
'MTH': 34.0,
'MTL': 1.9,
'NAV': 0.2,
'NEBL': 0.01,
'NEO': 0.0,
'NULS': 2.1,
'OAX': 8.3,
'OMG': 0.57,
'OST': 17.0,
'POE': 88.0,
'POWR': 8.6,
'PPT': 0.25,
'QSP': 21.0,
'QTUM': 0.01,
'RCN': 35.0,
'RDN': 2.2,
'REQ': 18.1,
'RLC': 4.1,
'SALT': 1.3,
'SBTC': 1.0,
'SNGLS': 42,
'SNM': 29.0,
'SNT': 32.0,
'STORJ': 5.9,
'STRAT': 0.1,
'SUB': 7.4,
'TNB': 82.0,
'TNT': 47.0,
'TRIG': 6.7,
'TRX': 129.0,
'USDT': 23.0,
'VEN': 1.8,
'VIB': 28.0,
'VIBE': 7.2,
'WABI': 3.5,
'WAVES': 0.002,
'WINGS': 9.3,
'WTC': 0.5,
'XLM': 0.01,
'XMR': 0.04,
'XRP': 0.25,
'XVG': 0.1,
'XZC': 0.02,
'YOYOW': 39.0,
'ZEC': 0.005,
'ZRX': 5.7,
},
'deposit': {},
},
},
'commonCurrencies': {
'YOYO': 'YOYOW',
'BCC': 'BCH',
'NANO': 'XRB',
},
// exchange-specific options
'options': {
'defaultLimitOrderType': 'limit', // or 'limit_maker'
'hasAlreadyAuthenticatedSuccessfully': false,
'warnOnFetchOpenOrdersWithoutSymbol': true,
'recvWindow': 5 * 1000, // 5 sec, binance default
'timeDifference': 0, // the difference between system clock and Binance clock
'adjustForTimeDifference': false, // controls the adjustment logic upon instantiation
},
'exceptions': {
'-1000': ExchangeNotAvailable, // {"code":-1000,"msg":"An unknown error occured while processing the request."}
'-1013': InvalidOrder, // createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL
'-1021': InvalidNonce, // 'your time is ahead of server'
'-1100': InvalidOrder, // createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'
'-2010': InsufficientFunds, // createOrder -> 'Account has insufficient balance for requested action.'
'-2011': OrderNotFound, // cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'
'-2013': OrderNotFound, // fetchOrder (1, 'BTC/USDT') -> 'Order does not exist'
'-2015': AuthenticationError, // "Invalid API-key, IP, or permissions for action."
},
});
}
nonce () {
return this.milliseconds () - this.options['timeDifference'];
}
async loadTimeDifference () {
const response = await this.publicGetTime ();
const after = this.milliseconds ();
this.options['timeDifference'] = parseInt (after - response['serverTime']);
return this.options['timeDifference'];
}
async fetchMarkets () {
let response = await this.publicGetExchangeInfo ();
if (this.options['adjustForTimeDifference'])
await this.loadTimeDifference ();
let markets = response['symbols'];
let result = [];
for (let i = 0; i < markets.length; i++) {
let market = markets[i];
let id = market['symbol'];
// "123456" is a "test symbol/market"
if (id === '123456')
continue;
let baseId = market['baseAsset'];
let quoteId = market['quoteAsset'];
let base = this.commonCurrencyCode (baseId);
let quote = this.commonCurrencyCode (quoteId);
let symbol = base + '/' + quote;
let filters = this.indexBy (market['filters'], 'filterType');
let precision = {
'base': market['baseAssetPrecision'],
'quote': market['quotePrecision'],
'amount': market['baseAssetPrecision'],
'price': market['quotePrecision'],
};
let active = (market['status'] === 'TRADING');
// lot size is deprecated as of 2018.02.06
let lot = -1 * Math.log10 (precision['amount']);
let entry = {
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
'info': market,
'lot': lot, // lot size is deprecated as of 2018.02.06
'active': active,
'precision': precision,
'limits': {
'amount': {
'min': Math.pow (10, -precision['amount']),
'max': undefined,
},
'price': {
'min': Math.pow (10, -precision['price']),
'max': undefined,
},
'cost': {
'min': lot,
'max': undefined,
},
},
};
if ('PRICE_FILTER' in filters) {
let filter = filters['PRICE_FILTER'];
entry['precision']['price'] = this.precisionFromString (filter['tickSize']);
entry['limits']['price'] = {
'min': this.safeFloat (filter, 'minPrice'),
'max': this.safeFloat (filter, 'maxPrice'),
};
}
if ('LOT_SIZE' in filters) {
let filter = filters['LOT_SIZE'];
entry['precision']['amount'] = this.precisionFromString (filter['stepSize']);
entry['lot'] = this.safeFloat (filter, 'stepSize'); // lot size is deprecated as of 2018.02.06
entry['limits']['amount'] = {
'min': this.safeFloat (filter, 'minQty'),
'max': this.safeFloat (filter, 'maxQty'),
};
}
if ('MIN_NOTIONAL' in filters) {
entry['limits']['cost']['min'] = parseFloat (filters['MIN_NOTIONAL']['minNotional']);
}
result.push (entry);
}
return result;
}
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) {
let market = this.markets[symbol];
let key = 'quote';
let rate = market[takerOrMaker];
let cost = parseFloat (this.costToPrecision (symbol, amount * rate));
if (side === 'sell') {
cost *= price;
} else {
key = 'base';
}
return {
'type': takerOrMaker,
'currency': market[key],
'rate': rate,
'cost': parseFloat (this.feeToPrecision (symbol, cost)),
};
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
let response = await this.privateGetAccount (params);
let result = { 'info': response };
let balances = response['balances'];
for (let i = 0; i < balances.length; i++) {
let balance = balances[i];
let currency = balance['asset'];
if (currency in this.currencies_by_id)
currency = this.currencies_by_id[currency]['code'];
let account = {
'free': parseFloat (balance['free']),
'used': parseFloat (balance['locked']),
'total': 0.0,
};
account['total'] = this.sum (account['free'], account['used']);
result[currency] = account;
}
return this.parseBalance (result);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
let market = this.market (symbol);
let request = {
'symbol': market['id'],
};
if (typeof limit !== 'undefined')
request['limit'] = limit; // default = maximum = 100
let response = await this.publicGetDepth (this.extend (request, params));
let orderbook = this.parseOrderBook (response);
orderbook['nonce'] = this.safeInteger (response, 'lastUpdateId');
return orderbook;
}
parseTicker (ticker, market = undefined) {
let timestamp = this.safeInteger (ticker, 'closeTime');
let iso8601 = (typeof timestamp === 'undefined') ? undefined : this.iso8601 (timestamp);
let symbol = this.findSymbol (this.safeString (ticker, 'symbol'), market);
let last = this.safeFloat (ticker, 'lastPrice');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': iso8601,
'high': this.safeFloat (ticker, 'highPrice'),
'low': this.safeFloat (ticker, 'lowPrice'),
'bid': this.safeFloat (ticker, 'bidPrice'),
'bidVolume': this.safeFloat (ticker, 'bidQty'),
'ask': this.safeFloat (ticker, 'askPrice'),
'askVolume': this.safeFloat (ticker, 'askQty'),
'vwap': this.safeFloat (ticker, 'weightedAvgPrice'),
'open': this.safeFloat (ticker, 'openPrice'),
'close': last,
'last': last,
'previousClose': this.safeFloat (ticker, 'prevClosePrice'), // previous day close
'change': this.safeFloat (ticker, 'priceChange'),
'percentage': this.safeFloat (ticker, 'priceChangePercent'),
'average': undefined,
'baseVolume': this.safeFloat (ticker, 'volume'),
'quoteVolume': this.safeFloat (ticker, 'quoteVolume'),
'info': ticker,
};
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
let market = this.market (symbol);
let response = await this.publicGetTicker24hr (this.extend ({
'symbol': market['id'],
}, params));
return this.parseTicker (response, market);
}
parseTickers (rawTickers, symbols = undefined) {
let tickers = [];
for (let i = 0; i < rawTickers.length; i++) {
tickers.push (this.parseTicker (rawTickers[i]));
}
return this.filterByArray (tickers, 'symbol', symbols);
}
async fetchBidAsks (symbols = undefined, params = {}) {
await this.loadMarkets ();
let rawTickers = await this.publicGetTickerBookTicker (params);
return this.parseTickers (rawTickers, symbols);
}
async fetchTickers (symbols = undefined, params = {}) {
await this.loadMarkets ();
let rawTickers = await this.publicGetTicker24hr (params);
return this.parseTickers (rawTickers, symbols);
}
parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
return [
ohlcv[0],
parseFloat (ohlcv[1]),
parseFloat (ohlcv[2]),
parseFloat (ohlcv[3]),
parseFloat (ohlcv[4]),
parseFloat (ohlcv[5]),
];
}
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let market = this.market (symbol);
let request = {
'symbol': market['id'],
'interval': this.timeframes[timeframe],
};
if (typeof since !== 'undefined')
request['startTime'] = since;
if (typeof limit !== 'undefined')
request['limit'] = limit; // default == max == 500
let response = await this.publicGetKlines (this.extend (request, params));
return this.parseOHLCVs (response, market, timeframe, since, limit);
}
parseTrade (trade, market = undefined) {
let timestampField = ('T' in trade) ? 'T' : 'time';
let timestamp = trade[timestampField];
let priceField = ('p' in trade) ? 'p' : 'price';
let price = parseFloat (trade[priceField]);
let amountField = ('q' in trade) ? 'q' : 'qty';
let amount = parseFloat (trade[amountField]);
let idField = ('a' in trade) ? 'a' : 'id';
let id = trade[idField].toString ();
let side = undefined;
let order = undefined;
if ('orderId' in trade)
order = trade['orderId'].toString ();
if ('m' in trade) {
side = trade['m'] ? 'sell' : 'buy'; // this is reversed intentionally
} else {
side = (trade['isBuyer']) ? 'buy' : 'sell'; // this is a true side
}
let fee = undefined;
if ('commission' in trade) {
fee = {
'cost': this.safeFloat (trade, 'commission'),
'currency': this.commonCurrencyCode (trade['commissionAsset']),
};
}
let takerOrMaker = undefined;
if ('isMaker' in trade)
takerOrMaker = trade['isMaker'] ? 'maker' : 'taker';
return {
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': market['symbol'],
'id': id,
'order': order,
'type': undefined,
'takerOrMaker': takerOrMaker,
'side': side,
'price': price,
'cost': price * amount,
'amount': amount,
'fee': fee,
};
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let market = this.market (symbol);
let request = {
'symbol': market['id'],
};
if (typeof since !== 'undefined') {
request['startTime'] = since;
request['endTime'] = since + 3600000;
}
if (typeof limit !== 'undefined')
request['limit'] = limit;
// 'fromId': 123, // ID to get aggregate trades from INCLUSIVE.
// 'startTime': 456, // Timestamp in ms to get aggregate trades from INCLUSIVE.
// 'endTime': 789, // Timestamp in ms to get aggregate trades until INCLUSIVE.
// 'limit': 500, // default = maximum = 500
let response = await this.publicGetAggTrades (this.extend (request, params));
return this.parseTrades (response, market, since, limit);
}
parseOrderStatus (status) {
let statuses = {
'NEW': 'open',
'PARTIALLY_FILLED': 'open',
'FILLED': 'closed',
'CANCELED': 'canceled',
};
return (status in statuses) ? statuses[status] : status.toLowerCase ();
}
parseOrder (order, market = undefined) {
let status = this.safeValue (order, 'status');
if (typeof status !== 'undefined')
status = this.parseOrderStatus (status);
let symbol = this.findSymbol (this.safeString (order, 'symbol'), market);
let timestamp = undefined;
if ('time' in order)
timestamp = order['time'];
else if ('transactTime' in order)
timestamp = order['transactTime'];
let iso8601 = undefined;
if (typeof timestamp !== 'undefined')
iso8601 = this.iso8601 (timestamp);
let price = this.safeFloat (order, 'price');
let amount = this.safeFloat (order, 'origQty');
let filled = this.safeFloat (order, 'executedQty', 0.0);
let remaining = undefined;
let cost = undefined;
if (typeof filled !== 'undefined') {
if (typeof amount !== 'undefined')
remaining = Math.max (amount - filled, 0.0);
if (typeof price !== 'undefined')
cost = price * filled;
}
let id = this.safeString (order, 'orderId');
let type = this.safeString (order, 'type');
if (typeof type !== 'undefined')
type = type.toLowerCase ();
let side = this.safeString (order, 'side');
if (typeof side !== 'undefined')
side = side.toLowerCase ();
let result = {
'info': order,
'id': id,
'timestamp': timestamp,
'datetime': iso8601,
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'amount': amount,
'cost': cost,
'filled': filled,
'remaining': remaining,
'status': status,
'fee': undefined,
};
return result;
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
let market = this.market (symbol);
// the next 5 lines are added to support for testing orders
let method = 'privatePostOrder';
let test = this.safeValue (params, 'test', false);
if (test) {
method += 'Test';
params = this.omit (params, 'test');
}
let order = {
'symbol': market['id'],
'quantity': this.amountToString (symbol, amount),
'type': type.toUpperCase (),
'side': side.toUpperCase (),
};
if (type === 'limit') {
order['type'] = this.options['defaultLimitOrderType'].toUpperCase ();
order = this.extend (order, {
'price': this.priceToPrecision (symbol, price),
'timeInForce': 'GTC', // 'GTC' = Good To Cancel (default), 'IOC' = Immediate Or Cancel
});
} else if (type === 'limit_maker') {
order['price'] = this.priceToPrecision (symbol, price);
}
let response = await this[method] (this.extend (order, params));
return this.parseOrder (response);
}
async fetchOrder (id, symbol = undefined, params = {}) {
if (!symbol)
throw new ExchangeError (this.id + ' fetchOrder requires a symbol param');
await this.loadMarkets ();
let market = this.market (symbol);
let origClientOrderId = this.safeValue (params, 'origClientOrderId');
let request = {
'symbol': market['id'],
};
if (typeof origClientOrderId !== 'undefined')
request['origClientOrderId'] = origClientOrderId;
else
request['orderId'] = parseInt (id);
let response = await this.privateGetOrder (this.extend (request, params));
return this.parseOrder (response, market);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (!symbol)
throw new ExchangeError (this.id + ' fetchOrders requires a symbol param');
await this.loadMarkets ();
let market = this.market (symbol);
let request = {
'symbol': market['id'],
};
if (limit)
request['limit'] = limit;
let response = await this.privateGetAllOrders (this.extend (request, params));
return this.parseOrders (response, market, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let market = undefined;
let request = {};
if (typeof symbol !== 'undefined') {
market = this.market (symbol);
request['symbol'] = market['id'];
} else if (this.options['warnOnFetchOpenOrdersWithoutSymbol']) {
let symbols = this.symbols;
let numSymbols = symbols.length;
let fetchOpenOrdersRateLimit = parseInt (numSymbols / 2);
throw new ExchangeError (this.id + ' fetchOpenOrders WARNING: fetching open orders without specifying a symbol is rate-limited to one call per ' + fetchOpenOrdersRateLimit.toString () + ' seconds. Do not call this method frequently to avoid ban. Set ' + this.id + '.options["warnOnFetchOpenOrdersWithoutSymbol"] = false to suppress this warning message.');
}
let response = await this.privateGetOpenOrders (this.extend (request, params));
return this.parseOrders (response, market, since, limit);
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
let orders = await this.fetchOrders (symbol, since, limit, params);
return this.filterBy (orders, 'status', 'closed');
}
async cancelOrder (id, symbol = undefined, params = {}) {
if (!symbol)
throw new ExchangeError (this.id + ' cancelOrder requires a symbol argument');
await this.loadMarkets ();
let market = this.market (symbol);
let response = await this.privateDeleteOrder (this.extend ({
'symbol': market['id'],
'orderId': parseInt (id),
// 'origClientOrderId': id,
}, params));
return response;
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (!symbol)
throw new ExchangeError (this.id + ' fetchMyTrades requires a symbol argument');
await this.loadMarkets ();
let market = this.market (symbol);
let request = {
'symbol': market['id'],
};
if (limit)
request['limit'] = limit;
let response = await this.privateGetMyTrades (this.extend (request, params));
return this.parseTrades (response, market, since, limit);
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
let currency = this.currency (code);
let response = await this.wapiGetDepositAddress (this.extend ({
'asset': currency['id'],
}, params));
if ('success' in response) {
if (response['success']) {
let address = this.safeString (response, 'address');
let tag = this.safeString (response, 'addressTag');
return {
'currency': code,
'address': this.checkAddress (address),
'tag': tag,
'status': 'ok',
'info': response,
};
}
}
}
async fetchFundingFees (codes = undefined, params = {}) {
// by default it will try load withdrawal fees of all currencies (with separate requests)
// however if you define codes = [ 'ETH', 'BTC' ] in args it will only load those
await this.loadMarkets ();
let withdrawFees = {};
let info = {};
if (typeof codes === 'undefined')
codes = Object.keys (this.currencies);
for (let i = 0; i < codes.length; i++) {
let code = codes[i];
let currency = this.currency (code);
let response = await this.wapiGetWithdrawFee ({
'asset': currency['id'],
});
withdrawFees[code] = this.safeFloat (response, 'withdrawFee');
info[code] = response;
}
return {
'withdraw': withdrawFees,
'deposit': {},
'info': info,
};
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
let currency = this.currency (code);
let name = address.slice (0, 20);
let request = {
'asset': currency['id'],
'address': address,
'amount': parseFloat (amount),
'name': name,
};
if (tag)
request['addressTag'] = tag;
let response = await this.wapiPostWithdraw (this.extend (request, params));
return {
'info': response,
'id': this.safeString (response, 'id'),
};
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = this.urls['api'][api];
url += '/' + path;
if (api === 'wapi')
url += '.html';
// v1 special case for userDataStream
if (path === 'userDataStream') {
body = this.urlencode (params);
headers = {
'X-MBX-APIKEY': this.apiKey,
'Content-Type': 'application/x-www-form-urlencoded',
};
} else if ((api === 'private') || (api === 'wapi')) {
this.checkRequiredCredentials ();
let query = this.urlencode (this.extend ({
'timestamp': this.nonce (),
'recvWindow': this.options['recvWindow'],
}, params));
let signature = this.hmac (this.encode (query), this.encode (this.secret));
query += '&' + 'signature=' + signature;
headers = {
'X-MBX-APIKEY': this.apiKey,
};
if ((method === 'GET') || (api === 'wapi')) {
url += '?' + query;
} else {
body = query;
headers['Content-Type'] = 'application/x-www-form-urlencoded';
}
} else {
if (Object.keys (params).length)
url += '?' + this.urlencode (params);
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (code, reason, url, method, headers, body) {
if ((code === 418) || (code === 429))
throw new DDoSProtection (this.id + ' ' + code.toString () + ' ' + reason + ' ' + body);
// error response in a form: { "code": -1013, "msg": "Invalid quantity." }
// following block cointains legacy checks against message patterns in "msg" property
// will switch "code" checks eventually, when we know all of them
if (code >= 400) {
if (body.indexOf ('Price * QTY is zero or less') >= 0)
throw new InvalidOrder (this.id + ' order cost = amount * price is zero or less ' + body);
if (body.indexOf ('LOT_SIZE') >= 0)
throw new InvalidOrder (this.id + ' order amount should be evenly divisible by lot size, use this.amountToLots (symbol, amount) ' + body);
if (body.indexOf ('PRICE_FILTER') >= 0)
throw new InvalidOrder (this.id + ' order price exceeds allowed price precision or invalid, use this.priceToPrecision (symbol, amount) ' + body);
}
if (body.length > 0) {
if (body[0] === '{') {
let response = JSON.parse (body);
// check success value for wapi endpoints
// response in format {'msg': 'The coin does not exist.', 'success': true/false}
let success = this.safeValue (response, 'success', true);
if (!success) {
if ('msg' in response)
try {
response = JSON.parse (response['msg']);
} catch (e) {
response = {};
}
}
// checks against error codes
let error = this.safeString (response, 'code');
if (typeof error !== 'undefined') {
const exceptions = this.exceptions;
if (error in exceptions) {
// a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
// despite that their message is very confusing, it is raised by Binance
// on a temporary ban (the API key is valid, but disabled for a while)
if ((error === '-2015') && this.options['hasAlreadyAuthenticatedSuccessfully']) {
throw new DDoSProtection (this.id + ' temporary banned: ' + body);
}
throw new exceptions[error] (this.id + ' ' + body);
} else {
throw new ExchangeError (this.id + ': unknown error code: ' + body + ' ' + error);
}
}
if (!success) {
throw new ExchangeError (this.id + ': success value false: ' + body);
}
}
}
}
async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let response = await this.fetch2 (path, api, method, params, headers, body);
// a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
if ((api === 'private') || (api === 'wapi'))
this.options['hasAlreadyAuthenticatedSuccessfully'] = true;
return response;
}
};