UNPKG

sfccxt

Version:

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

1,098 lines (1,050 loc) 136 kB
'use strict'; // ---------------------------------------------------------------------------- /* eslint-disable */ const functions = require ('./functions'); const { isNode , clone , unCamelCase , throttle , timeout , TimedOut , defaultFetch } = functions const { // eslint-disable-line object-curly-newline ExchangeError , BadSymbol , NullResponse , InvalidAddress , InvalidOrder , NotSupported , AuthenticationError , DDoSProtection , RequestTimeout , ExchangeNotAvailable , RateLimitExceeded , ArgumentsRequired } = require ('./errors') const { TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING, TICK_SIZE } = functions.precisionConstants const BN = require ('../static_dependencies/BN/bn') const Precise = require ('./Precise') // ---------------------------------------------------------------------------- module.exports = class Exchange { describe () { return { 'id': undefined, 'name': undefined, 'countries': undefined, 'enableRateLimit': true, 'rateLimit': 2000, // milliseconds = seconds * 1000 'certified': false, // if certified by the CCXT dev team 'pro': false, // if it is integrated with CCXT Pro for WebSocket support 'alias': false, // whether this exchange is an alias to another exchange 'has': { 'publicAPI': true, 'privateAPI': true, 'CORS': undefined, 'spot': undefined, 'margin': undefined, 'swap': undefined, 'future': undefined, 'option': undefined, 'addMargin': undefined, 'cancelAllOrders': undefined, 'cancelOrder': true, 'cancelOrders': undefined, 'createDepositAddress': undefined, 'createLimitOrder': true, 'createMarketOrder': true, 'createOrder': true, 'createPostOnlyOrder': undefined, 'createReduceOnlyOrder': undefined, 'createStopOrder': undefined, 'createStopLimitOrder': undefined, 'createStopMarketOrder': undefined, 'editOrder': 'emulated', 'fetchAccounts': undefined, 'fetchBalance': true, 'fetchBidsAsks': undefined, 'fetchBorrowInterest': undefined, 'fetchBorrowRate': undefined, 'fetchBorrowRateHistory': undefined, 'fetchBorrowRatesPerSymbol': undefined, 'fetchBorrowRates': undefined, 'fetchCanceledOrders': undefined, 'fetchClosedOrder': undefined, 'fetchClosedOrders': undefined, 'fetchCurrencies': 'emulated', 'fetchDeposit': undefined, 'fetchDepositAddress': undefined, 'fetchDepositAddresses': undefined, 'fetchDepositAddressesByNetwork': undefined, 'fetchDeposits': undefined, 'fetchTransactionFee': undefined, 'fetchTransactionFees': undefined, 'fetchFundingHistory': undefined, 'fetchFundingRate': undefined, 'fetchFundingRateHistory': undefined, 'fetchFundingRates': undefined, 'fetchIndexOHLCV': undefined, 'fetchL2OrderBook': true, 'fetchLedger': undefined, 'fetchLedgerEntry': undefined, 'fetchLeverageTiers': undefined, 'fetchMarketLeverageTiers': undefined, 'fetchMarkets': true, 'fetchMarkOHLCV': undefined, 'fetchMyTrades': undefined, 'fetchOHLCV': 'emulated', 'fetchOpenInterest': undefined, 'fetchOpenInterestHistory': undefined, 'fetchOpenOrder': undefined, 'fetchOpenOrders': undefined, 'fetchOrder': undefined, 'fetchOrderBook': true, 'fetchOrderBooks': undefined, 'fetchOrders': undefined, 'fetchOrderTrades': undefined, 'fetchPermissions': undefined, 'fetchPosition': undefined, 'fetchPositions': undefined, 'fetchPositionsRisk': undefined, 'fetchPremiumIndexOHLCV': undefined, 'fetchStatus': 'emulated', 'fetchTicker': true, 'fetchTickers': undefined, 'fetchTime': undefined, 'fetchTrades': true, 'fetchTradingFee': undefined, 'fetchTradingFees': undefined, 'fetchTradingLimits': undefined, 'fetchTransactions': undefined, 'fetchTransfers': undefined, 'fetchWithdrawAddresses': undefined, 'fetchWithdrawal': undefined, 'fetchWithdrawals': undefined, 'reduceMargin': undefined, 'setLeverage': undefined, 'setMargin': undefined, 'setMarginMode': undefined, 'setPositionMode': undefined, 'signIn': undefined, 'transfer': undefined, 'withdraw': undefined, }, 'urls': { 'logo': undefined, 'api': undefined, 'www': undefined, 'doc': undefined, 'fees': undefined, }, 'api': undefined, 'requiredCredentials': { 'apiKey': true, 'secret': true, 'uid': false, 'login': false, 'password': false, 'twofa': false, // 2-factor authentication (one-time password key) 'privateKey': false, // a "0x"-prefixed hexstring private key for a wallet 'walletAddress': false, // the wallet address "0x"-prefixed hexstring 'token': false, // reserved for HTTP auth in some cases }, 'markets': undefined, // to be filled manually or by fetchMarkets 'currencies': {}, // to be filled manually or by fetchMarkets 'timeframes': undefined, // redefine if the exchange has.fetchOHLCV 'fees': { 'trading': { 'tierBased': undefined, 'percentage': undefined, 'taker': undefined, 'maker': undefined, }, 'funding': { 'tierBased': undefined, 'percentage': undefined, 'withdraw': {}, 'deposit': {}, }, }, 'status': { 'status': 'ok', 'updated': undefined, 'eta': undefined, 'url': undefined, }, 'exceptions': undefined, 'httpExceptions': { '422': ExchangeError, '418': DDoSProtection, '429': RateLimitExceeded, '404': ExchangeNotAvailable, '409': ExchangeNotAvailable, '410': ExchangeNotAvailable, '451': ExchangeNotAvailable, '500': ExchangeNotAvailable, '501': ExchangeNotAvailable, '502': ExchangeNotAvailable, '520': ExchangeNotAvailable, '521': ExchangeNotAvailable, '522': ExchangeNotAvailable, '525': ExchangeNotAvailable, '526': ExchangeNotAvailable, '400': ExchangeNotAvailable, '403': ExchangeNotAvailable, '405': ExchangeNotAvailable, '503': ExchangeNotAvailable, '530': ExchangeNotAvailable, '408': RequestTimeout, '504': RequestTimeout, '401': AuthenticationError, '407': AuthenticationError, '511': AuthenticationError, }, 'commonCurrencies': { // gets extended/overwritten in subclasses 'XBT': 'BTC', 'BCC': 'BCH', 'BCHABC': 'BCH', 'BCHSV': 'BSV', }, 'precisionMode': DECIMAL_PLACES, 'paddingMode': NO_PADDING, 'limits': { 'leverage': { 'min': undefined, 'max': undefined }, 'amount': { 'min': undefined, 'max': undefined }, 'price': { 'min': undefined, 'max': undefined }, 'cost': { 'min': undefined, 'max': undefined }, }, } // return } // describe () constructor (userConfig = {}) { Object.assign (this, functions) // // if (isNode) { // this.nodeVersion = process.version.match (/\d+\.\d+\.\d+/)[0] //§ this.userAgent = { // 'User-Agent': 'ccxt/' + Exchange.ccxtVersion + // ' (+https://github.com/ccxt/ccxt)' + // ' Node.js/' + this.nodeVersion + ' (JavaScript)' // } // } // this.options = this.getDefaultOptions(); // exchange-specific options, if any // fetch implementation options (JS only) this.fetchOptions = { // keepalive: true, // does not work in Chrome, https://github.com/ccxt/ccxt/issues/6368 } // http properties this.userAgents = { 'chrome': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36', 'chrome39': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36', 'chrome100': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36', } this.headers = {} // prepended to URL, like https://proxy.com/https://exchange.com/api... this.proxy = '' this.origin = '*' // CORS origin // underlying properties this.minFundingAddressLength = 1 // used in checkAddress this.substituteCommonCurrencyCodes = true // reserved this.quoteJsonNumbers = true // treat numbers in json as quoted precise strings this.number = Number // or String (a pointer to a function) this.handleContentTypeApplicationZip = false // whether fees should be summed by currency code this.reduceFees = true // do not delete this line, it is needed for users to be able to define their own fetchImplementation this.fetchImplementation = defaultFetch this.validateServerSsl = true this.validateClientSsl = false // default property values this.timeout = 10000 // milliseconds this.verbose = false this.debug = false this.userAgent = undefined this.twofa = undefined // two-factor authentication (2FA) // default credentials this.apiKey = undefined this.secret = undefined this.uid = undefined this.login = undefined this.password = undefined this.privateKey = undefined // a "0x"-prefixed hexstring private key for a wallet this.walletAddress = undefined // a wallet address "0x"-prefixed hexstring this.token = undefined // reserved for HTTP auth in some cases // placeholders for cached data this.balance = {} this.orderbooks = {} this.tickers = {} this.orders = undefined this.trades = {} this.transactions = {} this.ohlcvs = {} this.myTrades = undefined this.positions = {} // web3 and cryptography flags this.requiresWeb3 = false this.requiresEddsa = false this.precision = {} // response handling flags and properties this.lastRestRequestTimestamp = 0 this.enableLastJsonResponse = true this.enableLastHttpResponse = true this.enableLastResponseHeaders = true this.last_http_response = undefined this.last_json_response = undefined this.last_response_headers = undefined // camelCase and snake_notation support const unCamelCaseProperties = (obj = this) => { if (obj !== null) { const ownPropertyNames = Object.getOwnPropertyNames (obj) for (let i = 0; i < ownPropertyNames.length; i++) { const k = ownPropertyNames[i] this[unCamelCase (k)] = this[k] } unCamelCaseProperties (Object.getPrototypeOf (obj)) } } unCamelCaseProperties () // merge constructor overrides to this instance const configEntries = Object.entries (this.describe ()).concat (Object.entries (userConfig)) for (let i = 0; i < configEntries.length; i++) { const [property, value] = configEntries[i] if (value && Object.getPrototypeOf (value) === Object.prototype) { this[property] = this.deepExtend (this[property], value) } else { this[property] = value } } // http client options const agentOptions = { 'keepAlive': true, } // ssl options if (!this.validateServerSsl) { agentOptions['rejectUnauthorized'] = false; } // js-specific http options if (!this.httpAgent && defaultFetch.http && isNode) { this.httpAgent = new defaultFetch.http.Agent (agentOptions) } if (!this.httpsAgent && defaultFetch.https && isNode) { this.httpsAgent = new defaultFetch.https.Agent (agentOptions) } // generate old metainfo interface const hasKeys = Object.keys (this.has) for (let i = 0; i < hasKeys.length; i++) { const k = hasKeys[i] this['has' + this.capitalize (k)] = !!this.has[k] // converts 'emulated' to true } // generate implicit api if (this.api) { this.defineRestApi (this.api, 'request') } // init the request rate limiter this.initRestRateLimiter () // init predefined markets if any if (this.markets) { this.setMarkets (this.markets) } } encodeURIComponent (...args) { return encodeURIComponent (...args) } checkRequiredVersion (requiredVersion, error = true) { let result = true const [ major1, minor1, patch1 ] = requiredVersion.split ('.') , [ major2, minor2, patch2 ] = Exchange.ccxtVersion.split ('.') , intMajor1 = parseInt (major1) , intMinor1 = parseInt (minor1) , intPatch1 = parseInt (patch1) , intMajor2 = parseInt (major2) , intMinor2 = parseInt (minor2) , intPatch2 = parseInt (patch2) if (intMajor1 > intMajor2) { result = false } if (intMajor1 === intMajor2) { if (intMinor1 > intMinor2) { result = false } else if (intMinor1 === intMinor2 && intPatch1 > intPatch2) { result = false } } if (!result) { if (error) { throw new NotSupported ('Your current version of CCXT is ' + Exchange.ccxtVersion + ', a newer version ' + requiredVersion + ' is required, please, upgrade your version of CCXT') } else { return error } } return result } checkAddress (address) { if (address === undefined) { throw new InvalidAddress (this.id + ' address is undefined') } // check the address is not the same letter like 'aaaaa' nor too short nor has a space if ((this.unique (address).length === 1) || address.length < this.minFundingAddressLength || address.includes (' ')) { throw new InvalidAddress (this.id + ' address is invalid or has less than ' + this.minFundingAddressLength.toString () + ' characters: "' + this.json (address) + '"') } return address } initRestRateLimiter () { if (this.rateLimit === undefined) { throw new Error (this.id + '.rateLimit property is not configured'); } this.tokenBucket = this.extend ({ delay: 0.001, capacity: 1, cost: 1, maxCapacity: 1000, refillRate: (this.rateLimit > 0) ? 1 / this.rateLimit : Number.MAX_VALUE, }, this.tokenBucket); this.throttle = throttle (this.tokenBucket); this.executeRestRequest = (url, method = 'GET', headers = undefined, body = undefined) => { // fetchImplementation cannot be called on this. in browsers: // TypeError Failed to execute 'fetch' on 'Window': Illegal invocation const fetchImplementation = this.fetchImplementation; const params = { method, headers, body, timeout: this.timeout }; if (this.agent) { params['agent'] = this.agent; } else if (this.httpAgent && url.indexOf ('http://') === 0) { params['agent'] = this.httpAgent; } else if (this.httpsAgent && url.indexOf ('https://') === 0) { params['agent'] = this.httpsAgent; } const promise = fetchImplementation (url, this.extend (params, this.fetchOptions)) .catch ((e) => { if (isNode) { throw new ExchangeNotAvailable ([ this.id, method, url, e.type, e.message ].join (' ')); } throw e; // rethrow all unknown errors }) .then ((response) => this.handleRestResponse (response, url, method, headers, body)); return timeout (this.timeout, promise).catch ((e) => { if (e instanceof TimedOut) { throw new RequestTimeout (this.id + ' ' + method + ' ' + url + ' request timed out (' + this.timeout + ' ms)'); } throw e; }) }; } setSandboxMode (enabled) { if (!!enabled) { // eslint-disable-line no-extra-boolean-cast if ('test' in this.urls) { if (typeof this.urls['api'] === 'string') { this.urls['apiBackup'] = this.urls['api'] this.urls['api'] = this.urls['test'] } else { this.urls['apiBackup'] = clone (this.urls['api']) this.urls['api'] = clone (this.urls['test']) } } else { throw new NotSupported (this.id + ' does not have a sandbox URL') } } else if ('apiBackup' in this.urls) { if (typeof this.urls['api'] === 'string') { this.urls['api'] = this.urls['apiBackup'] } else { this.urls['api'] = clone (this.urls['apiBackup']) } } } defineRestApiEndpoint (methodName, uppercaseMethod, lowercaseMethod, camelcaseMethod, path, paths, config = {}) { const splitPath = path.split (/[^a-zA-Z0-9]/) const camelcaseSuffix = splitPath.map (this.capitalize).join ('') const underscoreSuffix = splitPath.map ((x) => x.trim ().toLowerCase ()).filter ((x) => x.length > 0).join ('_') const camelcasePrefix = [ paths[0] ].concat (paths.slice (1).map (this.capitalize)).join ('') const underscorePrefix = [ paths[0] ].concat (paths.slice (1).map ((x) => x.trim ()).filter ((x) => x.length > 0)).join ('_') const camelcase = camelcasePrefix + camelcaseMethod + this.capitalize (camelcaseSuffix) const underscore = underscorePrefix + '_' + lowercaseMethod + '_' + underscoreSuffix const typeArgument = (paths.length > 1) ? paths : paths[0] // handle call costs here const partial = async (params = {}, context = {}) => this[methodName] (path, typeArgument, uppercaseMethod, params, undefined, undefined, config, context) // const partial = async (params) => this[methodName] (path, typeArgument, uppercaseMethod, params || {}) this[camelcase] = partial this[underscore] = partial } defineRestApi (api, methodName, paths = []) { const keys = Object.keys (api) for (let i = 0; i < keys.length; i++) { const key = keys[i] const value = api[key] const uppercaseMethod = key.toUpperCase () const lowercaseMethod = key.toLowerCase () const camelcaseMethod = this.capitalize (lowercaseMethod) if (Array.isArray (value)) { for (let k = 0; k < value.length; k++) { const path = value[k].trim () this.defineRestApiEndpoint (methodName, uppercaseMethod, lowercaseMethod, camelcaseMethod, path, paths) } // the options HTTP method conflicts with the 'options' API url path // } else if (key.match (/^(?:get|post|put|delete|options|head|patch)$/i)) { } else if (key.match (/^(?:get|post|put|delete|head|patch)$/i)) { const endpoints = Object.keys (value); for (let j = 0; j < endpoints.length; j++) { const endpoint = endpoints[j] const path = endpoint.trim () const config = value[endpoint] if (typeof config === 'object') { this.defineRestApiEndpoint (methodName, uppercaseMethod, lowercaseMethod, camelcaseMethod, path, paths, config) } else if (typeof config === 'number') { this.defineRestApiEndpoint (methodName, uppercaseMethod, lowercaseMethod, camelcaseMethod, path, paths, { cost: config }) } else { throw new NotSupported (this.id + ' defineRestApi() API format is not supported, API leafs must strings, objects or numbers'); } } } else { this.defineRestApi (value, methodName, paths.concat ([ key ])) } } } log (... args) { console.log (... args) } fetch (url, method = 'GET', headers = undefined, body = undefined) { if (isNode && this.userAgent) { if (typeof this.userAgent === 'string') { headers = this.extend ({ 'User-Agent': this.userAgent }, headers) } else if ((typeof this.userAgent === 'object') && ('User-Agent' in this.userAgent)) { headers = this.extend (this.userAgent, headers) } } if (typeof this.proxy === 'function') { url = this.proxy (url) if (isNode) { headers = this.extend ({ 'Origin': this.origin }, headers) } } else if (typeof this.proxy === 'string') { if (this.proxy.length && isNode) { headers = this.extend ({ 'Origin': this.origin }, headers) } url = this.proxy + url } headers = this.extend (this.headers, headers) headers = this.setHeaders (headers) if (this.verbose) { this.log ("fetch Request:\n", this.id, method, url, "\nRequestHeaders:\n", headers, "\nRequestBody:\n", body, "\n") } return this.executeRestRequest (url, method, headers, body) } parseJson (jsonString) { try { if (this.isJsonEncodedObject (jsonString)) { return JSON.parse (this.onJsonResponse (jsonString)) } } catch (e) { // SyntaxError return undefined } } getResponseHeaders (response) { const result = {} response.headers.forEach ((value, key) => { key = key.split ('-').map ((word) => this.capitalize (word)).join ('-') result[key] = value }) return result } handleRestResponse (response, url, method = 'GET', requestHeaders = undefined, requestBody = undefined) { const responseHeaders = this.getResponseHeaders (response) if (this.handleContentTypeApplicationZip && (responseHeaders['Content-Type'] === 'application/zip')) { const responseBuffer = response.buffer (); if (this.enableLastResponseHeaders) { this.last_response_headers = responseHeaders } if (this.enableLastHttpResponse) { this.last_http_response = responseBuffer } if (this.verbose) { this.log ("handleRestResponse:\n", this.id, method, url, response.status, response.statusText, "\nResponseHeaders:\n", responseHeaders, "ZIP redacted", "\n") } // no error handler needed, because it would not be a zip response in case of an error return responseBuffer; } return response.text ().then ((responseBody) => { const bodyText = this.onRestResponse (response.status, response.statusText, url, method, responseHeaders, responseBody, requestHeaders, requestBody); const json = this.parseJson (bodyText) if (this.enableLastResponseHeaders) { this.last_response_headers = responseHeaders } if (this.enableLastHttpResponse) { this.last_http_response = responseBody } if (this.enableLastJsonResponse) { this.last_json_response = json } if (this.verbose) { this.log ("handleRestResponse:\n", this.id, method, url, response.status, response.statusText, "\nResponseHeaders:\n", responseHeaders, "\nResponseBody:\n", responseBody, "\n") } const skipFurtherErrorHandling = this.handleErrors (response.status, response.statusText, url, method, responseHeaders, responseBody, json, requestHeaders, requestBody) if (!skipFurtherErrorHandling) { this.handleHttpStatusCode (response.status, response.statusText, url, method, responseBody) } return json || responseBody }) } onRestResponse (statusCode, statusText, url, method, responseHeaders, responseBody, requestHeaders, requestBody) { return responseBody.trim () } onJsonResponse (responseBody) { return this.quoteJsonNumbers ? responseBody.replace (/":([+.0-9eE-]+)([,}])/g, '":"$1"$2') : responseBody; } async loadMarketsHelper (reload = false, params = {}) { if (!reload && this.markets) { if (!this.markets_by_id) { return this.setMarkets (this.markets) } return this.markets } let currencies = undefined // only call if exchange API provides endpoint (true), thus avoid emulated versions ('emulated') if (this.has.fetchCurrencies === true) { currencies = await this.fetchCurrencies () } const markets = await this.fetchMarkets (params) return this.setMarkets (markets, currencies) } loadMarkets (reload = false, params = {}) { // this method is async, it returns a promise if ((reload && !this.reloadingMarkets) || !this.marketsLoading) { this.reloadingMarkets = true this.marketsLoading = this.loadMarketsHelper (reload, params).then ((resolved) => { this.reloadingMarkets = false return resolved }, (error) => { this.reloadingMarkets = false throw error }) } return this.marketsLoading } fetchCurrencies (params = {}) { // markets are returned as a list // currencies are returned as a dict // this is for historical reasons // and may be changed for consistency later return new Promise ((resolve, reject) => resolve (this.currencies)); } fetchMarkets (params = {}) { // markets are returned as a list // currencies are returned as a dict // this is for historical reasons // and may be changed for consistency later return new Promise ((resolve, reject) => resolve (Object.values (this.markets))) } filterBySinceLimit (array, since = undefined, limit = undefined, key = 'timestamp', tail = false) { const sinceIsDefined = (since !== undefined && since !== null) if (sinceIsDefined) { array = array.filter ((entry) => entry[key] >= since) } if (limit !== undefined && limit !== null) { array = tail ? array.slice (-limit) : array.slice (0, limit) } return array } filterByValueSinceLimit (array, field, value = undefined, since = undefined, limit = undefined, key = 'timestamp', tail = false) { const valueIsDefined = value !== undefined && value !== null const sinceIsDefined = since !== undefined && since !== null // single-pass filter for both symbol and since if (valueIsDefined || sinceIsDefined) { array = array.filter ((entry) => ((valueIsDefined ? (entry[field] === value) : true) && (sinceIsDefined ? (entry[key] >= since) : true))) } if (limit !== undefined && limit !== null) { array = tail ? array.slice (-limit) : array.slice (0, limit) } return array } checkRequiredDependencies () { return } remove0xPrefix (hexData) { if (hexData.slice (0, 2) === '0x') { return hexData.slice (2) } else { return hexData } } hashMessage (message) { // takes a hex encoded message const binaryMessage = this.base16ToBinary (this.remove0xPrefix (message)) const prefix = this.stringToBinary ('\x19Ethereum Signed Message:\n' + binaryMessage.sigBytes) return '0x' + this.hash (this.binaryConcat (prefix, binaryMessage), 'keccak', 'hex') } signHash (hash, privateKey) { const signature = this.ecdsa (hash.slice (-64), privateKey.slice (-64), 'secp256k1', undefined) return { 'r': '0x' + signature['r'], 's': '0x' + signature['s'], 'v': 27 + signature['v'], } } signMessage (message, privateKey) { return this.signHash (this.hashMessage (message), privateKey.slice (-64)) } signMessageString (message, privateKey) { // still takes the input as a hex string // same as above but returns a string instead of an object const signature = this.signMessage (message, privateKey) return signature['r'] + this.remove0xPrefix (signature['s']) + this.binaryToBase16 (this.numberToBE (signature['v'])) } parseNumber (value, d = undefined) { if (value === undefined) { return d } else { try { return this.number (value) } catch (e) { return d } } } checkOrderArguments (market, type, side, amount, price, params) { if (price === undefined) { if (type === 'limit') { throw new ArgumentsRequired (this.id + ' createOrder() requires a price argument for a limit order'); } } if (amount <= 0) { throw new ArgumentsRequired (this.id + ' createOrder() amount should be above 0'); } } handleHttpStatusCode (code, reason, url, method, body) { const codeAsString = code.toString (); if (codeAsString in this.httpExceptions) { const ErrorClass = this.httpExceptions[codeAsString]; throw new ErrorClass (this.id + ' ' + method + ' ' + url + ' ' + codeAsString + ' ' + reason + ' ' + body); } } /* eslint-enable */ // ------------------------------------------------------------------------ // ######################################################################## // ######################################################################## // ######################################################################## // ######################################################################## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######## ######################## ######################## // ######## ######################## ######################## // ######## ######################## ######################## // ######## ######################## ######################## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######################################################################## // ######################################################################## // ######################################################################## // ######################################################################## // ######## ######## ######## ######## // ######## ######## ######## ######## // ######## ######## ######## ######## // ######## ######## ######## ######## // ################ ######################## ################ // ################ ######################## ################ // ################ ######################## ################ // ################ ######################## ################ // ######## ######## ################ ################ // ######## ######## ################ ################ // ######## ######## ################ ################ // ######## ######## ################ ################ // ######################################################################## // ######################################################################## // ######################################################################## // ######################################################################## // ------------------------------------------------------------------------ // METHODS BELOW THIS LINE ARE TRANSPILED FROM JAVASCRIPT TO PYTHON AND PHP getDefaultOptions () { return { 'defaultNetworkCodeReplacements': { 'ETH': { 'ERC20': 'ETH' }, 'TRX': { 'TRC20': 'TRX' }, 'CRO': { 'CRC20': 'CRONOS' }, }, }; } safeLedgerEntry (entry, currency = undefined) { currency = this.safeCurrency (undefined, currency); let direction = this.safeString (entry, 'direction'); let before = this.safeString (entry, 'before'); let after = this.safeString (entry, 'after'); const amount = this.safeString (entry, 'amount'); if (amount !== undefined) { if (before === undefined && after !== undefined) { before = Precise.stringSub (after, amount); } else if (before !== undefined && after === undefined) { after = Precise.stringAdd (before, amount); } } if (before !== undefined && after !== undefined) { if (direction === undefined) { if (Precise.stringGt (before, after)) { direction = 'out'; } if (Precise.stringGt (after, before)) { direction = 'in'; } } } const fee = this.safeValue (entry, 'fee'); if (fee !== undefined) { fee['cost'] = this.safeNumber (fee, 'cost'); } const timestamp = this.safeInteger (entry, 'timestamp'); return { 'id': this.safeString (entry, 'id'), 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'direction': direction, 'account': this.safeString (entry, 'account'), 'referenceId': this.safeString (entry, 'referenceId'), 'referenceAccount': this.safeString (entry, 'referenceAccount'), 'type': this.safeString (entry, 'type'), 'currency': currency['code'], 'amount': this.parseNumber (amount), 'before': this.parseNumber (before), 'after': this.parseNumber (after), 'status': this.safeString (entry, 'status'), 'fee': fee, 'info': entry, }; } setMarkets (markets, currencies = undefined) { const values = []; const marketValues = this.toArray (markets); for (let i = 0; i < marketValues.length; i++) { const market = this.deepExtend (this.safeMarket (), { 'precision': this.precision, 'limits': this.limits, }, this.fees['trading'], marketValues[i]); values.push (market); } this.markets = this.indexBy (values, 'symbol'); this.markets_by_id = this.indexBy (markets, 'id'); const marketsSortedBySymbol = this.keysort (this.markets); const marketsSortedById = this.keysort (this.markets_by_id); this.symbols = Object.keys (marketsSortedBySymbol); this.ids = Object.keys (marketsSortedById); if (currencies !== undefined) { this.currencies = this.deepExtend (this.currencies, currencies); } else { let baseCurrencies = []; let quoteCurrencies = []; for (let i = 0; i < values.length; i++) { const market = values[i]; const defaultCurrencyPrecision = (this.precisionMode === DECIMAL_PLACES) ? 8 : this.parseNumber ('1e-8'); const marketPrecision = this.safeValue (market, 'precision', {}); if ('base' in market) { const currencyPrecision = this.safeValue2 (marketPrecision, 'base', 'amount', defaultCurrencyPrecision); const currency = { 'id': this.safeString2 (market, 'baseId', 'base'), 'numericId': this.safeString (market, 'baseNumericId'), 'code': this.safeString (market, 'base'), 'precision': currencyPrecision, }; baseCurrencies.push (currency); } if ('quote' in market) { const currencyPrecision = this.safeValue2 (marketPrecision, 'quote', 'price', defaultCurrencyPrecision); const currency = { 'id': this.safeString2 (market, 'quoteId', 'quote'), 'numericId': this.safeString (market, 'quoteNumericId'), 'code': this.safeString (market, 'quote'), 'precision': currencyPrecision, }; quoteCurrencies.push (currency); } } baseCurrencies = this.sortBy (baseCurrencies, 'code'); quoteCurrencies = this.sortBy (quoteCurrencies, 'code'); this.baseCurrencies = this.indexBy (baseCurrencies, 'code'); this.quoteCurrencies = this.indexBy (quoteCurrencies, 'code'); const allCurrencies = this.arrayConcat (baseCurrencies, quoteCurrencies); const groupedCurrencies = this.groupBy (allCurrencies, 'code'); const codes = Object.keys (groupedCurrencies); const resultingCurrencies = []; for (let i = 0; i < codes.length; i++) { const code = codes[i]; const groupedCurrenciesCode = this.safeValue (groupedCurrencies, code, []); let highestPrecisionCurrency = this.safeValue (groupedCurrenciesCode, 0); for (let j = 1; j < groupedCurrenciesCode.length; j++) { const currentCurrency = groupedCurrenciesCode[j]; if (this.precisionMode === TICK_SIZE) { highestPrecisionCurrency = (currentCurrency['precision'] < highestPrecisionCurrency['precision']) ? currentCurrency : highestPrecisionCurrency; } else { highestPrecisionCurrency = (currentCurrency['precision'] > highestPrecisionCurrency['precision']) ? currentCurrency : highestPrecisionCurrency; } } resultingCurrencies.push (highestPrecisionCurrency); } const sortedCurrencies = this.sortBy (resultingCurrencies, 'code'); this.currencies = this.deepExtend (this.currencies, this.indexBy (sortedCurrencies, 'code')); } this.currencies_by_id = this.indexBy (this.currencies, 'id'); const currenciesSortedByCode = this.keysort (this.currencies); this.codes = Object.keys (currenciesSortedByCode); return this.markets; } safeBalance (balance) { const balances = this.omit (balance, [ 'info', 'timestamp', 'datetime', 'free', 'used', 'total' ]); const codes = Object.keys (balances); balance['free'] = {}; balance['used'] = {}; balance['total'] = {}; const debtBalance = {}; for (let i = 0; i < codes.length; i++) { const code = codes[i]; let total = this.safeString (balance[code], 'total'); let free = this.safeString (balance[code], 'free'); let used = this.safeString (balance[code], 'used'); const debt = this.safeString (balance[code], 'debt'); if ((total === undefined) && (free !== undefined) && (used !== undefined)) { total = Precise.stringAdd (free, used); } if ((free === undefined) && (total !== undefined) && (used !== undefined)) { free = Precise.stringSub (total, used); } if ((used === undefined) && (total !== undefined) && (free !== undefined)) { used = Precise.stringSub (total, free); } balance[code]['free'] = this.parseNumber (free); balance[code]['used'] = this.parseNumber (used); balance[code]['total'] = this.parseNumber (total); balance['free'][code] = balance[code]['free']; balance['used'][code] = balance[code]['used']; balance['total'][code] = balance[code]['total']; if (debt !== undefined) { balance[code]['debt'] = this.parseNumber (debt); debtBalance[code] = balance[code]['debt']; } } const debtBalanceArray = Object.keys (debtBalance); const length = debtBalanceArray.length; if (length) { balance['debt'] = debtBalance; } return balance; } safeOrder (order, market = undefined) { // parses numbers as strings // it is important pass the trades as unparsed rawTrades let amount = this.omitZero (this.safeString (order, 'amount')); let remaining = this.safeString (order, 'remaining'); let filled = this.safeString (order, 'filled'); let cost = this.safeString (order, 'cost'); let average = this.omitZero (this.safeString (order, 'average')); let price = this.omitZero (this.safeString (order, 'price')); let lastTradeTimeTimestamp = this.safeInteger (order, 'lastTradeTimestamp'); const parseFilled = (filled === undefined); const parseCost = (cost === undefined); const parseLastTradeTimeTimestamp = (lastTradeTimeTimestamp === undefined); const fee = this.safeValue (order, 'fee'); const parseFee = (fee === undefined); const parseFees = this.safeValue (order, 'fees') === undefined; const shouldParseFees = parseFee || parseFees; const fees = this.safeValue (order, 'fees', []); let trades = []; if (parseFilled || parseCost || shouldParseFees) { const rawTrades = this.safeValue (order, 'trades', trades); const oldNumber = this.number; // we parse trades as strings here! this.number = String; trades = this.parseTrades (rawTrades, market, undefined, undefined, { 'symbol': order['symbol'], 'side': order['side'], 'type': order['type'], 'order': order['id'], }); this.number = oldNumber; let tradesLength = 0; const isArray = Array.isArray (trades); if (isArray) { tradesLength = trades.length; } if (isArray && (tradesLength > 0)) { // move properties that are defined in trades up into the order if (order['symbol'] === undefined) { order['symbol'] = trades[0]['symbol']; } if (order['side'] === undefined) { order['side'] = trades[0]['side']; } if (order['type'] === undefined) { order['type'] = trades[0]['type']; } if (order['id'] === undefined) { order['id'] = trades[0]['order']; } if (parseFilled) { filled = '0'; } if (parseCost) { cost = '0'; } for (let i = 0; i < trades.length; i++) { const trade = trades[i]; const tradeAmount = this.safeString (trade, 'amount'); if (parseFilled && (tradeAmount !== undefined)) { filled = Precise.stringAdd (filled, tradeAmount); } const tradeCost = this.safeString (trade, 'cost'); if (parseCost && (tradeCost !== undefined)) { cost = Precise.stringAdd (cost, tradeCost); } const tradeTimestamp = this.safeValue (trade, 'timestamp'); if (parseLastTradeTimeTimestamp && (tradeTimestamp !== undefined)) { if (lastTradeTimeTimestamp === undefined) { lastTradeTimeTimestamp = tradeTimestamp; } else { lastTradeTimeTimestamp = Math.max (lastTradeTimeTimestamp, tradeTimestamp); } } if (shouldParseFees) { const tradeFees = this.safeValue (trade, 'fees'); if (tradeFees !== undefined) { for (let j = 0; j < tradeFees.length; j++) { const tradeFee = tradeFees[j]; fees.push (this.extend ({}, tradeFee)); } } else { const tradeFee = this.safeValue (trade, 'fee'); if (tradeFee !== undefined) { fees.push (this.extend ({}, tradeFee)); } } } } } } if (shouldParseFees) { const reducedFees = this.reduceFees ? this.reduceFeesByCurrency (fees) : fees; const reducedLength = reducedFees.length; for (let i = 0; i < reducedLength; i++) { reducedFees[i]['cost'] = this.safeNumber (reducedFees[i], 'cost'); if ('rate' in reducedFees[i]) { reducedFees[i]['rate'] = this.safeNumber (reducedFees[i], 'rate'); } } if (!parseFee && (reducedLength === 0)) { fee['cost'] = this.safeNumber (fee, 'cost'); if ('rate' in fee) { fee['rate'] = this.safeNumber (fee, 'rate'); } reducedFees.push (fee); } order['fees'] = reducedFees; if (parseFee && (reducedLength === 1)) { order['fee'] = reducedFees[0]; } } if (amount === undefined) { // ensure amount = filled + remaining if (filled !== undefined && remaining !== undefined) { amount = Precise.stringAdd (filled, remaining); } else if (this.safeString (order, 'status') === 'closed') { amount = filled;