@proton/ccxt
Version:
A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges
1,024 lines (1,022 loc) • 96 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
// ---------------------------------------------------------------------------
import Exchange from './abstract/exmo.js';
import { ArgumentsRequired, ExchangeError, OrderNotFound, AuthenticationError, InsufficientFunds, InvalidOrder, InvalidNonce, OnMaintenance, RateLimitExceeded, BadRequest, PermissionDenied } from './base/errors.js';
import { Precise } from './base/Precise.js';
import { TICK_SIZE } from './base/functions/number.js';
import { sha512 } from './static_dependencies/noble-hashes/sha512.js';
// ---------------------------------------------------------------------------
export default class exmo extends Exchange {
describe() {
return this.deepExtend(super.describe(), {
'id': 'exmo',
'name': 'EXMO',
'countries': ['LT'],
'rateLimit': 350,
'version': 'v1.1',
'has': {
'CORS': undefined,
'spot': true,
'margin': true,
'swap': false,
'future': false,
'option': false,
'addMargin': true,
'cancelOrder': true,
'cancelOrders': false,
'createDepositAddress': false,
'createOrder': true,
'createStopLimitOrder': true,
'createStopMarketOrder': true,
'createStopOrder': true,
'fetchAccounts': false,
'fetchBalance': true,
'fetchCanceledOrders': true,
'fetchCurrencies': true,
'fetchDeposit': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchDepositsWithdrawals': true,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': true,
'fetchFundingHistory': false,
'fetchFundingRate': false,
'fetchFundingRateHistory': false,
'fetchFundingRates': false,
'fetchIndexOHLCV': false,
'fetchMarginMode': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenInterestHistory': false,
'fetchOpenOrders': true,
'fetchOrder': 'emulated',
'fetchOrderBook': true,
'fetchOrderBooks': true,
'fetchOrderTrades': true,
'fetchPositionMode': false,
'fetchPremiumIndexOHLCV': false,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchTradingFee': false,
'fetchTradingFees': true,
'fetchTransactionFees': true,
'fetchTransactions': true,
'fetchTransfer': false,
'fetchTransfers': false,
'fetchWithdrawal': true,
'fetchWithdrawals': true,
'reduceMargin': true,
'setMargin': false,
'transfer': false,
'withdraw': true,
},
'timeframes': {
'1m': '1',
'5m': '5',
'15m': '15',
'30m': '30',
'45m': '45',
'1h': '60',
'2h': '120',
'3h': '180',
'4h': '240',
'1d': 'D',
'1w': 'W',
'1M': 'M',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766491-1b0ea956-5eda-11e7-9225-40d67b481b8d.jpg',
'api': {
'public': 'https://api.exmo.com',
'private': 'https://api.exmo.com',
'web': 'https://exmo.me',
},
'www': 'https://exmo.me',
'referral': 'https://exmo.me/?ref=131685',
'doc': [
'https://exmo.me/en/api_doc?ref=131685',
],
'fees': 'https://exmo.com/en/docs/fees',
},
'api': {
'web': {
'get': [
'ctrl/feesAndLimits',
'en/docs/fees',
],
},
'public': {
'get': [
'currency',
'currency/list/extended',
'order_book',
'pair_settings',
'ticker',
'trades',
'candles_history',
'required_amount',
'payments/providers/crypto/list',
],
},
'private': {
'post': [
'user_info',
'order_create',
'order_cancel',
'stop_market_order_create',
'stop_market_order_cancel',
'user_open_orders',
'user_trades',
'user_cancelled_orders',
'order_trades',
'deposit_address',
'withdraw_crypt',
'withdraw_get_txid',
'excode_create',
'excode_load',
'code_check',
'wallet_history',
'wallet_operations',
'margin/user/order/create',
'margin/user/order/update',
'margin/user/order/cancel',
'margin/user/position/close',
'margin/user/position/margin_add',
'margin/user/position/margin_remove',
'margin/currency/list',
'margin/pair/list',
'margin/settings',
'margin/funding/list',
'margin/user/info',
'margin/user/order/list',
'margin/user/order/history',
'margin/user/order/trades',
'margin/user/order/max_quantity',
'margin/user/position/list',
'margin/user/position/margin_remove_info',
'margin/user/position/margin_add_info',
'margin/user/wallet/list',
'margin/user/wallet/history',
'margin/user/trade/list',
'margin/trades',
'margin/liquidation/feed',
],
},
},
'fees': {
'trading': {
'feeSide': 'get',
'tierBased': true,
'percentage': true,
'maker': this.parseNumber('0.004'),
'taker': this.parseNumber('0.004'),
},
'transaction': {
'tierBased': false,
'percentage': false, // fixed transaction fees for crypto, see fetchDepositWithdrawFees below
},
},
'options': {
'networks': {
'ETH': 'ERC20',
'TRX': 'TRC20',
},
'fetchTradingFees': {
'method': 'fetchPrivateTradingFees', // or 'fetchPublicTradingFees'
},
'margin': {
'fillResponseFromRequest': true,
},
},
'commonCurrencies': {
'GMT': 'GMT Token',
},
'precisionMode': TICK_SIZE,
'exceptions': {
'exact': {
'40005': AuthenticationError,
'40009': InvalidNonce,
'40015': ExchangeError,
'40016': OnMaintenance,
'40017': AuthenticationError,
'40032': PermissionDenied,
'40033': PermissionDenied,
'40034': RateLimitExceeded,
'50052': InsufficientFunds,
'50054': InsufficientFunds,
'50304': OrderNotFound,
'50173': OrderNotFound,
'50277': InvalidOrder,
'50319': InvalidOrder,
'50321': InvalidOrder,
'50381': InvalidOrder, // {"result":false,"error":"Error 50381: More than 2 decimal places are not permitted for pair BTC_USD"}
},
'broad': {
'range period is too long': BadRequest,
'invalid syntax': BadRequest,
'API rate limit exceeded': RateLimitExceeded, // {"result":false,"error":"API rate limit exceeded for x.x.x.x. Retry after 60 sec.","history":[],"begin":1579392000,"end":1579478400}
},
},
});
}
async modifyMarginHelper(symbol, amount, type, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'position_id': market['id'],
'quantity': amount,
};
let method = undefined;
if (type === 'add') {
method = 'privatePostMarginUserPositionMarginAdd';
}
else if (type === 'reduce') {
method = 'privatePostMarginUserPositionMarginReduce';
}
const response = await this[method](this.extend(request, params));
//
// {}
//
const margin = this.parseMarginModification(response, market);
const options = this.safeValue(this.options, 'margin', {});
const fillResponseFromRequest = this.safeValue(options, 'fillResponseFromRequest', true);
if (fillResponseFromRequest) {
margin['type'] = type;
margin['amount'] = amount;
}
return margin;
}
parseMarginModification(data, market = undefined) {
//
// {}
//
return {
'info': data,
'type': undefined,
'amount': undefined,
'code': this.safeValue(market, 'quote'),
'symbol': this.safeSymbol(undefined, market),
'total': undefined,
'status': 'ok',
};
}
async reduceMargin(symbol, amount, params = {}) {
/**
* @method
* @name exmo#reduceMargin
* @description remove margin from a position
* @param {string} symbol unified market symbol
* @param {float} amount the amount of margin to remove
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a [margin structure]{@link https://docs.ccxt.com/#/?id=reduce-margin-structure}
*/
return await this.modifyMarginHelper(symbol, amount, 'reduce', params);
}
async addMargin(symbol, amount, params = {}) {
/**
* @method
* @name exmo#addMargin
* @description add margin
* @param {string} symbol unified market symbol
* @param {float} amount amount of margin to add
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a [margin structure]{@link https://docs.ccxt.com/#/?id=add-margin-structure}
*/
return await this.modifyMarginHelper(symbol, amount, 'add', params);
}
async fetchTradingFees(params = {}) {
/**
* @method
* @name exmo#fetchTradingFees
* @description fetch the trading fees for multiple markets
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/#/?id=fee-structure} indexed by market symbols
*/
let method = this.safeString(params, 'method');
params = this.omit(params, 'method');
if (method === undefined) {
const options = this.safeValue(this.options, 'fetchTradingFees', {});
method = this.safeString(options, 'method', 'fetchPrivateTradingFees');
}
return await this[method](params);
}
async fetchPrivateTradingFees(params = {}) {
await this.loadMarkets();
const response = await this.privatePostMarginPairList(params);
//
// {
// pairs: [{
// name: 'EXM_USD',
// buy_price: '0.02728391',
// sell_price: '0.0276',
// last_trade_price: '0.0276',
// ticker_updated: '1646956050056696046',
// is_fair_price: true,
// max_price_precision: '8',
// min_order_quantity: '1',
// max_order_quantity: '50000',
// min_order_price: '0.00000001',
// max_order_price: '1000',
// max_position_quantity: '50000',
// trade_taker_fee: '0.05',
// trade_maker_fee: '0',
// liquidation_fee: '0.5',
// max_leverage: '3',
// default_leverage: '3',
// liquidation_level: '5',
// margin_call_level: '7.5',
// position: '1',
// updated: '1638976144797807397'
// }
// ...
// ]
// }
//
const pairs = this.safeValue(response, 'pairs', []);
const result = {};
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i];
const marketId = this.safeString(pair, 'name');
const symbol = this.safeSymbol(marketId, undefined, '_');
const makerString = this.safeString(pair, 'trade_maker_fee');
const takerString = this.safeString(pair, 'trade_taker_fee');
const maker = this.parseNumber(Precise.stringDiv(makerString, '100'));
const taker = this.parseNumber(Precise.stringDiv(takerString, '100'));
result[symbol] = {
'info': pair,
'symbol': symbol,
'maker': maker,
'taker': taker,
'percentage': true,
'tierBased': true,
};
}
return result;
}
async fetchPublicTradingFees(params = {}) {
await this.loadMarkets();
const response = await this.publicGetPairSettings(params);
//
// {
// BTC_USD: {
// min_quantity: '0.00002',
// max_quantity: '1000',
// min_price: '1',
// max_price: '150000',
// max_amount: '500000',
// min_amount: '1',
// price_precision: '2',
// commission_taker_percent: '0.3',
// commission_maker_percent: '0.3'
// },
// }
//
const result = {};
for (let i = 0; i < this.symbols.length; i++) {
const symbol = this.symbols[i];
const market = this.market(symbol);
const fee = this.safeValue(response, market['id'], {});
const makerString = this.safeString(fee, 'commission_maker_percent');
const takerString = this.safeString(fee, 'commission_taker_percent');
const maker = this.parseNumber(Precise.stringDiv(makerString, '100'));
const taker = this.parseNumber(Precise.stringDiv(takerString, '100'));
result[symbol] = {
'info': fee,
'symbol': symbol,
'maker': maker,
'taker': taker,
'percentage': true,
'tierBased': true,
};
}
return result;
}
parseFixedFloatValue(input) {
if ((input === undefined) || (input === '-')) {
return undefined;
}
if (input === '') {
return 0;
}
const isPercentage = (input.indexOf('%') >= 0);
const parts = input.split(' ');
const value = parts[0].replace('%', '');
const result = parseFloat(value);
if ((result > 0) && isPercentage) {
throw new ExchangeError(this.id + ' parseFixedFloatValue() detected an unsupported non-zero percentage-based fee ' + input);
}
return result;
}
async fetchTransactionFees(codes = undefined, params = {}) {
/**
* @method
* @name exmo#fetchTransactionFees
* @description *DEPRECATED* please use fetchDepositWithdrawFees instead
* @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2
* @param {[string]|undefined} codes list of unified currency codes
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a list of [transaction fees structures]{@link https://docs.ccxt.com/#/?id=fees-structure}
*/
await this.loadMarkets();
const cryptoList = await this.publicGetPaymentsProvidersCryptoList(params);
//
// {
// "BTC":[
// { "type":"deposit", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 },
// { "type":"withdraw", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"350", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.0005 BTC", "currency_confirmations":6 }
// ],
// "ETH":[
// { "type":"withdraw", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"500", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.004 ETH", "currency_confirmations":4 },
// { "type":"deposit", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.01 ETH. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 }
// ],
// "USDT":[
// { "type":"deposit", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":false,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 },
// { "type":"withdraw", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":false,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"5 USDT", "currency_confirmations":6 },
// { "type":"deposit", "name":"USDT (ERC20)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 },
// {
// "type":"withdraw",
// "name":"USDT (ERC20)",
// "currency_name":"USDT",
// "min":"55",
// "max":"200000",
// "enabled":true,
// "comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Recommendation: Due to the high load of ERC20 network, using TRC20 address for withdrawal is recommended.",
// "commission_desc":"10 USDT",
// "currency_confirmations":6
// },
// { "type":"deposit", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":true,"comment":"Minimum deposit amount is 10 USDT. Only TRON main network supported", "commission_desc":"0%", "currency_confirmations":2 },
// { "type":"withdraw", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"150000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Only TRON main network supported.", "commission_desc":"1 USDT", "currency_confirmations":6 }
// ],
// "XLM":[
// { "type":"deposit", "name":"XLM", "currency_name":"XLM", "min":"1", "max":"1000000", "enabled":true,"comment":"Attention! A deposit without memo(invoice) will not be credited. Minimum deposit amount is 1 XLM. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 },
// { "type":"withdraw", "name":"XLM", "currency_name":"XLM", "min":"21", "max":"1000000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1 }
// ],
// }
//
const result = {};
const cryptoListKeys = Object.keys(cryptoList);
for (let i = 0; i < cryptoListKeys.length; i++) {
const code = cryptoListKeys[i];
if (codes !== undefined && !this.inArray(code, codes)) {
continue;
}
result[code] = {
'deposit': undefined,
'withdraw': undefined,
};
const currency = this.currency(code);
const currencyId = this.safeString(currency, 'id');
const providers = this.safeValue(cryptoList, currencyId, []);
for (let j = 0; j < providers.length; j++) {
const provider = providers[j];
const typeInner = this.safeString(provider, 'type');
const commissionDesc = this.safeString(provider, 'commission_desc');
const fee = this.parseFixedFloatValue(commissionDesc);
result[code][typeInner] = fee;
}
result[code]['info'] = providers;
}
// cache them for later use
this.options['transactionFees'] = result;
return result;
}
async fetchDepositWithdrawFees(codes = undefined, params = {}) {
/**
* @method
* @name exmo#fetchDepositWithdrawFees
* @description fetch deposit and withdraw fees
* @see https://documenter.getpostman.com/view/10287440/SzYXWKPi#4190035d-24b1-453d-833b-37e0a52f88e2
* @param {[string]|undefined} codes list of unified currency codes
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a list of [transaction fees structures]{@link https://docs.ccxt.com/#/?id=fees-structure}
*/
await this.loadMarkets();
const response = await this.publicGetPaymentsProvidersCryptoList(params);
//
// {
// "USDT": [
// {
// "type": "deposit", // or "withdraw"
// "name": "USDT (ERC20)",
// "currency_name": "USDT",
// "min": "10",
// "max": "0",
// "enabled": true,
// "comment": "Minimum deposit amount is 10 USDT",
// "commission_desc": "0%",
// "currency_confirmations": 2
// },
// ...
// ],
// ...
// }
//
const result = this.parseDepositWithdrawFees(response, codes);
// cache them for later use
this.options['transactionFees'] = result;
return result;
}
parseDepositWithdrawFee(fee, currency = undefined) {
//
// [
// {
// "type": "deposit", // or "withdraw"
// "name": "BTC",
// "currency_name": "BTC",
// "min": "0.001",
// "max": "0",
// "enabled": true,
// "comment": "Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider this when sending funds",
// "commission_desc": "0%",
// "currency_confirmations": 1
// },
// ...
// ]
//
const result = this.depositWithdrawFee(fee);
for (let i = 0; i < fee.length; i++) {
const provider = fee[i];
const type = this.safeString(provider, 'type');
const networkId = this.safeString(provider, 'name');
const networkCode = this.networkIdToCode(networkId, this.safeString(currency, 'code'));
const commissionDesc = this.safeString(provider, 'commission_desc');
let splitCommissionDesc = [];
let percentage = undefined;
if (commissionDesc !== undefined) {
splitCommissionDesc = commissionDesc.split('%');
const splitCommissionDescLength = splitCommissionDesc.length;
percentage = splitCommissionDescLength >= 2;
}
const network = this.safeValue(result['networks'], networkCode);
if (network === undefined) {
result['networks'][networkCode] = {
'withdraw': {
'fee': undefined,
'percentage': undefined,
},
'deposit': {
'fee': undefined,
'percentage': undefined,
},
};
}
result['networks'][networkCode][type] = {
'fee': this.parseFixedFloatValue(this.safeString(splitCommissionDesc, 0)),
'percentage': percentage,
};
}
return this.assignDefaultDepositWithdrawFees(result);
}
async fetchCurrencies(params = {}) {
/**
* @method
* @name exmo#fetchCurrencies
* @description fetches all available currencies on an exchange
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} an associative dictionary of currencies
*/
//
const currencyList = await this.publicGetCurrencyListExtended(params);
//
// [
// {"name":"VLX","description":"Velas"},
// {"name":"RUB","description":"Russian Ruble"},
// {"name":"BTC","description":"Bitcoin"},
// {"name":"USD","description":"US Dollar"}
// ]
//
const cryptoList = await this.publicGetPaymentsProvidersCryptoList(params);
//
// {
// "BTC":[
// { "type":"deposit", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.001 BTC. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 },
// { "type":"withdraw", "name":"BTC", "currency_name":"BTC", "min":"0.001", "max":"350", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.0005 BTC", "currency_confirmations":6 }
// ],
// "ETH":[
// { "type":"withdraw", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"500", "enabled":true,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"0.004 ETH", "currency_confirmations":4 },
// { "type":"deposit", "name":"ETH", "currency_name":"ETH", "min":"0.01", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 0.01 ETH. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 }
// ],
// "USDT":[
// { "type":"deposit", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":false,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 },
// { "type":"withdraw", "name":"USDT (OMNI)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":false,"comment":"Do not withdraw directly to the Crowdfunding or ICO address as your account will not be credited with tokens from such sales.", "commission_desc":"5 USDT", "currency_confirmations":6 },
// { "type":"deposit", "name":"USDT (ERC20)", "currency_name":"USDT", "min":"10", "max":"0", "enabled":true,"comment":"Minimum deposit amount is 10 USDT", "commission_desc":"0%", "currency_confirmations":2 },
// { "type":"withdraw", "name":"USDT (ERC20)", "currency_name":"USDT", "min":"55", "max":"200000", "enabled":true, "comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Recommendation: Due to the high load of ERC20 network, using TRC20 address for withdrawal is recommended.", "commission_desc":"10 USDT", "currency_confirmations":6 },
// { "type":"deposit", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"100000", "enabled":true,"comment":"Minimum deposit amount is 10 USDT. Only TRON main network supported", "commission_desc":"0%", "currency_confirmations":2 },
// { "type":"withdraw", "name":"USDT (TRC20)", "currency_name":"USDT", "min":"10", "max":"150000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales. Only TRON main network supported.", "commission_desc":"1 USDT", "currency_confirmations":6 }
// ],
// "XLM":[
// { "type":"deposit", "name":"XLM", "currency_name":"XLM", "min":"1", "max":"1000000", "enabled":true,"comment":"Attention! A deposit without memo(invoice) will not be credited. Minimum deposit amount is 1 XLM. We do not support BSC and BEP20 network, please consider this when sending funds", "commission_desc":"0%", "currency_confirmations":1 },
// { "type":"withdraw", "name":"XLM", "currency_name":"XLM", "min":"21", "max":"1000000", "enabled":true,"comment":"Caution! Do not withdraw directly to a crowdfund or ICO address, as your account will not be credited with tokens from such sales.", "commission_desc":"0.01 XLM", "currency_confirmations":1 }
// ],
// }
//
const result = {};
for (let i = 0; i < currencyList.length; i++) {
const currency = currencyList[i];
const currencyId = this.safeString(currency, 'name');
const name = this.safeString(currency, 'description');
const providers = this.safeValue(cryptoList, currencyId);
let active = false;
let type = 'crypto';
const limits = {
'deposit': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
};
let fee = undefined;
let depositEnabled = undefined;
let withdrawEnabled = undefined;
if (providers === undefined) {
active = true;
type = 'fiat';
}
else {
for (let j = 0; j < providers.length; j++) {
const provider = providers[j];
const typeInner = this.safeString(provider, 'type');
const minValue = this.safeString(provider, 'min');
let maxValue = this.safeString(provider, 'max');
if (Precise.stringEq(maxValue, '0.0')) {
maxValue = undefined;
}
const activeProvider = this.safeValue(provider, 'enabled');
if (typeInner === 'deposit') {
if (activeProvider && !depositEnabled) {
depositEnabled = true;
}
else if (!activeProvider) {
depositEnabled = false;
}
}
else if (typeInner === 'withdraw') {
if (activeProvider && !withdrawEnabled) {
withdrawEnabled = true;
}
else if (!activeProvider) {
withdrawEnabled = false;
}
}
if (activeProvider) {
active = true;
const limitMin = this.numberToString(limits[typeInner]['min']);
if ((limits[typeInner]['min'] === undefined) || (Precise.stringLt(minValue, limitMin))) {
limits[typeInner]['min'] = minValue;
limits[typeInner]['max'] = maxValue;
if (typeInner === 'withdraw') {
const commissionDesc = this.safeString(provider, 'commission_desc');
fee = this.parseFixedFloatValue(commissionDesc);
}
}
}
}
}
const code = this.safeCurrencyCode(currencyId);
result[code] = {
'id': currencyId,
'code': code,
'name': name,
'type': type,
'active': active,
'deposit': depositEnabled,
'withdraw': withdrawEnabled,
'fee': fee,
'precision': this.parseNumber('1e-8'),
'limits': limits,
'info': providers,
'networks': {},
};
}
return result;
}
async fetchMarkets(params = {}) {
/**
* @method
* @name exmo#fetchMarkets
* @description retrieves data on all markets for exmo
* @param {object} params extra parameters specific to the exchange api endpoint
* @returns {[object]} an array of objects representing market data
*/
const response = await this.publicGetPairSettings(params);
//
// {
// "BTC_USD":{
// "min_quantity":"0.0001",
// "max_quantity":"1000",
// "min_price":"1",
// "max_price":"30000",
// "max_amount":"500000",
// "min_amount":"1",
// "price_precision":8,
// "commission_taker_percent":"0.4",
// "commission_maker_percent":"0.4"
// },
// }
//
const keys = Object.keys(response);
const result = [];
for (let i = 0; i < keys.length; i++) {
const id = keys[i];
const market = response[id];
const symbol = id.replace('_', '/');
const [baseId, quoteId] = symbol.split('/');
const base = this.safeCurrencyCode(baseId);
const quote = this.safeCurrencyCode(quoteId);
const takerString = this.safeString(market, 'commission_taker_percent');
const makerString = this.safeString(market, 'commission_maker_percent');
result.push({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': undefined,
'baseId': baseId,
'quoteId': quoteId,
'settleId': undefined,
'type': 'spot',
'spot': true,
'margin': true,
'swap': false,
'future': false,
'option': false,
'active': undefined,
'contract': false,
'linear': undefined,
'inverse': undefined,
'taker': this.parseNumber(Precise.stringDiv(takerString, '100')),
'maker': this.parseNumber(Precise.stringDiv(makerString, '100')),
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.parseNumber('1e-8'),
'price': this.parseNumber(this.parsePrecision(this.safeString(market, 'price_precision'))),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': this.safeNumber(market, 'min_quantity'),
'max': this.safeNumber(market, 'max_quantity'),
},
'price': {
'min': this.safeNumber(market, 'min_price'),
'max': this.safeNumber(market, 'max_price'),
},
'cost': {
'min': this.safeNumber(market, 'min_amount'),
'max': this.safeNumber(market, 'max_amount'),
},
},
'info': market,
});
}
return result;
}
async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name exmo#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents
* @param {int|undefined} since timestamp in ms of the earliest candle to fetch
* @param {int|undefined} limit the maximum amount of candles to fetch
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'symbol': market['id'],
'resolution': this.safeString(this.timeframes, timeframe, timeframe),
};
const options = this.safeValue(this.options, 'fetchOHLCV');
const maxLimit = this.safeInteger(options, 'maxLimit', 3000);
const duration = this.parseTimeframe(timeframe);
const now = this.milliseconds();
if (since === undefined) {
if (limit === undefined) {
limit = 1000; // cap default at generous amount
}
if (limit > maxLimit) {
limit = maxLimit; // avoid exception
}
request['from'] = this.parseToInt(now / 1000) - limit * duration - 1;
request['to'] = this.parseToInt(now / 1000);
}
else {
request['from'] = this.parseToInt(since / 1000) - 1;
if (limit === undefined) {
request['to'] = this.parseToInt(now / 1000);
}
else {
if (limit > maxLimit) {
throw new BadRequest(this.id + ' fetchOHLCV() will serve ' + maxLimit.toString() + ' candles at most');
}
const to = this.sum(since, limit * duration * 1000);
request['to'] = this.parseToInt(to / 1000);
}
}
const response = await this.publicGetCandlesHistory(this.extend(request, params));
//
// {
// "candles":[
// {"t":1584057600000,"o":0.02235144,"c":0.02400233,"h":0.025171,"l":0.02221,"v":5988.34031761},
// {"t":1584144000000,"o":0.0240373,"c":0.02367413,"h":0.024399,"l":0.0235,"v":2027.82522329},
// {"t":1584230400000,"o":0.02363458,"c":0.02319242,"h":0.0237948,"l":0.02223196,"v":1707.96944997},
// ]
// }
//
const candles = this.safeValue(response, 'candles', []);
return this.parseOHLCVs(candles, market, timeframe, since, limit);
}
parseOHLCV(ohlcv, market = undefined) {
//
// {
// "t":1584057600000,
// "o":0.02235144,
// "c":0.02400233,
// "h":0.025171,
// "l":0.02221,
// "v":5988.34031761
// }
//
return [
this.safeInteger(ohlcv, 't'),
this.safeNumber(ohlcv, 'o'),
this.safeNumber(ohlcv, 'h'),
this.safeNumber(ohlcv, 'l'),
this.safeNumber(ohlcv, 'c'),
this.safeNumber(ohlcv, 'v'),
];
}
parseBalance(response) {
const result = { 'info': response };
const free = this.safeValue(response, 'balances', {});
const used = this.safeValue(response, 'reserved', {});
const currencyIds = Object.keys(free);
for (let i = 0; i < currencyIds.length; i++) {
const currencyId = currencyIds[i];
const code = this.safeCurrencyCode(currencyId);
const account = this.account();
if (currencyId in free) {
account['free'] = this.safeString(free, currencyId);
}
if (currencyId in used) {
account['used'] = this.safeString(used, currencyId);
}
result[code] = account;
}
return this.safeBalance(result);
}
async fetchBalance(params = {}) {
/**
* @method
* @name exmo#fetchBalance
* @description query for balance and get the amount of funds available for trading or funds locked in orders
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure}
*/
await this.loadMarkets();
const response = await this.privatePostUserInfo(params);
//
// {
// "uid":131685,
// "server_date":1628999600,
// "balances":{
// "EXM":"0",
// "USD":"0",
// "EUR":"0",
// "GBP":"0",
// },
// }
//
return this.parseBalance(response);
}
async fetchOrderBook(symbol, limit = undefined, params = {}) {
/**
* @method
* @name exmo#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int|undefined} limit the maximum amount of order book entries to return
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'pair': market['id'],
};
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.publicGetOrderBook(this.extend(request, params));
const result = this.safeValue(response, market['id']);
return this.parseOrderBook(result, market['symbol'], undefined, 'bid', 'ask');
}
async fetchOrderBooks(symbols = undefined, limit = undefined, params = {}) {
/**
* @method
* @name exmo#fetchOrderBooks
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data for multiple markets
* @param {[string]|undefined} symbols list of unified market symbols, all symbols fetched if undefined, default is undefined
* @param {int|undefined} limit max number of entries per orderbook to return, default is undefined
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbol
*/
await this.loadMarkets();
let ids = undefined;
if (symbols === undefined) {
ids = this.ids.join(',');
// max URL length is 2083 symbols, including http schema, hostname, tld, etc...
if (ids.length > 2048) {
const numIds = this.ids.length;
throw new ExchangeError(this.id + ' fetchOrderBooks() has ' + numIds.toString() + ' symbols exceeding max URL length, you are required to specify a list of symbols in the first argument to fetchOrderBooks');
}
}
else {
ids = this.marketIds(symbols);
ids = ids.join(',');
}
const request = {
'pair': ids,
};
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.publicGetOrderBook(this.extend(request, params));
const result = {};
const marketIds = Object.keys(response);
for (let i = 0; i < marketIds.length; i++) {
const marketId = marketIds[i];
const symbol = this.safeSymbol(marketId);
result[symbol] = this.parseOrderBook(response[marketId], symbol, undefined, 'bid', 'ask');
}
return result;
}
parseTicker(ticker, market = undefined) {
//
// {
// "buy_price":"0.00002996",
// "sell_price":"0.00003002",
// "last_trade":"0.00002992",
// "high":"0.00003028",
// "low":"0.00002935",
// "avg":"0.00002963",
// "vol":"1196546.3163222",
// "vol_curr":"35.80066578",
// "updated":1642291733
// }
//
const timestamp = this.safeTimestamp(ticker, 'updated');
market = this.safeMarket(undefined, market);
const last = this.safeString(ticker, 'last_trade');
return this.safeTicker({
'symbol': market['symbol'],
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'high': this.safeString(ticker, 'high'),
'low': this.safeString(ticker, 'low'),
'bid': this.safeString(ticker, 'buy_price'),
'bidVolume': undefined,
'ask': this.safeString(ticker, 'sell_price'),
'askVolume': undefined,
'vwap': undefined,
'open': undefined,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': this.safeString(ticker, 'avg'),
'baseVolume': this.safeString(ticker, 'vol'),
'quoteVolume': this.safeString(ticker, 'vol_curr'),
'info': ticker,
}, market);
}
async fetchTickers(symbols = undefined, params = {}) {
/**
* @method
* @name exmo#fetchTickers
* @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
* @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
* @param {object} params extra parameters specific to the exmo api endpoint
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
await this.loadMarke