UNPKG

@proton/ccxt

Version:

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

1,103 lines (1,099 loc) 180 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var functions = require('./functions.js'); var errors = require('./errors.js'); var Precise = require('./Precise.js'); var WsClient = require('./ws/WsClient.js'); var Future = require('./ws/Future.js'); var OrderBook = require('./ws/OrderBook.js'); var totp = require('./functions/totp.js'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return Object.freeze(n); } // ---------------------------------------------------------------------------- const { isNode, keys, values, deepExtend, extend, clone, flatten, unique, indexBy, sortBy, sortBy2, safeFloat2, groupBy, aggregate, uuid, unCamelCase, precisionFromString, Throttler, capitalize, now, decimalToPrecision, safeValue, safeValue2, safeString, safeString2, seconds, milliseconds, binaryToBase16, numberToBE, base16ToBinary, iso8601, omit, isJsonEncodedObject, safeInteger, sum, omitZero, implodeParams, extractParams, json, vwap, merge, binaryConcat, hash, ecdsa, arrayConcat, encode, urlencode, hmac, numberToString, parseTimeframe, safeInteger2, safeStringLower, parse8601, yyyymmdd, safeStringUpper, safeTimestamp, binaryConcatArray, uuidv1, numberToLE, ymdhms, stringToBase64, decode, uuid22, safeIntegerProduct2, safeIntegerProduct, safeStringLower2, yymmdd, base58ToBinary, safeTimestamp2, rawencode, keysort, inArray, isEmpty, ordered, filterBy, uuid16, safeFloat, base64ToBinary, safeStringUpper2, urlencodeWithArrayRepeat, microseconds, binaryToBase64, binaryToBase58, strip, toArray, safeFloatN, safeIntegerN, safeIntegerProductN, safeTimestampN, safeValueN, safeStringN, safeStringLowerN, safeStringUpperN, urlencodeNested, parseDate, ymd, isArray, base64ToString, crc32, TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING, TICK_SIZE, SIGNIFICANT_DIGITS } = functions; // ---------------------------------------------------------------------------- class Exchange { constructor(userConfig = {}) { this.api = undefined; this.userAgent = undefined; this.user_agent = undefined; // 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 = {}; this.origin = '*'; // CORS origin // this.agent = undefined; // maintained for backwards compatibility 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; this.validateServerSsl = true; this.validateClientSsl = false; this.timeout = 10000; // milliseconds this.verbose = false; this.twofa = undefined; // two-factor authentication (2FA) this.balance = {}; this.orderbooks = {}; this.tickers = {}; this.orders = undefined; this.transactions = {}; this.positions = {}; this.requiresWeb3 = false; this.requiresEddsa = false; this.enableLastJsonResponse = true; this.enableLastHttpResponse = true; this.enableLastResponseHeaders = true; this.last_http_response = undefined; this.last_json_response = undefined; this.last_response_headers = undefined; this.id = undefined; this.markets = undefined; this.status = undefined; this.rateLimit = undefined; // milliseconds this.tokenBucket = undefined; this.throttler = undefined; this.enableRateLimit = undefined; this.httpExceptions = undefined; this.markets_by_id = undefined; this.symbols = undefined; this.ids = undefined; this.currencies = undefined; this.baseCurrencies = undefined; this.quoteCurrencies = undefined; this.currencies_by_id = undefined; this.codes = undefined; this.reloadingMarkets = undefined; this.marketsLoading = undefined; this.accounts = undefined; this.accountsById = undefined; this.commonCurrencies = undefined; this.hostname = undefined; this.precisionMode = undefined; this.paddingMode = undefined; this.exceptions = {}; this.timeframes = {}; this.version = undefined; this.marketsByAltname = undefined; this.name = undefined; this.targetAccount = undefined; this.stablePairs = {}; // WS/PRO options this.clients = {}; this.newUpdates = true; this.streaming = {}; this.deepExtend = deepExtend; this.isNode = isNode; this.keys = keys; this.values = values; this.extend = extend; this.clone = clone; this.flatten = flatten; this.unique = unique; this.indexBy = indexBy; this.sortBy = sortBy; this.sortBy2 = sortBy2; this.groupBy = groupBy; this.aggregate = aggregate; this.uuid = uuid; this.unCamelCase = unCamelCase; this.precisionFromString = precisionFromString; this.capitalize = capitalize; this.now = now; this.decimalToPrecision = decimalToPrecision; this.safeValue = safeValue; this.safeValue2 = safeValue2; this.safeString = safeString; this.safeString2 = safeString2; this.safeFloat = safeFloat; this.safeFloat2 = safeFloat2; this.seconds = seconds; this.milliseconds = milliseconds; this.binaryToBase16 = binaryToBase16; this.numberToBE = numberToBE; this.base16ToBinary = base16ToBinary; this.iso8601 = iso8601; this.omit = omit; this.isJsonEncodedObject = isJsonEncodedObject; this.safeInteger = safeInteger; this.sum = sum; this.omitZero = omitZero; this.implodeParams = implodeParams; this.extractParams = extractParams; this.json = json; this.vwap = vwap; this.merge = merge; this.binaryConcat = binaryConcat; this.hash = hash; this.arrayConcat = arrayConcat; this.encode = encode; this.urlencode = urlencode; this.hmac = hmac; this.numberToString = numberToString; this.parseTimeframe = parseTimeframe; this.safeInteger2 = safeInteger2; this.safeStringLower = safeStringLower; this.parse8601 = parse8601; this.yyyymmdd = yyyymmdd; this.safeStringUpper = safeStringUpper; this.safeTimestamp = safeTimestamp; this.binaryConcatArray = binaryConcatArray; this.uuidv1 = uuidv1; this.numberToLE = numberToLE; this.ymdhms = ymdhms; this.yymmdd = yymmdd; this.stringToBase64 = stringToBase64; this.decode = decode; this.uuid22 = uuid22; this.safeIntegerProduct2 = safeIntegerProduct2; this.safeIntegerProduct = safeIntegerProduct; this.base58ToBinary = base58ToBinary; this.base64ToBinary = base64ToBinary; this.safeTimestamp2 = safeTimestamp2; this.rawencode = rawencode; this.keysort = keysort; this.inArray = inArray; this.safeStringLower2 = safeStringLower2; this.safeStringUpper2 = safeStringUpper2; this.isEmpty = isEmpty; this.ordered = ordered; this.filterBy = filterBy; this.uuid16 = uuid16; this.urlencodeWithArrayRepeat = urlencodeWithArrayRepeat; this.microseconds = microseconds; this.binaryToBase64 = binaryToBase64; this.binaryToBase58 = binaryToBase58; this.strip = strip; this.toArray = toArray; this.safeFloatN = safeFloatN; this.safeIntegerN = safeIntegerN; this.safeIntegerProductN = safeIntegerProductN; this.safeTimestampN = safeTimestampN; this.safeValueN = safeValueN; this.safeStringN = safeStringN; this.safeStringLowerN = safeStringLowerN; this.safeStringUpperN = safeStringUpperN; this.urlencodeNested = urlencodeNested; this.parseDate = parseDate; this.ymd = ymd; this.isArray = isArray; this.base64ToString = base64ToString; this.crc32 = crc32; Object.assign(this, functions); // // if (isNode) { // this.nodeVersion = process.version.match (/\d+\.\d+\.\d+/)[0] // this.userAgent = { // 'User-Agent': 'ccxt/' + (Exchange as any).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) // http properties this.headers = {}; 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 = undefined; this.validateServerSsl = true; this.validateClientSsl = false; // default property values this.timeout = 10000; // milliseconds this.verbose = false; 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; // 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; } } // ssl options if (!this.validateServerSsl) ; // 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); } this.newUpdates = (this.options.newUpdates !== undefined) ? this.options.newUpdates : true; } describe() { return { 'id': undefined, 'name': undefined, 'countries': undefined, 'enableRateLimit': true, 'rateLimit': 2000, 'certified': false, 'pro': false, 'alias': false, '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, 'createOrderWs': undefined, 'editOrderWs': undefined, 'cancelOrderWs': undefined, 'cancelOrdersWs': undefined, 'cancelAllOrdersWs': 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, 'fetchDepositsWithdrawals': undefined, 'fetchTransactionFee': undefined, 'fetchTransactionFees': undefined, 'fetchFundingHistory': undefined, 'fetchFundingRate': undefined, 'fetchFundingRateHistory': undefined, 'fetchFundingRates': undefined, 'fetchIndexOHLCV': undefined, 'fetchL2OrderBook': true, 'fetchLastPrices': undefined, 'fetchLedger': undefined, 'fetchLedgerEntry': undefined, 'fetchLeverageTiers': undefined, 'fetchMarketLeverageTiers': undefined, 'fetchMarkets': true, 'fetchMarkOHLCV': undefined, 'fetchMyTrades': undefined, 'fetchOHLCV': undefined, '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, 'privateKey': false, 'walletAddress': false, 'token': false, // reserved for HTTP auth in some cases }, 'markets': undefined, 'currencies': {}, 'timeframes': undefined, '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': errors.ExchangeError, '418': errors.DDoSProtection, '429': errors.RateLimitExceeded, '404': errors.ExchangeNotAvailable, '409': errors.ExchangeNotAvailable, '410': errors.ExchangeNotAvailable, '451': errors.ExchangeNotAvailable, '500': errors.ExchangeNotAvailable, '501': errors.ExchangeNotAvailable, '502': errors.ExchangeNotAvailable, '520': errors.ExchangeNotAvailable, '521': errors.ExchangeNotAvailable, '522': errors.ExchangeNotAvailable, '525': errors.ExchangeNotAvailable, '526': errors.ExchangeNotAvailable, '400': errors.ExchangeNotAvailable, '403': errors.ExchangeNotAvailable, '405': errors.ExchangeNotAvailable, '503': errors.ExchangeNotAvailable, '530': errors.ExchangeNotAvailable, '408': errors.RequestTimeout, '504': errors.RequestTimeout, '401': errors.AuthenticationError, '407': errors.AuthenticationError, '511': errors.AuthenticationError, }, 'commonCurrencies': { '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 () encodeURIComponent(...args) { // @ts-expect-error return encodeURIComponent(...args); } checkRequiredVersion(requiredVersion, error = true) { let result = true; const [major1, minor1, patch1] = requiredVersion.split('.'), [major2, minor2, patch2] = Exchange.ccxtVersion.split('.'), intMajor1 = this.parseToInt(major1), intMinor1 = this.parseToInt(minor1), intPatch1 = this.parseToInt(patch1), intMajor2 = this.parseToInt(major2), intMinor2 = this.parseToInt(minor2), intPatch2 = this.parseToInt(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 errors.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 errors.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 errors.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.throttler = new Throttler(this.tokenBucket); } throttle(cost = undefined) { return this.throttler.throttle(cost); } 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 errors.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); } async fetch(url, method = 'GET', headers = undefined, body = undefined) { // ##### PROXY & HEADERS ##### headers = this.extend(this.headers, headers); const [proxyUrl, httpProxy, httpsProxy, socksProxy] = this.checkProxySettings(url, method, headers, body); if (proxyUrl !== undefined) { // in node we need to set header to * if (isNode) { headers = this.extend({ 'Origin': this.origin }, headers); } url = proxyUrl + url; } else if (httpProxy !== undefined) { const module = await Promise.resolve().then(function () { return require(/* webpackIgnore: true */ '../static_dependencies/proxies/http-proxy-agent/index.js'); }); const proxyAgent = new module.HttpProxyAgent(httpProxy); this.agent = proxyAgent; } else if (httpsProxy !== undefined) { const module = await Promise.resolve().then(function () { return require(/* webpackIgnore: true */ '../static_dependencies/proxies/https-proxy-agent/index.js'); }); const proxyAgent = new module.HttpsProxyAgent(httpsProxy); proxyAgent.keepAlive = true; this.agent = proxyAgent; } else if (socksProxy !== undefined) { let module = undefined; try { // @ts-ignore module = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(/* webpackIgnore: true */ 'socks-proxy-agent')); }); } catch (e) { throw new errors.NotSupported(this.id + ' - to use SOCKS proxy with ccxt, at first you need install module "npm i socks-proxy-agent" '); } this.agent = new module.SocksProxyAgent(socksProxy); } const userAgent = (this.userAgent !== undefined) ? this.userAgent : this.user_agent; if (userAgent && isNode) { if (typeof userAgent === 'string') { headers = this.extend({ 'User-Agent': userAgent }, headers); } else if ((typeof userAgent === 'object') && ('User-Agent' in userAgent)) { headers = this.extend(userAgent, headers); } } headers = this.setHeaders(headers); // ######## end of proxies ######## if (this.verbose) { this.log("fetch Request:\n", this.id, method, url, "\nRequestHeaders:\n", headers, "\nRequestBody:\n", body, "\n"); } if (this.fetchImplementation === undefined) { if (isNode) { const module = await Promise.resolve().then(function () { return require(/* webpackIgnore: true */ '../static_dependencies/node-fetch/index.js'); }); if (this.agent === undefined) { const { Agent } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(/* webpackIgnore: true */ 'node:https')); }); this.agent = new Agent({ keepAlive: true }); } this.AbortError = module.AbortError; this.fetchImplementation = module.default; this.FetchError = module.FetchError; } else { this.fetchImplementation = self.fetch; this.AbortError = DOMException; this.FetchError = TypeError; } } // 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; } const controller = new AbortController(); params['signal'] = controller.signal; const timeout = setTimeout(() => { controller.abort(); }, this.timeout); try { const response = await fetchImplementation(url, params); clearTimeout(timeout); return this.handleRestResponse(response, url, method, headers, body); } catch (e) { if (e instanceof this.AbortError) { throw new errors.RequestTimeout(this.id + ' ' + method + ' ' + url + ' request timed out (' + this.timeout + ' ms)'); } else if (e instanceof this.FetchError) { throw new errors.NetworkError(this.id + ' ' + method + ' ' + url + ' fetch failed'); } throw e; } } 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))); } checkRequiredDependencies() { return; } 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 errors.ArgumentsRequired(this.id + ' createOrder() requires a price argument for a limit order'); } } if (amount <= 0) { throw new errors.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); } } remove0xPrefix(hexData) { if (hexData.slice(0, 2) === '0x') { return hexData.slice(2); } else { return hexData; } } // method to override findTimeframe(timeframe, timeframes = undefined) { timeframes = timeframes || this.timeframes; const keys = Object.keys(timeframes); for (let i = 0; i < keys.length; i++) { const key = keys[i]; if (timeframes[key] === timeframe) { return key; } } return undefined; } spawn(method, ...args) { const future = Future.createFuture(); method.apply(this, args).then(future.resolve).catch(future.reject); return future; } delay(timeout, method, ...args) { setTimeout(() => { this.spawn(method, ...args); }, timeout); } // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // WS/PRO methods orderBook(snapshot = {}, depth = Number.MAX_SAFE_INTEGER) { return new OrderBook.OrderBook(snapshot, depth); } indexedOrderBook(snapshot = {}, depth = Number.MAX_SAFE_INTEGER) { return new OrderBook.IndexedOrderBook(snapshot, depth); } countedOrderBook(snapshot = {}, depth = Number.MAX_SAFE_INTEGER) { return new OrderBook.CountedOrderBook(snapshot, depth); } handleMessage(client, message) { } // stub to override // ping (client) {} // stub to override client(url) { this.clients = this.clients || {}; if (!this.clients[url]) { const onMessage = this.handleMessage.bind(this); const onError = this.onError.bind(this); const onClose = this.onClose.bind(this); const onConnected = this.onConnected.bind(this); // decide client type here: ws / signalr / socketio const wsOptions = this.safeValue(this.options, 'ws', {}); const options = this.deepExtend(this.streaming, { 'log': this.log ? this.log.bind(this) : this.log, 'ping': this.ping ? this.ping.bind(this) : this.ping, 'verbose': this.verbose, 'throttler': new Throttler(this.tokenBucket), // add support for proxies 'options': { 'agent': this.agent, } }, wsOptions); this.clients[url] = new WsClient(url, onMessage, onError, onClose, onConnected, options); } return this.clients[url]; } watch(url, messageHash, message = undefined, subscribeHash = undefined, subscription = undefined) { // // Without comments the code of this method is short and easy: // // const client = this.client (url) // const backoffDelay = 0 // const future = client.future (messageHash) // const connected = client.connect (backoffDelay) // connected.then (() => { // if (message && !client.subscriptions[subscribeHash]) { // client.subscriptions[subscribeHash] = true // client.send (message) // } // }).catch ((error) => {}) // return future // // The following is a longer version of this method with comments // const client = this.client(url); // todo: calculate the backoff using the clients cache const backoffDelay = 0; // // watchOrderBook ---- future ----+---------------+----→ user // | | // ↓ ↑ // | | // connect ......→ resolve // | | // ↓ ↑ // | | // subscribe -----→ receive // if ((subscribeHash === undefined) && (messageHash in client.futures)) { return client.futures[messageHash]; } const future = client.future(messageHash); // read and write subscription, this is done before connecting the client // to avoid race conditions when other parts of the code read or write to the client.subscriptions const clientSubscription = client.subscriptions[subscribeHash]; if (!clientSubscription) { client.subscriptions[subscribeHash] = subscription || true; } // we intentionally do not use await here to avoid unhandled exceptions // the policy is to make sure that 100% of promises are resolved or rejected // either with a call to client.resolve or client.reject with // a proper exception class instance const connected = client.connect(backoffDelay); // the following is executed only if the catch-clause does not // catch any connection-level exceptions from the client // (connection established successfully) if (!clientSubscription) { connected.then(() => { const options = this.safeValue(this.options, 'ws'); const cost = this.safeValue(options, 'cost', 1); if (message) { if (this.enableRateLimit && client.throttle) { // add cost here | // | // V client.throttle(cost).then(() => { client.send(message); }).catch((e) => { throw e; }); } else { client.send(message) .catch((e) => { throw e; }); } } }).catch((e) => { delete (client.subscriptions[subscribeHash]); throw e; }); } return future; } onConnected(client, message = undefined) { // for user hooks // console.log ('Connected to', client.url) } onError(client, error) { if ((client.url in this.clients) && (this.clients[client.url].error)) { delete this.clients[client.url]; } } onClose(client, error) { if (client.error) ; else { // server disconnected a working connection if (this.clients[client.url]) { delete this.clients[client.url]; } } } async close() { const clients = Object.values(this.clients || {}); for (let i = 0; i < clients.length; i++) { const client = clients[i]; delete this.clients[client.url]; await client.close(); } } handleDelta(bookside, delta, nonce = undefined) { //stub } async loadOrderBook(client, messageHash, symbol, limit = undefined, params = {}) { if (!(symbol in this.orderbooks)) { client.reject(new errors.ExchangeError(this.id + ' loadOrderBook() orderbook is not initiated'), messageHash); return; } const maxRetries = this.handleOption('watchOrderBook', 'maxRetries', 3); let tries = 0; try { const stored = this.orderbooks[symbol]; while (tries < maxRetries) { const cache = stored.cache; const orderBook = await this.fetchOrderBook(symbol, limit, params); const index = this.getCacheIndex(orderBook, cache); if (index >= 0) { stored.reset(orderBook); this.handleDeltas(stored, cache.slice(index)); stored.cache.length = 0; client.resolve(stored, messageHash); return; } tries++; } client.reject(new errors.ExchangeError(this.id + ' nonce is behind the cache after ' + maxRetries.toString() + ' tries.'), messageHash); delete this.clients[client.url]; } catch (e) { client.reject(e, messageHash); await this.loadOrderBook(client, messageHash, symbol, limit, params); } } handleDeltas(orderbook, deltas, nonce = undefined) { for (let i = 0; i < deltas.length; i++) { this.handleDelta(orderbook, deltas[i]); } } // eslint-disable-next-line no-unused-vars getCacheIndex(orderbook, deltas) { // return the first index of the cache that can be applied to the orderbook or -1 if not possible return -1; } convertToBigInt(value) { return BigInt(value); // used on XT } valueIsDefined(value) { return value !== undefined && value !== null; } arraySlice(array, first, second = undefined) { if (second === undefined) { return array.slice(first); } return array.slice(first, second); } getProperty(obj, property, defaultValue = undefined) { return (property in obj ? obj[property] : defaultValue); } /* eslint-enable */ // ------------------------------------------------------------------------ // ######################################################################## // ######################################################################## // ######################################################################## // ######################################################################## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######## ######################## ######################## // ######## ######################## ######################## // ######## ######################## ######################## // ######## ######################## ######################## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######## ######## ######## // ######################################################################## // ######################################################################## // ######################################################################## // ######################################################################## // ######## ######## ######## ######## // ######## ######## ######## ######## // ######## ######## ######## ######## // ######## ######## ######## ######## // ################ ######################## ################ // ################ ######################## ################ // ################ ######################## ################ // ################ ######################## ################ // ######## ######## ################ #