UNPKG

ccxt-bybit

Version:

A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges

925 lines (893 loc) 35.9 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { AuthenticationError, ArgumentsRequired, ExchangeError, InvalidOrder, BadRequest, OrderNotFound, DDoSProtection, BadSymbol } = require ('./base/errors'); // --------------------------------------------------------------------------- module.exports = class bitmart extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'bitmart', 'name': 'BitMart', 'countries': [ 'US', 'CN', 'HK', 'KR' ], 'rateLimit': 1000, 'version': 'v2', 'has': { 'CORS': true, 'fetchMarkets': true, 'fetchTicker': true, 'fetchTickers': true, 'fetchTime': true, 'fetchCurrencies': true, 'fetchOrderBook': true, 'fetchTrades': true, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchBalance': true, 'createOrder': true, 'cancelOrder': true, 'cancelAllOrders': true, 'fetchOrders': false, 'fetchOrderTrades': true, 'fetchOpenOrders': true, 'fetchClosedOrders': true, 'fetchCanceledOrders': true, 'fetchOrder': true, }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/61835713-a2662f80-ae85-11e9-9d00-6442919701fd.jpg', 'api': 'https://openapi.bitmart.com', 'www': 'https://www.bitmart.com/', 'doc': 'https://github.com/bitmartexchange/bitmart-official-api-docs', 'referral': 'http://www.bitmart.com/?r=rQCFLh', }, 'requiredCredentials': { 'apiKey': true, 'secret': true, 'uid': true, }, 'api': { 'token': { 'post': [ 'authentication', ], }, 'public': { 'get': [ 'currencies', 'ping', 'steps', 'symbols', 'symbols_details', 'symbols/{symbol}/kline', 'symbols/{symbol}/orders', 'symbols/{symbol}/trades', 'ticker', 'time', ], }, 'private': { 'get': [ 'orders', 'orders/{id}', 'trades', 'wallet', ], 'post': [ 'orders', ], 'delete': [ 'orders', 'orders/{id}', ], }, }, 'timeframes': { '1m': 1, '3m': 3, '5m': 5, '15m': 15, '30m': 30, '45m': 45, '1h': 60, '2h': 120, '3h': 180, '4h': 240, '1d': 1440, '1w': 10080, '1M': 43200, }, 'fees': { 'trading': { 'tierBased': true, 'percentage': true, 'taker': 0.002, 'maker': 0.001, 'tiers': { 'taker': [ [0, 0.20 / 100], [10, 0.18 / 100], [50, 0.16 / 100], [250, 0.14 / 100], [1000, 0.12 / 100], [5000, 0.10 / 100], [25000, 0.08 / 100], [50000, 0.06 / 100], ], 'maker': [ [0, 0.1 / 100], [10, 0.09 / 100], [50, 0.08 / 100], [250, 0.07 / 100], [1000, 0.06 / 100], [5000, 0.05 / 100], [25000, 0.04 / 100], [50000, 0.03 / 100], ], }, }, }, 'exceptions': { 'exact': { 'Place order error': InvalidOrder, // {"message":"Place order error"} 'Not found': OrderNotFound, // {"message":"Not found"} 'Visit too often, please try again later': DDoSProtection, // {"code":-30,"msg":"Visit too often, please try again later","subMsg":"","data":{}} 'Unknown symbol': BadSymbol, // {"message":"Unknown symbol"} }, 'broad': { 'Invalid limit. limit must be in the range': InvalidOrder, 'Maximum price is': InvalidOrder, // {"message":"Maximum price is 0.112695"} // {"message":"Required Integer parameter 'status' is not present"} // {"message":"Required String parameter 'symbol' is not present"} // {"message":"Required Integer parameter 'offset' is not present"} // {"message":"Required Integer parameter 'limit' is not present"} // {"message":"Required Long parameter 'from' is not present"} // {"message":"Required Long parameter 'to' is not present"} 'is not present': BadRequest, }, }, 'commonCurrencies': { 'ONE': 'Menlo One', 'PLA': 'Plair', }, }); } async fetchTime (params = {}) { const response = await this.publicGetTime (params); // // { // "server_time": 1527777538000 // } // return this.safeInteger (response, 'server_time'); } async signIn (params = {}) { const message = this.apiKey + ':' + this.secret + ':' + this.uid; const data = { 'grant_type': 'client_credentials', 'client_id': this.apiKey, 'client_secret': this.hmac (this.encode (message), this.encode (this.secret), 'sha256'), }; const response = await this.tokenPostAuthentication (this.extend (data, params)); const accessToken = this.safeString (response, 'access_token'); if (!accessToken) { throw new AuthenticationError (this.id + ' signIn() failed to authenticate. Access token missing from response.'); } const expiresIn = this.safeInteger (response, 'expires_in'); this.options['expires'] = this.sum (this.nonce (), expiresIn * 1000); this.options['accessToken'] = accessToken; return response; } async fetchMarkets (params = {}) { const markets = await this.publicGetSymbolsDetails (params); // // [ // { // "id":"1SG_BTC", // "base_currency":"1SG", // "quote_currency":"BTC", // "quote_increment":"0.1", // "base_min_size":"0.1000000000", // "base_max_size":"10000000.0000000000", // "price_min_precision":4, // "price_max_precision":6, // "expiration":"NA" // } // ] // const result = []; for (let i = 0; i < markets.length; i++) { const market = markets[i]; const id = this.safeString (market, 'id'); const baseId = this.safeString (market, 'base_currency'); const quoteId = this.safeString (market, 'quote_currency'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); const symbol = base + '/' + quote; // // https://github.com/bitmartexchange/bitmart-official-api-docs/blob/master/rest/public/symbols_details.md#response-details // from the above API doc: // quote_increment Minimum order price as well as the price increment // price_min_precision Minimum price precision (digit) used to query price and kline // price_max_precision Maximum price precision (digit) used to query price and kline // // the docs are wrong: https://github.com/ccxt/ccxt/issues/5612 // const quoteIncrement = this.safeString (market, 'quote_increment'); const amountPrecision = this.precisionFromString (quoteIncrement); const pricePrecision = this.safeInteger (market, 'price_max_precision'); const precision = { 'amount': amountPrecision, 'price': pricePrecision, }; const limits = { 'amount': { 'min': this.safeFloat (market, 'base_min_size'), 'max': this.safeFloat (market, 'base_max_size'), }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }; result.push ({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'baseId': baseId, 'quoteId': quoteId, 'precision': precision, 'limits': limits, 'info': market, }); } return result; } parseTicker (ticker, market = undefined) { const timestamp = this.milliseconds (); const marketId = this.safeString (ticker, 'symbol_id'); let symbol = undefined; if (marketId !== undefined) { if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; symbol = market['symbol']; } else if (marketId !== undefined) { const [ baseId, quoteId ] = marketId.split ('_'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote; } } const last = this.safeFloat (ticker, 'current_price'); const percentage = this.safeFloat (ticker, 'fluctuation'); return { 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeFloat (ticker, 'highest_price'), 'low': this.safeFloat (ticker, 'lowest_price'), 'bid': this.safeFloat (ticker, 'bid_1'), 'bidVolume': this.safeFloat (ticker, 'bid_1_amount'), 'ask': this.safeFloat (ticker, 'ask_1'), 'askVolume': this.safeFloat (ticker, 'ask_1_amount'), 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': percentage * 100, 'average': undefined, 'baseVolume': this.safeFloat (ticker, 'volume'), 'quoteVolume': this.safeFloat (ticker, 'base_volume'), 'info': ticker, }; } async fetchTicker (symbol, params = {}) { await this.loadMarkets (); const request = { 'symbol': this.marketId (symbol), }; const response = await this.publicGetTicker (this.extend (request, params)); // // { // "volume":"97487.38", // "ask_1":"0.00148668", // "base_volume":"144.59", // "lowest_price":"0.00144362", // "bid_1":"0.00148017", // "highest_price":"0.00151000", // "ask_1_amount":"92.03", // "current_price":"0.00148230", // "fluctuation":"+0.0227", // "symbol_id":"XRP_ETH", // "url":"https://www.bitmart.com/trade?symbol=XRP_ETH", // "bid_1_amount":"134.78" // } // return this.parseTicker (response); } async fetchTickers (symbols = undefined, params = {}) { await this.loadMarkets (); const tickers = await this.publicGetTicker (params); const result = {}; for (let i = 0; i < tickers.length; i++) { const ticker = this.parseTicker (tickers[i]); const symbol = ticker['symbol']; result[symbol] = ticker; } return result; } async fetchCurrencies (params = {}) { const currencies = await this.publicGetCurrencies (params); // // [ // { // "name":"CNY1", // "withdraw_enabled":false, // "id":"CNY1", // "deposit_enabled":false // } // ] // const result = {}; for (let i = 0; i < currencies.length; i++) { const currency = currencies[i]; const currencyId = this.safeString (currency, 'id'); const code = this.safeCurrencyCode (currencyId); const name = this.safeString (currency, 'name'); const withdrawEnabled = this.safeValue (currency, 'withdraw_enabled'); const depositEnabled = this.safeValue (currency, 'deposit_enabled'); const active = withdrawEnabled && depositEnabled; result[code] = { 'id': currencyId, 'code': code, 'name': name, 'info': currency, // the original payload 'active': active, 'fee': undefined, 'precision': undefined, 'limits': { 'amount': { 'min': undefined, 'max': undefined }, 'price': { 'min': undefined, 'max': undefined }, 'cost': { 'min': undefined, 'max': undefined }, 'withdraw': { 'min': undefined, 'max': undefined }, }, }; } return result; } async fetchOrderBook (symbol, limit = undefined, params = {}) { await this.loadMarkets (); const request = { 'symbol': this.marketId (symbol), // 'precision': 4, // optional price precision / depth level whose range is defined in symbol details }; const response = await this.publicGetSymbolsSymbolOrders (this.extend (request, params)); return this.parseOrderBook (response, undefined, 'buys', 'sells', 'price', 'amount'); } parseTrade (trade, market = undefined) { // // fetchTrades (public) // // { // "amount":"2.29275119", // "price":"0.021858", // "count":"104.8930", // "order_time":1563997286061, // "type":"sell" // } // // fetchMyTrades (private) // // { // active: true, // amount: '0.2000', // entrustType: 1, // entrust_id: 979648824, // fees: '0.0000085532', // price: '0.021383', // symbol: 'ETH_BTC', // timestamp: 1574343514000, // trade_id: 329418828 // }, // const id = this.safeString (trade, 'trade_id'); const timestamp = this.safeInteger2 (trade, 'timestamp', 'order_time'); const type = undefined; let side = this.safeStringLower (trade, 'type'); if ((side === undefined) && ('entrustType' in trade)) { side = trade['entrustType'] ? 'sell' : 'buy'; } const price = this.safeFloat (trade, 'price'); const amount = this.safeFloat (trade, 'amount'); let cost = undefined; if (price !== undefined) { if (amount !== undefined) { cost = amount * price; } } const orderId = this.safeInteger (trade, 'entrust_id'); const marketId = this.safeString (trade, 'symbol'); let symbol = undefined; if (marketId !== undefined) { if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; symbol = market['symbol']; } else { const [ baseId, quoteId ] = marketId.split ('_'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote; } } if (symbol === undefined) { if (market !== undefined) { symbol = market['symbol']; } } const feeCost = this.safeFloat (trade, 'fees'); let fee = undefined; if (feeCost !== undefined) { let feeCurrencyCode = undefined; if (market !== undefined) { feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote']; } fee = { 'cost': feeCost, 'currency': feeCurrencyCode, }; } return { 'info': trade, 'id': id, 'order': orderId, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'amount': amount, 'cost': cost, 'takerOrMaker': undefined, 'fee': fee, }; } async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], }; const response = await this.publicGetSymbolsSymbolTrades (this.extend (request, params)); // // [ // { // "amount":"2.29275119", // "price":"0.021858", // "count":"104.8930", // "order_time":1563997286061, // "type":"sell" // } // ] // return this.parseTrades (response, market, since, limit); } async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); // limit is required, must be in the range (0, 50) const maxLimit = 50; limit = (limit === undefined) ? maxLimit : Math.min (limit, maxLimit); const request = { 'symbol': market['id'], 'offset': 0, // current page, starts from 0 'limit': limit, // required }; const response = await this.privateGetTrades (this.extend (request, params)); // // { // "total_trades": 216, // "total_pages": 22, // "current_page": 0, // "trades": [ // { // "symbol": "BMX_ETH", // "amount": "1.0", // "fees": "0.0005000000", // "trade_id": 2734956, // "price": "0.00013737", // "active": true, // "entrust_id": 5576623, // "timestamp": 1545292334000 // }, // ] // } // const trades = this.safeValue (response, 'trades', []); return this.parseTrades (trades, market, since, limit); } async fetchOrderTrades (id, symbol = undefined, since = undefined, limit = undefined, params = {}) { await this.loadMarkets (); const request = { 'entrust_id': id, }; return await this.fetchMyTrades (symbol, since, limit, this.extend (request, params)); } parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) { return [ this.safeInteger (ohlcv, 'timestamp'), this.safeFloat (ohlcv, 'open_price'), this.safeFloat (ohlcv, 'highest_price'), this.safeFloat (ohlcv, 'lowest_price'), this.safeFloat (ohlcv, 'current_price'), this.safeFloat (ohlcv, 'volume'), ]; } async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { if (since === undefined && limit === undefined) { throw new ArgumentsRequired (this.id + ' fetchOHLCV requires either a `since` argument or a `limit` argument (or both)'); } await this.loadMarkets (); const market = this.market (symbol); const periodInSeconds = this.parseTimeframe (timeframe); const duration = periodInSeconds * limit * 1000; let to = this.milliseconds (); if (since === undefined) { since = to - duration; } else { to = this.sum (since, duration); } const request = { 'symbol': market['id'], 'from': since, // start time of k-line data (in milliseconds, required) 'to': to, // end time of k-line data (in milliseconds, required) 'step': this.timeframes[timeframe], // steps of sampling (in minutes, default 1 minute, optional) }; const response = await this.publicGetSymbolsSymbolKline (this.extend (request, params)); // // [ // { // "timestamp":1525761000000, // "open_price":"0.010130", // "highest_price":"0.010130", // "lowest_price":"0.010130", // "current_price":"0.010130", // "volume":"0.000000" // } // ] // return this.parseOHLCVs (response, market, timeframe, since, limit); } async fetchBalance (params = {}) { await this.loadMarkets (); const balances = await this.privateGetWallet (params); // // [ // { // "name":"Bitcoin", // "available":"0.0000000000", // "frozen":"0.0000000000", // "id":"BTC" // } // ] // const result = { 'info': balances }; for (let i = 0; i < balances.length; i++) { const balance = balances[i]; const currencyId = this.safeString (balance, 'id'); const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['free'] = this.safeFloat (balance, 'available'); account['used'] = this.safeFloat (balance, 'frozen'); result[code] = account; } return this.parseBalance (result); } parseOrder (order, market = undefined) { // // createOrder // // { // "entrust_id":1223181 // } // // cancelOrder // // {} // // fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders // // { // "entrust_id":1223181, // "symbol":"BMX_ETH", // "timestamp":1528060666000, // "side":"buy", // "price":"1.000000", // "fees":"0.1", // "original_amount":"1", // "executed_amount":"1", // "remaining_amount":"0", // "status":3 // } // const id = this.safeString (order, 'entrust_id'); const timestamp = this.milliseconds (); const status = this.parseOrderStatus (this.safeString (order, 'status')); let symbol = undefined; const marketId = this.safeString (order, 'symbol'); if (marketId !== undefined) { if (marketId in this.markets_by_id) { market = this.markets_by_id[marketId]; } else { const [ baseId, quoteId ] = marketId.split ('_'); const base = this.safeCurrencyCode (baseId); const quote = this.safeCurrencyCode (quoteId); symbol = base + '/' + quote; } } if ((symbol === undefined) && (market !== undefined)) { symbol = market['symbol']; } const price = this.safeFloat (order, 'price'); const amount = this.safeFloat (order, 'original_amount'); let cost = undefined; let filled = this.safeFloat (order, 'executed_amount'); let remaining = this.safeFloat (order, 'remaining_amount'); if (amount !== undefined) { if (remaining !== undefined) { if (filled === undefined) { filled = amount - remaining; } } if (filled !== undefined) { if (remaining === undefined) { remaining = amount - filled; } if (cost === undefined) { if (price !== undefined) { cost = price * filled; } } } } const side = this.safeString (order, 'side'); const type = undefined; return { 'id': id, 'info': order, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'lastTradeTimestamp': undefined, 'symbol': symbol, 'type': type, 'side': side, 'price': price, 'amount': amount, 'cost': undefined, 'average': undefined, 'filled': filled, 'remaining': remaining, 'status': status, 'fee': undefined, 'trades': undefined, }; } parseOrderStatus (status) { const statuses = { '0': 'all', '1': 'open', '2': 'open', '3': 'closed', '4': 'canceled', '5': 'open', '6': 'closed', }; return this.safeString (statuses, status, status); } async createOrder (symbol, type, side, amount, price = undefined, params = {}) { if (type !== 'limit') { throw new ExchangeError (this.id + ' allows limit orders only'); } await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'side': side.toLowerCase (), 'amount': this.amountToPrecision (symbol, amount), 'price': this.priceToPrecision (symbol, price), }; const response = await this.privatePostOrders (this.extend (request, params)); // // { // "entrust_id":1223181 // } // return this.parseOrder (response, market); } async cancelOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); const intId = parseInt (id); const request = { 'id': intId, 'entrust_id': intId, }; const response = await this.privateDeleteOrdersId (this.extend (request, params)); // // responds with an empty object {} // return this.parseOrder (response); } async cancelAllOrders (symbol = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' cancelAllOrders requires a symbol argument'); } const side = this.safeString (params, 'side'); if (side === undefined) { throw new ArgumentsRequired (this.id + " cancelAllOrders requires a `side` parameter ('buy' or 'sell')"); } await this.loadMarkets (); const market = this.market (symbol); const request = { 'symbol': market['id'], 'side': side, // 'buy' or 'sell' }; const response = await this.privateDeleteOrders (this.extend (request, params)); // // responds with an empty object {} // return response; } async fetchOrdersByStatus (status, symbol = undefined, since = undefined, limit = undefined, params = {}) { if (symbol === undefined) { throw new ArgumentsRequired (this.id + ' fetchOrdersByStatus requires a symbol argument'); } await this.loadMarkets (); const market = this.market (symbol); if (limit === undefined) { limit = 500; // default 500, max 1000 } const request = { 'symbol': market['id'], 'status': status, 'offset': 0, // current page, starts from 0 'limit': limit, }; const response = await this.privateGetOrders (this.extend (request, params)); // // { // "orders":[ // { // "entrust_id":1223181, // "symbol":"BMX_ETH", // "timestamp":1528060666000, // "side":"buy", // "price":"1.000000", // "fees":"0.1", // "original_amount":"1", // "executed_amount":"1", // "remaining_amount":"0", // "status":3 // } // ], // "total_pages":1, // "total_orders":1, // "current_page":0, // } // const orders = this.safeValue (response, 'orders', []); return this.parseOrders (orders, market, since, limit); } async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { // 5 = pending & partially filled orders return await this.fetchOrdersByStatus (5, symbol, since, limit, params); } async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { // 3 = closed orders return await this.fetchOrdersByStatus (3, symbol, since, limit, params); } async fetchCanceledOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) { // 4 = canceled orders return await this.fetchOrdersByStatus (4, symbol, since, limit, params); } async fetchOrder (id, symbol = undefined, params = {}) { await this.loadMarkets (); const request = { 'id': id, }; const response = await this.privateGetOrdersId (this.extend (request, params)); // // { // "entrust_id":1223181, // "symbol":"BMX_ETH", // "timestamp":1528060666000, // "side":"buy", // "price":"1.000000", // "fees":"0.1", // "original_amount":"1", // "executed_amount":"1", // "remaining_amount":"0", // "status":3 // } // return this.parseOrder (response); } nonce () { return this.milliseconds (); } sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params); let query = this.omit (params, this.extractParams (path)); if (api === 'public') { if (Object.keys (query).length) { url += '?' + this.urlencode (query); } } else if (api === 'token') { this.checkRequiredCredentials (); body = this.urlencode (query); headers = { 'Content-Type': 'application/x-www-form-urlencoded', }; } else { const nonce = this.nonce (); this.checkRequiredCredentials (); const token = this.safeString (this.options, 'accessToken'); if (token === undefined) { throw new AuthenticationError (this.id + ' ' + path + ' endpoint requires an accessToken option or a prior call to signIn() method'); } const expires = this.safeInteger (this.options, 'expires'); if (expires !== undefined) { if (nonce >= expires) { throw new AuthenticationError (this.id + ' accessToken expired, supply a new accessToken or call the signIn() method'); } } if (Object.keys (query).length) { url += '?' + this.urlencode (query); } headers = { 'Content-Type': 'application/json', 'X-BM-TIMESTAMP': nonce.toString (), 'X-BM-AUTHORIZATION': 'Bearer ' + token, }; if (method !== 'GET') { query = this.keysort (query); body = this.json (query); const message = this.urlencode (query); headers['X-BM-SIGNATURE'] = this.hmac (this.encode (message), this.encode (this.secret), 'sha256'); } } return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } handleErrors (code, reason, url, method, headers, body, response, requestHeaders, requestBody) { if (response === undefined) { return; } // // {"message":"Maximum price is 0.112695"} // {"message":"Required Integer parameter 'status' is not present"} // {"message":"Required String parameter 'symbol' is not present"} // {"message":"Required Integer parameter 'offset' is not present"} // {"message":"Required Integer parameter 'limit' is not present"} // {"message":"Required Long parameter 'from' is not present"} // {"message":"Required Long parameter 'to' is not present"} // {"message":"Invalid status. status=6 not support any more, please use 3:deal_success orders, 4:cancelled orders"} // {"message":"Not found"} // {"message":"Place order error"} // const feedback = this.id + ' ' + body; const message = this.safeString2 (response, 'message', 'msg'); if (message !== undefined) { this.throwExactlyMatchedException (this.exceptions['exact'], message, feedback); this.throwBroadlyMatchedException (this.exceptions['broad'], message, feedback); throw new ExchangeError (feedback); // unknown message } } };