@proton/ccxt
Version:
A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges
1,101 lines (1,099 loc) • 178 kB
JavaScript
// ----------------------------------------------------------------------------
// 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, 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;
// import exceptions from "./errors.js"
import { // eslint-disable-line object-curly-newline
ExchangeError, BadSymbol, NullResponse, InvalidAddress, InvalidOrder, NotSupported, AuthenticationError, DDoSProtection, RequestTimeout, NetworkError, ExchangeNotAvailable, ArgumentsRequired, RateLimitExceeded, BadRequest } from "./errors.js";
import { Precise } from './Precise.js';
//-----------------------------------------------------------------------------
import WsClient from './ws/WsClient.js';
import { createFuture } from './ws/Future.js';
import { OrderBook as WsOrderBook, IndexedOrderBook, CountedOrderBook } from './ws/OrderBook.js';
import totp from './functions/totp.js';
// ----------------------------------------------------------------------------
export default 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;
}
}
// 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');
}
// 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': ExchangeError,
'418': DDoSProtection,
'429': RateLimitExceeded,
'404': ExchangeNotAvailable,
'409': ExchangeNotAvailable,
'410': ExchangeNotAvailable,
'451': ExchangeNotAvailable,
'500': ExchangeNotAvailable,
'501': ExchangeNotAvailable,
'502': ExchangeNotAvailable,
'520': ExchangeNotAvailable,
'521': ExchangeNotAvailable,
'522': ExchangeNotAvailable,
'525': ExchangeNotAvailable,
'526': ExchangeNotAvailable,
'400': ExchangeNotAvailable,
'403': ExchangeNotAvailable,
'405': ExchangeNotAvailable,
'503': ExchangeNotAvailable,
'530': ExchangeNotAvailable,
'408': RequestTimeout,
'504': RequestTimeout,
'401': AuthenticationError,
'407': AuthenticationError,
'511': AuthenticationError,
},
'commonCurrencies': {
'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 NotSupported('Your current version of CCXT is ' + Exchange.ccxtVersion + ', a newer version ' + requiredVersion + ' is required, please, upgrade your version of CCXT');
}
else {
return error;
}
}
return result;
}
checkAddress(address) {
if (address === undefined) {
throw new InvalidAddress(this.id + ' address is undefined');
}
// check the address is not the same letter like 'aaaaa' nor too short nor has a space
if ((this.unique(address).length === 1) || address.length < this.minFundingAddressLength || address.includes(' ')) {
throw new InvalidAddress(this.id + ' address is invalid or has less than ' + this.minFundingAddressLength.toString() + ' characters: "' + this.json(address) + '"');
}
return address;
}
initRestRateLimiter() {
if (this.rateLimit === undefined) {
throw new Error(this.id + '.rateLimit property is not configured');
}
this.tokenBucket = this.extend({
delay: 0.001,
capacity: 1,
cost: 1,
maxCapacity: 1000,
refillRate: (this.rateLimit > 0) ? 1 / this.rateLimit : Number.MAX_VALUE,
}, this.tokenBucket);
this.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 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 import(/* 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 import(/* 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 import(/* webpackIgnore: true */ 'socks-proxy-agent');
}
catch (e) {
throw new 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 import(/* webpackIgnore: true */ '../static_dependencies/node-fetch/index.js');
if (this.agent === undefined) {
const { Agent } = await import(/* 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 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();
}
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 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;
}
}
// 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 = 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 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) {} // 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) {
// connection closed due to an error, do nothing
}
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 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 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 */
// ------------------------------------------------------------------------
// ########################################################################
// ########################################################################
// ########################################################################
// ########################################################################
// ######## ######## ########
// ######## ######## ########
// ######## ######## ########
// ######## ######## ########
// ######## ######################## ########################
// ######## ######################## ########################
// ######## ######################## ########################
// ######## ######################## ########################
// ######## ######## ########
// ######## ######## ########
// ######## ######## ########
// ######## ######## ########
// ########################################################################
// ########################################################################
// ########################################################################
// ########################################################################
// ######## ######## ######## ########
// ######## ######## ######## ########
// ######## ######## ######## ########
// ######## ######## ######## ########
// ################ ######################## ################
// ################ ######################## ################
// ################ ######################## ################
// ################ ######################## ################
// ######## ######## ################ ################
// ######## ######## ################ ################
// ######## ######## ################ ###########