UNPKG

@jalmonter/ccxt

Version:

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

1,094 lines (1,090 loc) 227 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 crypto = require('./functions/crypto.js'); var totp = require('./functions/totp.js'); var generic = require('./functions/generic.js'); var misc = require('./functions/misc.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 { 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, 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, binaryToBase58, safeTimestamp2, rawencode, keysort, inArray, isEmpty, ordered, filterBy, uuid16, safeFloat, base64ToBinary, safeStringUpper2, urlencodeWithArrayRepeat, microseconds, binaryToBase64, strip, toArray, safeFloatN, safeIntegerN, safeIntegerProductN, safeTimestampN, safeValueN, safeStringN, safeStringLowerN, safeStringUpperN, urlencodeNested, parseDate, ymd, base64ToString, crc32, TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING, TICK_SIZE, SIGNIFICANT_DIGITS } = functions; // ---------------------------------------------------------------------------- /** * @class Exchange */ 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.nodeHttpModuleLoaded = false; this.httpAgent = undefined; this.httpsAgent = undefined; 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.triggerOrders = undefined; this.transactions = {}; 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.last_request_headers = undefined; this.last_request_body = undefined; this.last_request_url = undefined; this.last_request_path = 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.keys = generic.keys; this.values = generic.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 = misc.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.binaryToBase58 = binaryToBase58; 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.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.base64ToString = base64ToString; this.crc32 = crc32; this.httpProxyAgentModule = undefined; this.httpsProxyAgentModule = undefined; this.socksProxyAgentModule = undefined; this.socksProxyAgentModuleChecked = false; this.proxyDictionaries = {}; this.proxyModulesLoaded = false; 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 = undefined; // 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; this.last_request_headers = undefined; this.last_request_body = undefined; this.last_request_url = undefined; this.last_request_path = 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; this.afterConstruct(); } 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, 'closeAllPositions': undefined, 'closePosition': undefined, 'createDepositAddress': undefined, 'createLimitOrder': true, 'createMarketOrder': true, 'createOrder': true, 'createMarketBuyOrderWithCost': undefined, 'createMarketOrderWithCost': undefined, 'createMarketSellOrderWithCost': undefined, 'createOrders': undefined, 'createPostOnlyOrder': undefined, 'createReduceOnlyOrder': undefined, 'createStopOrder': undefined, 'createStopLimitOrder': undefined, 'createStopMarketOrder': undefined, 'createOrderWs': undefined, 'editOrderWs': undefined, 'fetchOpenOrdersWs': undefined, 'fetchOrderWs': undefined, 'cancelOrderWs': undefined, 'cancelOrdersWs': undefined, 'cancelAllOrdersWs': undefined, 'fetchTradesWs': undefined, 'fetchBalanceWs': undefined, 'editOrder': 'emulated', 'fetchAccounts': undefined, 'fetchBalance': true, 'fetchBidsAsks': undefined, 'fetchBorrowInterest': undefined, 'fetchBorrowRateHistory': undefined, 'fetchCanceledOrders': undefined, 'fetchClosedOrder': undefined, 'fetchClosedOrders': undefined, 'fetchCrossBorrowRate': undefined, 'fetchCrossBorrowRates': 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, 'fetchIsolatedBorrowRate': undefined, 'fetchIsolatedBorrowRates': 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, 'fetchPositionsForSymbol': 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, 'watchOrderBook': undefined, 'watchOrders': undefined, 'watchMyTrades': undefined, 'watchTickers': undefined, 'watchTicker': undefined, 'watchTrades': undefined, 'watchTradesForSymbols': undefined, 'watchOrderBookForSymbols': undefined, 'watchOHLCVForSymbols': undefined, 'watchBalance': undefined, 'watchOHLCV': 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', '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 loadProxyModules() { this.proxyModulesLoaded = true; // todo: possible sync alternatives: https://stackoverflow.com/questions/51069002/convert-import-to-synchronous this.httpProxyAgentModule = await Promise.resolve().then(function () { return require(/* webpackIgnore: true */ '../static_dependencies/proxies/http-proxy-agent/index.js'); }); this.httpsProxyAgentModule = await Promise.resolve().then(function () { return require(/* webpackIgnore: true */ '../static_dependencies/proxies/https-proxy-agent/index.js'); }); if (this.socksProxyAgentModuleChecked === false) { this.socksProxyAgentModuleChecked = true; try { // @ts-ignore this.socksProxyAgentModule = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require(/* webpackIgnore: true */ 'socks-proxy-agent')); }); } catch (e) { } } } setProxyAgents(httpProxy, httpsProxy, socksProxy) { let chosenAgent = undefined; if (httpProxy) { if (this.httpProxyAgentModule === undefined) { throw new errors.NotSupported(this.id + ' you need to load JS proxy modules with `.loadProxyModules()` method at first to use proxies'); } if (!(httpProxy in this.proxyDictionaries)) { this.proxyDictionaries[httpProxy] = new this.httpProxyAgentModule.HttpProxyAgent(httpProxy); } chosenAgent = this.proxyDictionaries[httpProxy]; } else if (httpsProxy) { if (this.httpsProxyAgentModule === undefined) { throw new errors.NotSupported(this.id + ' you need to load JS proxy modules with `.loadProxyModules()` method at first to use proxies'); } if (!(httpsProxy in this.proxyDictionaries)) { this.proxyDictionaries[httpsProxy] = new this.httpsProxyAgentModule.HttpsProxyAgent(httpsProxy); } chosenAgent = this.proxyDictionaries[httpsProxy]; chosenAgent.keepAlive = true; } else if (socksProxy) { if (this.socksProxyAgentModule === undefined) { throw new errors.NotSupported(this.id + ' - to use SOCKS proxy with ccxt, at first you need install module "npm i socks-proxy-agent" and then initialize proxies with `.loadProxyModules()` method'); } if (!(socksProxy in this.proxyDictionaries)) { this.proxyDictionaries[socksProxy] = new this.socksProxyAgentModule.SocksProxyAgent(socksProxy); } chosenAgent = this.proxyDictionaries[socksProxy]; } return chosenAgent; } getHttpAgentIfNeeded(url) { return undefined; } async fetch(url, method = 'GET', headers = undefined, body = undefined) { // ##### PROXY & HEADERS ##### headers = this.extend(this.headers, headers); // proxy-url const proxyUrl = this.checkProxyUrlSettings(url, method, headers, body); if (proxyUrl !== undefined) { url = proxyUrl + this.encodeURIComponent(url); } // proxy agents const [httpProxy, httpsProxy, socksProxy] = this.checkProxySettings(url, method, headers, body); this.checkConflictingProxies(httpProxy || httpsProxy || socksProxy, proxyUrl); if (!this.proxyModulesLoaded) { await this.loadProxyModules(); // this is needed in JS, independently whether proxy properties were set or not, we have to load them because of necessity in WS, which would happen beyond 'fetch' method (WS/etc) } const chosenAgent = this.setProxyAgents(httpProxy, httpsProxy, socksProxy); // user-agent (this.userAgent !== undefined) ? this.userAgent : this.user_agent; // set final headers headers = this.setHeaders(headers); // log if (this.verbose) { this.log("fetch Request:\n", this.id, method, url, "\nRequestHeaders:\n", headers, "\nRequestBody:\n", body, "\n"); } if (this.fetchImplementation === undefined) { 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; } // override agent, if needed if (chosenAgent) { // if http(s)Proxy is being used params['agent'] = chosenAgent; } 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; } } spawn(method, ...args) { const future = Future.Future(); 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', {}); // proxy agents const [httpProxy, httpsProxy, socksProxy] = this.checkWsProxySettings(); const chosenAgent = this.setProxyAgents(httpProxy, httpsProxy, socksProxy); // part only for node-js const httpProxyAgent = this.getHttpAgentIfNeeded(url); const finalAgent = chosenAgent ? chosenAgent : (httpProxyAgent ? httpProxyAgent : this.agent); // 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': finalAgent, } }, wsOptions); this.clients[url] = new WsClient(url, onMessage, onError, onClose, onConnected, options); } return this.clients[url]; } watchMultiple(url, messageHashes, message = undefined, subscribeHashes = 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 // const future = Future.Future.race(messageHashes.map(messageHash => 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 let missingSubscriptions = []; if (subscribeHashes !== undefined) { for (let i = 0; i < subscribeHashes.length; i++) { const subscribeHash = subscribeHashes[i]; if (!client.subscriptions[subscribeHash]) { missingSubscriptions.push(subscribeHash); 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 ((subscribeHashes === undefined) || missingSubscriptions.length) { 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) => { for (let i = 0; i < missingSubscriptions.length; i++) { const subscribeHash = missingSubscriptions[i]; delete client.subscriptions[subscribeHash]; } future.reject(e); }); } else { client.send(message) .catch((e) => { for (let i = 0; i < missingSubscriptions.length; i++) { const subscribeHash = missingSubscriptions[i]; delete client.subscriptions[subscribeHash]; } future.reject(e); }); } } }).catch((e) => { for (let i = 0; i < missingSubscriptions.length; i++) { const subscribeHash = missingSubscriptions[i]; delete client.subscriptions[subscribeHash]; } future.reject(e); }); } return future; } 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 // | | // ↓ ↑ // | | //