UNPKG

ccxt

Version:

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

1,041 lines (1,038 loc) 348 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // ---------------------------------------------------------------------------- /* eslint-disable */ import * as functions from './functions.js'; const { isNode, selfIsDefined, 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, roundTimeframe, parseTimeframe, safeInteger2, safeStringLower, parse8601, yyyymmdd, safeStringUpper, safeTimestamp, binaryConcatArray, uuidv1, numberToLE, ymdhms, stringToBase64, decode, uuid22, safeIntegerProduct2, safeIntegerProduct, safeStringLower2, yymmdd, base58ToBinary, binaryToBase58, safeTimestamp2, rawencode, keysort, sort, inArray, isEmpty, ordered, filterBy, uuid16, safeFloat, base64ToBinary, safeStringUpper2, urlencodeWithArrayRepeat, microseconds, binaryToBase64, strip, toArray, safeFloatN, safeIntegerN, safeIntegerProductN, safeTimestampN, safeValueN, safeStringN, safeStringLowerN, safeStringUpperN, urlencodeNested, urlencodeBase64, parseDate, ymd, base64ToString, crc32, packb, TRUNCATE, ROUND, DECIMAL_PLACES, NO_PADDING, TICK_SIZE, SIGNIFICANT_DIGITS, sleep } = functions; import { keys as keysFunc, values as valuesFunc, vwap as vwapFunc } from './functions.js'; // import exceptions from "./errors.js" import { // eslint-disable-line object-curly-newline ExchangeError, BadSymbol, NullResponse, InvalidAddress, InvalidOrder, NotSupported, OperationFailed, BadResponse, AuthenticationError, DDoSProtection, RequestTimeout, NetworkError, InvalidProxySettings, ExchangeNotAvailable, ArgumentsRequired, RateLimitExceeded, BadRequest, UnsubscribeError } from "./errors.js"; import { Precise } from './Precise.js'; //----------------------------------------------------------------------------- import WsClient from './ws/WsClient.js'; import { Future } from './ws/Future.js'; import { OrderBook as WsOrderBook, IndexedOrderBook, CountedOrderBook } from './ws/OrderBook.js'; // ---------------------------------------------------------------------------- // import { axolotl } from './functions/crypto.js'; import totp from './functions/totp.js'; import ethers from '../static_dependencies/ethers/index.js'; import { TypedDataEncoder } from '../static_dependencies/ethers/hash/index.js'; import { SecureRandom } from "../static_dependencies/jsencrypt/lib/jsbn/rng.js"; import { getStarkKey, ethSigToPrivate, sign as starknetCurveSign } from '../static_dependencies/scure-starknet/index.js'; import init, * as zklink from '../static_dependencies/zklink/zklink-sdk-web.js'; import * as Starknet from '../static_dependencies/starknet/index.js'; import { sha256 } from '../static_dependencies/noble-hashes/sha256.js'; // ---------------------------------------------------------------------------- /** * @class Exchange */ export default class Exchange { constructor(userConfig = {}) { this.isSandboxModeEnabled = false; this.throttleProp = undefined; this.sleep = sleep; this.api = undefined; this.certified = false; this.pro = false; this.countries = 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.MAX_VALUE = Number.MAX_VALUE; // 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 (2-FA) this.balance = {}; this.liquidations = {}; this.orderbooks = {}; this.tickers = {}; this.fundingRates = {}; this.bidsasks = {}; this.orders = undefined; this.triggerOrders = undefined; this.transactions = {}; this.myLiquidations = {}; this.requiresWeb3 = false; this.requiresEddsa = false; this.precision = undefined; this.enableLastJsonResponse = false; 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 = 'Exchange'; this.markets = undefined; this.features = undefined; this.status = undefined; this.rateLimit = undefined; // milliseconds this.tokenBucket = undefined; this.throttler = undefined; this.enableRateLimit = undefined; this.httpExceptions = undefined; this.limits = undefined; this.markets_by_id = undefined; this.symbols = undefined; this.ids = undefined; this.currencies = {}; 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.alias = false; this.deepExtend = deepExtend; this.deepExtendSafe = deepExtend; this.isNode = isNode; this.keys = keysFunc; this.values = valuesFunc; this.extend = extend; this.clone = clone; this.flatten = flatten; this.unique = unique; this.indexBy = indexBy; this.indexBySafe = indexBy; this.roundTimeframe = roundTimeframe; 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 = vwapFunc; 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.sort = sort; 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.packb = packb; this.urlencodeBase64 = urlencodeBase64; this.httpProxyAgentModule = undefined; this.httpsProxyAgentModule = undefined; this.socksProxyAgentModule = undefined; this.socksProxyAgentModuleChecked = false; this.proxyDictionaries = {}; this.proxiesModulesLoading = undefined; 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.liquidations = {}; this.orders = undefined; this.trades = {}; this.transactions = {}; this.ohlcvs = {}; this.myLiquidations = {}; 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 = false; 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; } } // http client options const agentOptions = { 'keepAlive': true, }; // ssl options if (!this.validateServerSsl) { agentOptions['rejectUnauthorized'] = false; } // 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'); } this.newUpdates = (this.options.newUpdates !== undefined) ? this.options.newUpdates : true; this.afterConstruct(); if (this.safeBool(userConfig, 'sandbox') || this.safeBool(userConfig, 'testnet')) { this.setSandboxMode(true); } } 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 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; } throttle(cost = undefined) { return this.throttler.throttle(cost); } initThrottler() { this.throttler = new Throttler(this.tokenBucket); } 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); } async loadProxyModules() { // when loading markets, multiple parallel calls are made, so need one promise if (this.proxiesModulesLoading === undefined) { this.proxiesModulesLoading = (async () => { // we have to handle it with below nested way, because of dynamic // import issues (https://github.com/ccxt/ccxt/pull/20687) try { // todo: possible sync alternatives: https://stackoverflow.com/questions/51069002/convert-import-to-synchronous this.httpProxyAgentModule = await import(/* webpackIgnore: true */ '../static_dependencies/proxies/http-proxy-agent/index.js'); this.httpsProxyAgentModule = await import(/* webpackIgnore: true */ '../static_dependencies/proxies/https-proxy-agent/index.js'); } catch (e) { // if several users are using those frameworks which cause exceptions, // let them to be able to load modules still, by installing them try { // @ts-ignore this.httpProxyAgentModule = await import(/* webpackIgnore: true */ 'http-proxy-agent'); // @ts-ignore this.httpsProxyAgentModule = await import(/* webpackIgnore: true */ 'https-proxy-agent'); } catch (e) { } } if (this.socksProxyAgentModuleChecked === false) { try { // @ts-ignore this.socksProxyAgentModule = await import(/* webpackIgnore: true */ 'socks-proxy-agent'); } catch (e) { } this.socksProxyAgentModuleChecked = true; } })(); } return await this.proxiesModulesLoading; } setProxyAgents(httpProxy, httpsProxy, socksProxy) { let chosenAgent = undefined; // in browser-side, proxy modules are not supported in 'fetch/ws' methods if (!isNode && (httpProxy || httpsProxy || socksProxy)) { throw new NotSupported(this.id + ' - proxies in browser-side projects are not supported. You have several choices: [A] Use `exchange.proxyUrl` property to redirect requests through local/remote cors-proxy server (find sample file named "sample-local-proxy-server-with-cors" in https://github.com/ccxt/ccxt/tree/master/examples/ folder, which can be used for REST requests only) [B] override `exchange.fetch` && `exchange.watch` methods to send requests through your custom proxy'); } if (httpProxy) { if (this.httpProxyAgentModule === undefined) { throw new NotSupported(this.id + ' you need to load JS proxy modules with `await instance.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 NotSupported(this.id + ' you need to load JS proxy modules with `await instance.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 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 `await instance.loadProxyModules()` method'); } if (!(socksProxy in this.proxyDictionaries)) { this.proxyDictionaries[socksProxy] = new this.socksProxyAgentModule.SocksProxyAgent(socksProxy); } chosenAgent = this.proxyDictionaries[socksProxy]; } return chosenAgent; } async loadHttpProxyAgent() { // for `http://` protocol proxy-urls, we need to load `http` module only on first call if (!this.httpAgent) { const httpModule = await import(/* webpackIgnore: true */ 'node:http'); this.httpAgent = new httpModule.Agent(); } return this.httpAgent; } getHttpAgentIfNeeded(url) { if (isNode) { // only for non-ssl proxy if (url.substring(0, 5) === 'ws://') { if (this.httpAgent === undefined) { throw new NotSupported(this.id + ' to use proxy with non-ssl ws:// urls, at first run `await exchange.loadHttpProxyAgent()` method'); } return this.httpAgent; } } return undefined; } async fetch(url, method = 'GET', headers = undefined, body = undefined) { // load node-http(s) modules only on first call if (isNode) { if (!this.nodeHttpModuleLoaded) { this.nodeHttpModuleLoaded = true; const httpsModule = await import(/* webpackIgnore: true */ 'node:https'); this.httpsAgent = new httpsModule.Agent({ keepAlive: true }); } } // ##### PROXY & HEADERS ##### headers = this.extend(this.headers, headers); // proxy-url const proxyUrl = this.checkProxyUrlSettings(url, method, headers, body); let httpProxyAgent = false; if (proxyUrl !== undefined) { // part only for node-js if (isNode) { // in node-js we need to set header to * headers = this.extend({ 'Origin': this.origin }, headers); // only for http proxy if (proxyUrl.substring(0, 5) === 'http:') { await this.loadHttpProxyAgent(); httpProxyAgent = this.httpAgent; } } url = proxyUrl + this.urlEncoderForProxyUrl(url); } // proxy agents const [httpProxy, httpsProxy, socksProxy] = this.checkProxySettings(url, method, headers, body); this.checkConflictingProxies(httpProxy || httpsProxy || socksProxy, proxyUrl); // skip proxies on the browser if (isNode) { // 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) await this.loadProxyModules(); } const chosenAgent = this.setProxyAgents(httpProxy, httpsProxy, socksProxy); // user-agent 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); } } // 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"); } // end of proxies & headers if (this.fetchImplementation === undefined) { if (isNode) { if (this.agent === undefined) { this.agent = this.httpsAgent; } try { const module = await import(/* webpackIgnore: true */ '../static_dependencies/node-fetch/index.js'); this.AbortError = module.AbortError; this.fetchImplementation = module.default; this.FetchError = module.FetchError; } catch (e) { // some users having issues with dynamic imports (https://github.com/ccxt/ccxt/pull/20687) // so let them to fallback to node's native fetch if (typeof fetch === 'function') { this.fetchImplementation = fetch; // as it's browser-compatible implementation ( https://nodejs.org/dist/latest-v20.x/docs/api/globals.html#fetch ) // it throws same error types this.AbortError = DOMException; this.FetchError = TypeError; } else { throw new Error('Seems, "fetch" function is not available in your node-js version, please use latest node-js version'); } } } else { this.fetchImplementation = (selfIsDefined()) ? self.fetch : 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 (httpProxyAgent) { // if proxyUrl is being used, then specifically in nodejs, we need http module, not https params['agent'] = httpProxyAgent; } else 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 RequestTimeout(this.id + ' ' + method + ' ' + url + ' request timed out (' + this.timeout + ' ms)'); } else if (e instanceof this.FetchError) { throw new 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(); this.options['cachedCurrencies'] = currencies; } const markets = await this.fetchMarkets(params); if ('cachedCurrencies' in this.options) { delete this.options['cachedCurrencies']; } return this.setMarkets(markets, currencies); } /** * @method * @name Exchange#loadMarkets * @description Loads and prepares the markets for trading. * @param {boolean} reload - If true, the markets will be reloaded from the exchange. * @param {object} params - Additional exchange-specific parameters for the request. * @returns A promise that resolves to a dictionary of markets. * @throws An error if the markets cannot be loaded or prepared. * @remarks This method is asynchronous and returns a promise. * It ensures that the markets are only loaded once, even if the method is called multiple times. * If the markets are already loaded and not reloading, the method returns the existing markets. * If the markets are being reloaded, the method waits for the reload to complete before returning the markets. * If an error occurs during the loading or preparation of the markets, the promise is rejected with the error. */ async loadMarkets(reload = false, params = {}) { 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; } async 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)); } async fetchCurrenciesWs(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)); } async 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))); } async fetchMarketsWs(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 { // we should handle scientific notation here // so if the exchanges returns 1e-8 // this function will return 0.00000001 // check https://github.com/ccxt/ccxt/issues/24135 const numberNormalized = this.numberToString(value); if (numberNormalized.indexOf('e-') > -1) { return this.number(numberToString(parseFloat(numberNormalized))); } const result = this.number(numberNormalized); return isNaN(result) ? d : result; } 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); } } remove0xPrefix(hexData) { if (hexData.slice(0, 2) === '0x') { return hexData.slice(2); } else { return hexData; } } mapToSafeMap(dict) { return dict; // wrapper for go } safeMapToMap(dict) { return dict; // wrapper for go } spawn(method, ...args) { const future = Future(); // using setTimeout 0 to force the execution to run after the future is returned setTimeout(() => { method.apply(this, args).then(future.resolve).catch(future.reject); }, 0); return future; } delay(timeout, method, ...args) { setTimeout(() => { this.spawn(method, ...args); }, timeout); } // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- // WS/PRO methods orderBook(snapshot = {}, depth = Number.MAX_SAFE_INTEGER) { return new WsOrderBook(snapshot, depth); } indexedOrderBook(snapshot = {}, depth = Number.MAX_SAFE_INTEGER) { return new IndexedOrderBook(snapshot, depth); } countedOrderBook(snapshot = {}, depth = Number.MAX_SAFE_INTEGER) { return new CountedOrderBook(snapshot, depth); } handleMessage(client, message) { } // stub to override // ping (client: Client) {} // stub to override ping(client) { return undefined; } 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.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 // | | // ↓ ↑ // | | // 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