UNPKG

sfccxt

Version:

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

1,100 lines (1,078 loc) 121 kB
'use strict'; // --------------------------------------------------------------------------- const Exchange = require ('./base/Exchange'); const { ExchangeError, InvalidAddress, ArgumentsRequired, InsufficientFunds, AuthenticationError, OrderNotFound, InvalidOrder, BadRequest, InvalidNonce, BadSymbol, OnMaintenance, NotSupported, PermissionDenied, ExchangeNotAvailable } = require ('./base/errors'); const Precise = require ('./base/Precise'); const { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } = require ('./base/functions/number'); // --------------------------------------------------------------------------- module.exports = class bitfinex2 extends Exchange { describe () { return this.deepExtend (super.describe (), { 'id': 'bitfinex2', 'name': 'Bitfinex', 'countries': [ 'VG' ], 'version': 'v2', 'certified': false, 'pro': false, // new metainfo interface 'has': { 'CORS': undefined, 'spot': true, 'margin': undefined, // has but unimplemented 'swap': undefined, // has but unimplemented 'future': undefined, 'option': undefined, 'cancelAllOrders': true, 'cancelOrder': true, 'createDepositAddress': true, 'createLimitOrder': true, 'createMarketOrder': true, 'createOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'editOrder': undefined, 'fetchBalance': true, 'fetchClosedOrder': true, 'fetchClosedOrders': true, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchIndexOHLCV': false, 'fetchLedger': true, 'fetchMarginMode': false, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenOrder': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderTrades': true, 'fetchPositionMode': false, 'fetchStatus': true, 'fetchTickers': true, 'fetchTime': false, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactionFees': undefined, 'fetchTransactions': true, 'withdraw': true, }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '3h': '3h', '4h': '4h', '6h': '6h', '12h': '12h', '1d': '1D', '1w': '7D', '2w': '14D', '1M': '1M', }, // cheapest endpoint is 240 requests per minute => ~ 4 requests per second => ( 1000ms / 4 ) = 250ms between requests on average 'rateLimit': 250, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/27766244-e328a50c-5ed2-11e7-947b-041416579bb3.jpg', 'api': { 'v1': 'https://api.bitfinex.com', 'public': 'https://api-pub.bitfinex.com', 'private': 'https://api.bitfinex.com', }, 'www': 'https://www.bitfinex.com', 'doc': [ 'https://docs.bitfinex.com/v2/docs/', 'https://github.com/bitfinexcom/bitfinex-api-node', ], 'fees': 'https://www.bitfinex.com/fees', }, 'api': { 'public': { 'get': { 'conf/{config}': 2.66, // 90 requests a minute 'conf/pub:{action}:{object}': 2.66, 'conf/pub:{action}:{object}:{detail}': 2.66, 'conf/pub:map:{object}': 2.66, 'conf/pub:map:{object}:{detail}': 2.66, 'conf/pub:map:currency:{detail}': 2.66, 'conf/pub:map:currency:sym': 2.66, // maps symbols to their API symbols, BAB > BCH 'conf/pub:map:currency:label': 2.66, // verbose friendly names, BNT > Bancor 'conf/pub:map:currency:unit': 2.66, // maps symbols to unit of measure where applicable 'conf/pub:map:currency:undl': 2.66, // maps derivatives symbols to their underlying currency 'conf/pub:map:currency:pool': 2.66, // maps symbols to underlying network/protocol they operate on 'conf/pub:map:currency:explorer': 2.66, // maps symbols to their recognised block explorer URLs 'conf/pub:map:currency:tx:fee': 2.66, // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745 'conf/pub:map:tx:method': 2.66, 'conf/pub:list:{object}': 2.66, 'conf/pub:list:{object}:{detail}': 2.66, 'conf/pub:list:currency': 2.66, 'conf/pub:list:pair:exchange': 2.66, 'conf/pub:list:pair:margin': 2.66, 'conf/pub:list:pair:futures': 2.66, 'conf/pub:list:competitions': 2.66, 'conf/pub:info:{object}': 2.66, 'conf/pub:info:{object}:{detail}': 2.66, 'conf/pub:info:pair': 2.66, 'conf/pub:info:pair:futures': 2.66, 'conf/pub:info:tx:status': 2.66, // [ deposit, withdrawal ] statuses 1 = active, 0 = maintenance 'conf/pub:fees': 2.66, 'platform/status': 8, // 30 requests per minute = 0.5 requests per second => ( 1000ms / rateLimit ) / 0.5 = 8 'tickers': 2.66, // 90 requests a minute = 1.5 requests per second => ( 1000 / rateLimit ) / 1.5 = 2.666666666 'ticker/{symbol}': 2.66, 'tickers/hist': 2.66, 'trades/{symbol}/hist': 2.66, 'book/{symbol}/{precision}': 1, // 240 requests a minute 'book/{symbol}/P0': 1, 'book/{symbol}/P1': 1, 'book/{symbol}/P2': 1, 'book/{symbol}/P3': 1, 'book/{symbol}/R0': 1, 'stats1/{key}:{size}:{symbol}:{side}/{section}': 2.66, 'stats1/{key}:{size}:{symbol}:{side}/last': 2.66, 'stats1/{key}:{size}:{symbol}:{side}/hist': 2.66, 'stats1/{key}:{size}:{symbol}/{section}': 2.66, 'stats1/{key}:{size}:{symbol}/last': 2.66, 'stats1/{key}:{size}:{symbol}/hist': 2.66, 'stats1/{key}:{size}:{symbol}:long/last': 2.66, 'stats1/{key}:{size}:{symbol}:long/hist': 2.66, 'stats1/{key}:{size}:{symbol}:short/last': 2.66, 'stats1/{key}:{size}:{symbol}:short/hist': 2.66, 'candles/trade:{timeframe}:{symbol}:{period}/{section}': 2.66, 'candles/trade:{timeframe}:{symbol}/{section}': 2.66, 'candles/trade:{timeframe}:{symbol}/last': 2.66, 'candles/trade:{timeframe}:{symbol}/hist': 2.66, 'status/{type}': 2.66, 'status/deriv': 2.66, 'liquidations/hist': 80, // 3 requests a minute = 0.05 requests a second => ( 1000ms / rateLimit ) / 0.05 = 80 'rankings/{key}:{timeframe}:{symbol}/{section}': 2.66, 'rankings/{key}:{timeframe}:{symbol}/hist': 2.66, 'pulse/hist': 2.66, 'pulse/profile/{nickname}': 2.66, 'funding/stats/{symbol}/hist': 10, // ratelimit not in docs }, 'post': { 'calc/trade/avg': 2.66, 'calc/fx': 2.66, }, }, 'private': { 'post': { // 'auth/r/orders/{symbol}/new', // outdated // 'auth/r/stats/perf:{timeframe}/hist', // outdated 'auth/r/wallets': 2.66, 'auth/r/wallets/hist': 2.66, 'auth/r/orders': 2.66, 'auth/r/orders/{symbol}': 2.66, 'auth/w/order/submit': 2.66, 'auth/w/order/update': 2.66, 'auth/w/order/cancel': 2.66, 'auth/w/order/multi': 2.66, 'auth/w/order/cancel/multi': 2.66, 'auth/r/orders/{symbol}/hist': 2.66, 'auth/r/orders/hist': 2.66, 'auth/r/order/{symbol}:{id}/trades': 2.66, 'auth/r/trades/{symbol}/hist': 2.66, 'auth/r/trades/hist': 2.66, 'auth/r/ledgers/{currency}/hist': 2.66, 'auth/r/ledgers/hist': 2.66, 'auth/r/info/margin/{key}': 2.66, 'auth/r/info/margin/base': 2.66, 'auth/r/info/margin/sym_all': 2.66, 'auth/r/positions': 2.66, 'auth/w/position/claim': 2.66, 'auth/w/position/increase:': 2.66, 'auth/r/position/increase/info': 2.66, 'auth/r/positions/hist': 2.66, 'auth/r/positions/audit': 2.66, 'auth/r/positions/snap': 2.66, 'auth/w/deriv/collateral/set': 2.66, 'auth/w/deriv/collateral/limits': 2.66, 'auth/r/funding/offers': 2.66, 'auth/r/funding/offers/{symbol}': 2.66, 'auth/w/funding/offer/submit': 2.66, 'auth/w/funding/offer/cancel': 2.66, 'auth/w/funding/offer/cancel/all': 2.66, 'auth/w/funding/close': 2.66, 'auth/w/funding/auto': 2.66, 'auth/w/funding/keep': 2.66, 'auth/r/funding/offers/{symbol}/hist': 2.66, 'auth/r/funding/offers/hist': 2.66, 'auth/r/funding/loans': 2.66, 'auth/r/funding/loans/hist': 2.66, 'auth/r/funding/loans/{symbol}': 2.66, 'auth/r/funding/loans/{symbol}/hist': 2.66, 'auth/r/funding/credits': 2.66, 'auth/r/funding/credits/hist': 2.66, 'auth/r/funding/credits/{symbol}': 2.66, 'auth/r/funding/credits/{symbol}/hist': 2.66, 'auth/r/funding/trades/{symbol}/hist': 2.66, 'auth/r/funding/trades/hist': 2.66, 'auth/r/info/funding/{key}': 2.66, 'auth/r/info/user': 2.66, 'auth/r/summary': 2.66, 'auth/r/logins/hist': 2.66, 'auth/r/permissions': 2.66, 'auth/w/token': 2.66, 'auth/r/audit/hist': 2.66, 'auth/w/transfer': 2.66, // ratelimit not in docs... 'auth/w/deposit/address': 24, // 10 requests a minute = 0.166 requests per second => ( 1000ms / rateLimit ) / 0.166 = 24 'auth/w/deposit/invoice': 24, // ratelimit not in docs 'auth/w/withdraw': 24, // ratelimit not in docs 'auth/r/movements/{currency}/hist': 2.66, 'auth/r/movements/hist': 2.66, 'auth/r/alerts': 5.33, // 45 requests a minute = 0.75 requests per second => ( 1000ms / rateLimit ) / 0.75 => 5.33 'auth/w/alert/set': 2.66, 'auth/w/alert/price:{symbol}:{price}/del': 2.66, 'auth/w/alert/{type}:{symbol}:{price}/del': 2.66, 'auth/calc/order/avail': 2.66, 'auth/w/settings/set': 2.66, 'auth/r/settings': 2.66, 'auth/w/settings/del': 2.66, 'auth/r/pulse/hist': 2.66, 'auth/w/pulse/add': 16, // 15 requests a minute = 0.25 requests per second => ( 1000ms / rateLimit ) / 0.25 => 16 'auth/w/pulse/del': 2.66, }, }, }, 'fees': { 'trading': { 'feeSide': 'get', 'percentage': true, 'tierBased': true, 'maker': this.parseNumber ('0.001'), 'taker': this.parseNumber ('0.002'), 'tiers': { 'taker': [ [ this.parseNumber ('0'), this.parseNumber ('0.002') ], [ this.parseNumber ('500000'), this.parseNumber ('0.002') ], [ this.parseNumber ('1000000'), this.parseNumber ('0.002') ], [ this.parseNumber ('2500000'), this.parseNumber ('0.002') ], [ this.parseNumber ('5000000'), this.parseNumber ('0.002') ], [ this.parseNumber ('7500000'), this.parseNumber ('0.002') ], [ this.parseNumber ('10000000'), this.parseNumber ('0.0018') ], [ this.parseNumber ('15000000'), this.parseNumber ('0.0016') ], [ this.parseNumber ('20000000'), this.parseNumber ('0.0014') ], [ this.parseNumber ('25000000'), this.parseNumber ('0.0012') ], [ this.parseNumber ('30000000'), this.parseNumber ('0.001') ], ], 'maker': [ [ this.parseNumber ('0'), this.parseNumber ('0.001') ], [ this.parseNumber ('500000'), this.parseNumber ('0.0008') ], [ this.parseNumber ('1000000'), this.parseNumber ('0.0006') ], [ this.parseNumber ('2500000'), this.parseNumber ('0.0004') ], [ this.parseNumber ('5000000'), this.parseNumber ('0.0002') ], [ this.parseNumber ('7500000'), this.parseNumber ('0') ], [ this.parseNumber ('10000000'), this.parseNumber ('0') ], [ this.parseNumber ('15000000'), this.parseNumber ('0') ], [ this.parseNumber ('20000000'), this.parseNumber ('0') ], [ this.parseNumber ('25000000'), this.parseNumber ('0') ], [ this.parseNumber ('30000000'), this.parseNumber ('0') ], ], }, }, 'funding': { 'withdraw': {}, }, }, 'precisionMode': SIGNIFICANT_DIGITS, 'options': { 'precision': 'R0', // P0, P1, P2, P3, P4, R0 // convert 'EXCHANGE MARKET' to lowercase 'market' // convert 'EXCHANGE LIMIT' to lowercase 'limit' // everything else remains uppercase 'exchangeTypes': { // 'MARKET': undefined, 'EXCHANGE MARKET': 'market', // 'LIMIT': undefined, 'EXCHANGE LIMIT': 'limit', // 'STOP': undefined, 'EXCHANGE STOP': 'market', // 'TRAILING STOP': undefined, // 'EXCHANGE TRAILING STOP': undefined, // 'FOK': undefined, 'EXCHANGE FOK': 'limit', // 'STOP LIMIT': undefined, 'EXCHANGE STOP LIMIT': 'limit', // 'IOC': undefined, 'EXCHANGE IOC': 'limit', }, // convert 'market' to 'EXCHANGE MARKET' // convert 'limit' 'EXCHANGE LIMIT' // everything else remains as is 'orderTypes': { 'market': 'EXCHANGE MARKET', 'limit': 'EXCHANGE LIMIT', }, 'fiat': { 'USD': 'USD', 'EUR': 'EUR', 'JPY': 'JPY', 'GBP': 'GBP', 'CHN': 'CHN', }, // actually the correct names unlike the v1 // we don't want to extend this with accountsByType in v1 'v2AccountsByType': { 'spot': 'exchange', 'exchange': 'exchange', 'funding': 'funding', 'margin': 'margin', 'derivatives': 'margin', 'future': 'margin', }, }, 'exceptions': { 'exact': { '10001': PermissionDenied, // api_key: permission invalid (#10001) '10020': BadRequest, '10100': AuthenticationError, '10114': InvalidNonce, '20060': OnMaintenance, // {"code":503,"error":"temporarily_unavailable","error_description":"Sorry, the service is temporarily unavailable. See https://www.bitfinex.com/ for more info."} 'temporarily_unavailable': ExchangeNotAvailable, }, 'broad': { 'address': InvalidAddress, 'available balance is only': InsufficientFunds, 'not enough exchange balance': InsufficientFunds, 'Order not found': OrderNotFound, 'symbol: invalid': BadSymbol, 'Invalid order': InvalidOrder, }, }, 'commonCurrencies': { 'UST': 'USDT', 'EUTF0': 'EURT', 'USTF0': 'USDT', 'ALG': 'ALGO', // https://github.com/ccxt/ccxt/issues/6034 'AMP': 'AMPL', 'ATO': 'ATOM', // https://github.com/ccxt/ccxt/issues/5118 'BCHABC': 'XEC', 'BCHN': 'BCH', 'DAT': 'DATA', 'DOG': 'MDOGE', 'DSH': 'DASH', 'EDO': 'PNT', 'EUS': 'EURS', 'EUT': 'EURT', 'IDX': 'ID', 'IOT': 'IOTA', 'IQX': 'IQ', 'LUNA': 'LUNC', 'LUNA2': 'LUNA', 'MNA': 'MANA', 'ORS': 'ORS Group', // conflict with Origin Sport #3230 'PAS': 'PASS', 'QSH': 'QASH', 'QTM': 'QTUM', 'RBT': 'RBTC', 'SNG': 'SNGLS', 'STJ': 'STORJ', 'TERRAUST': 'USTC', 'TSD': 'TUSD', 'YGG': 'YEED', // conflict with Yield Guild Games 'YYW': 'YOYOW', 'UDC': 'USDC', 'VSY': 'VSYS', 'WAX': 'WAXP', 'XCH': 'XCHF', 'ZBT': 'ZB', }, }); } isFiat (code) { return (code in this.options['fiat']); } getCurrencyId (code) { return 'f' + code; } getCurrencyName (code) { // temporary fix for transpiler recognition, even though this is in parent class if (code in this.options['currencyNames']) { return this.options['currencyNames'][code]; } throw new NotSupported (this.id + ' ' + code + ' not supported for withdrawal'); } amountToPrecision (symbol, amount) { // https://docs.bitfinex.com/docs/introduction#amount-precision // The amount field allows up to 8 decimals. // Anything exceeding this will be rounded to the 8th decimal. return this.decimalToPrecision (amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); } priceToPrecision (symbol, price) { price = this.decimalToPrecision (price, ROUND, this.markets[symbol]['precision']['price'], this.precisionMode); // https://docs.bitfinex.com/docs/introduction#price-precision // The precision level of all trading prices is based on significant figures. // All pairs on Bitfinex use up to 5 significant digits and up to 8 decimals (e.g. 1.2345, 123.45, 1234.5, 0.00012345). // Prices submit with a precision larger than 5 will be cut by the API. return this.decimalToPrecision (price, TRUNCATE, 8, DECIMAL_PLACES); } async fetchStatus (params = {}) { /** * @method * @name bitfinex2#fetchStatus * @description the latest known information on the availability of the exchange API * @param {object} params extra parameters specific to the bitfinex2 api endpoint * @returns {object} a [status structure]{@link https://docs.ccxt.com/en/latest/manual.html#exchange-status-structure} */ // // [1] // operative // [0] // maintenance // const response = await this.publicGetPlatformStatus (params); const statusRaw = this.safeString (response, 0); return { 'status': this.safeString ({ '0': 'maintenance', '1': 'ok' }, statusRaw, statusRaw), 'updated': undefined, 'eta': undefined, 'url': undefined, 'info': response, }; } async fetchMarkets (params = {}) { /** * @method * @name bitfinex2#fetchMarkets * @description retrieves data on all markets for bitfinex2 * @param {object} params extra parameters specific to the exchange api endpoint * @returns {[object]} an array of objects representing market data */ let spotMarketsInfo = await this.publicGetConfPubInfoPair (params); let futuresMarketsInfo = await this.publicGetConfPubInfoPairFutures (params); spotMarketsInfo = this.safeValue (spotMarketsInfo, 0, []); futuresMarketsInfo = this.safeValue (futuresMarketsInfo, 0, []); const markets = this.arrayConcat (spotMarketsInfo, futuresMarketsInfo); let marginIds = await this.publicGetConfPubListPairMargin (params); marginIds = this.safeValue (marginIds, 0, []); // // [ // "1INCH:USD", // [ // null, // null, // null, // "2.0", // "100000.0", // null, // null, // null, // null, // null, // null, // null // ] // ] // const result = []; for (let i = 0; i < markets.length; i++) { const pair = markets[i]; const id = this.safeStringUpper (pair, 0); const market = this.safeValue (pair, 1, {}); let spot = true; if (id.indexOf ('F0') >= 0) { spot = false; } const swap = !spot; let baseId = undefined; let quoteId = undefined; if (id.indexOf (':') >= 0) { const parts = id.split (':'); baseId = parts[0]; quoteId = parts[1]; } else { baseId = id.slice (0, 3); quoteId = id.slice (3, 6); } let base = this.safeCurrencyCode (baseId); let quote = this.safeCurrencyCode (quoteId); const splitBase = base.split ('F0'); const splitQuote = quote.split ('F0'); base = this.safeString (splitBase, 0); quote = this.safeString (splitQuote, 0); let symbol = base + '/' + quote; baseId = this.getCurrencyId (baseId); quoteId = this.getCurrencyId (quoteId); let settle = undefined; if (swap) { settle = quote; symbol = symbol + ':' + settle; } const minOrderSizeString = this.safeString (market, 3); const maxOrderSizeString = this.safeString (market, 4); let margin = false; if (this.inArray (id, marginIds)) { margin = true; } result.push ({ 'id': 't' + id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': quoteId, 'type': spot ? 'spot' : 'swap', 'spot': spot, 'margin': margin, 'swap': swap, 'future': false, 'option': false, 'active': true, 'contract': swap, 'linear': swap ? true : undefined, 'inverse': swap ? false : undefined, 'contractSize': swap ? this.parseNumber ('1') : undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': parseInt ('8'), // https://github.com/ccxt/ccxt/issues/7310 'price': parseInt ('5'), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': this.parseNumber (minOrderSizeString), 'max': this.parseNumber (maxOrderSizeString), }, 'price': { 'min': this.parseNumber ('1e-8'), 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'info': market, }); } return result; } async fetchCurrencies (params = {}) { /** * @method * @name bitfinex2#fetchCurrencies * @description fetches all available currencies on an exchange * @param {object} params extra parameters specific to the bitfinex2 api endpoint * @returns {object} an associative dictionary of currencies */ const labels = [ 'pub:list:currency', 'pub:map:currency:sym', // maps symbols to their API symbols, BAB > BCH 'pub:map:currency:label', // verbose friendly names, BNT > Bancor 'pub:map:currency:unit', // maps symbols to unit of measure where applicable 'pub:map:currency:undl', // maps derivatives symbols to their underlying currency 'pub:map:currency:pool', // maps symbols to underlying network/protocol they operate on 'pub:map:currency:explorer', // maps symbols to their recognised block explorer URLs 'pub:map:currency:tx:fee', // maps currencies to their withdrawal fees https://github.com/ccxt/ccxt/issues/7745, 'pub:map:tx:method', // maps withdrawal/deposit methods to their API symbols ]; const config = labels.join (','); const request = { 'config': config, }; const response = await this.publicGetConfConfig (this.extend (request, params)); // // [ // // a list of symbols // ["AAA","ABS","ADA"], // // // sym // // maps symbols to their API symbols, BAB > BCH // [ // [ 'BAB', 'BCH' ], // [ 'CNHT', 'CNHt' ], // [ 'DSH', 'DASH' ], // [ 'IOT', 'IOTA' ], // [ 'LES', 'LEO-EOS' ], // [ 'LET', 'LEO-ERC20' ], // [ 'STJ', 'STORJ' ], // [ 'TSD', 'TUSD' ], // [ 'UDC', 'USDC' ], // [ 'USK', 'USDK' ], // [ 'UST', 'USDt' ], // [ 'USTF0', 'USDt0' ], // [ 'XCH', 'XCHF' ], // [ 'YYW', 'YOYOW' ], // // ... // ], // // label // // verbose friendly names, BNT > Bancor // [ // [ 'BAB', 'Bitcoin Cash' ], // [ 'BCH', 'Bitcoin Cash' ], // [ 'LEO', 'Unus Sed LEO' ], // [ 'LES', 'Unus Sed LEO (EOS)' ], // [ 'LET', 'Unus Sed LEO (ERC20)' ], // // ... // ], // // unit // // maps symbols to unit of measure where applicable // [ // [ 'IOT', 'Mi|MegaIOTA' ], // ], // // undl // // maps derivatives symbols to their underlying currency // [ // [ 'USTF0', 'UST' ], // [ 'BTCF0', 'BTC' ], // [ 'ETHF0', 'ETH' ], // ], // // pool // // maps symbols to underlying network/protocol they operate on // [ // [ 'SAN', 'ETH' ], [ 'OMG', 'ETH' ], [ 'AVT', 'ETH' ], [ 'EDO', 'ETH' ], // [ 'ESS', 'ETH' ], [ 'ATD', 'EOS' ], [ 'ADD', 'EOS' ], [ 'MTO', 'EOS' ], // [ 'PNK', 'ETH' ], [ 'BAB', 'BCH' ], [ 'WLO', 'XLM' ], [ 'VLD', 'ETH' ], // [ 'BTT', 'TRX' ], [ 'IMP', 'ETH' ], [ 'SCR', 'ETH' ], [ 'GNO', 'ETH' ], // // ... // ], // // explorer // // maps symbols to their recognised block explorer URLs // [ // [ // 'AIO', // [ // "https://mainnet.aion.network", // "https://mainnet.aion.network/#/account/VAL", // "https://mainnet.aion.network/#/transaction/VAL" // ] // ], // // ... // ], // // fee // // maps currencies to their withdrawal fees // [ // ["AAA",[0,0]], // ["ABS",[0,131.3]], // ["ADA",[0,0.3]], // ], // ] // const indexed = { 'sym': this.indexBy (this.safeValue (response, 1, []), 0), 'label': this.indexBy (this.safeValue (response, 2, []), 0), 'unit': this.indexBy (this.safeValue (response, 3, []), 0), 'undl': this.indexBy (this.safeValue (response, 4, []), 0), 'pool': this.indexBy (this.safeValue (response, 5, []), 0), 'explorer': this.indexBy (this.safeValue (response, 6, []), 0), 'fees': this.indexBy (this.safeValue (response, 7, []), 0), }; const ids = this.safeValue (response, 0, []); const result = {}; for (let i = 0; i < ids.length; i++) { const id = ids[i]; if (id.indexOf ('F0') >= 0) { // we get a lot of F0 currencies, skip those continue; } const code = this.safeCurrencyCode (id); const label = this.safeValue (indexed['label'], id, []); const name = this.safeString (label, 1); const pool = this.safeValue (indexed['pool'], id, []); const type = this.safeString (pool, 1); const feeValues = this.safeValue (indexed['fees'], id, []); const fees = this.safeValue (feeValues, 1, []); const fee = this.safeNumber (fees, 1); const undl = this.safeValue (indexed['undl'], id, []); const precision = '8'; // default precision, todo: fix "magic constants" const fid = 'f' + id; result[code] = { 'id': fid, 'uppercaseId': id, 'code': code, 'info': [ id, label, pool, feeValues, undl ], 'type': type, 'name': name, 'active': true, 'deposit': undefined, 'withdraw': undefined, 'fee': fee, 'precision': parseInt (precision), 'limits': { 'amount': { 'min': this.parseNumber (this.parsePrecision (precision)), 'max': undefined, }, 'withdraw': { 'min': fee, 'max': undefined, }, }, }; const networks = {}; const currencyNetworks = this.safeValue (response, 8, []); const cleanId = id.replace ('F0', ''); for (let j = 0; j < currencyNetworks.length; j++) { const pair = currencyNetworks[j]; const networkId = this.safeString (pair, 0); const currencyId = this.safeString (this.safeValue (pair, 1, []), 0); if (currencyId === cleanId) { const network = this.safeNetwork (networkId); networks[network] = { 'info': networkId, 'id': networkId.toLowerCase (), 'network': networkId, 'active': undefined, 'deposit': undefined, 'withdraw': undefined, 'fee': undefined, 'precision': undefined, 'limits': { 'withdraw': { 'min': undefined, 'max': undefined, }, }, }; } } const keysNetworks = Object.keys (networks); const networksLength = keysNetworks.length; if (networksLength > 0) { result[code]['networks'] = networks; } } return result; } safeNetwork (networkId) { const networksById = { 'BITCOIN': 'BTC', 'LITECOIN': 'LTC', 'ETHEREUM': 'ERC20', 'TETHERUSE': 'ERC20', 'TETHERUSO': 'OMNI', 'TETHERUSL': 'LIQUID', 'TETHERUSX': 'TRC20', 'TETHERUSS': 'EOS', 'TETHERUSDTAVAX': 'AVAX', 'TETHERUSDTSOL': 'SOL', 'TETHERUSDTALG': 'ALGO', 'TETHERUSDTBCH': 'BCH', 'TETHERUSDTKSM': 'KSM', 'TETHERUSDTDVF': 'DVF', 'TETHERUSDTOMG': 'OMG', }; return this.safeString (networksById, networkId, networkId); } async fetchBalance (params = {}) { /** * @method * @name bitfinex2#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 bitfinex2 api endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure} */ // this api call does not return the 'used' amount - use the v1 version instead (which also returns zero balances) // there is a difference between this and the v1 api, namely trading wallet is called margin in v2 await this.loadMarkets (); const accountsByType = this.safeValue (this.options, 'v2AccountsByType', {}); const requestedType = this.safeString (params, 'type', 'exchange'); const accountType = this.safeString (accountsByType, requestedType, requestedType); if (accountType === undefined) { const keys = Object.keys (accountsByType); throw new ExchangeError (this.id + ' fetchBalance() type parameter must be one of ' + keys.join (', ')); } const isDerivative = requestedType === 'derivatives'; const query = this.omit (params, 'type'); const response = await this.privatePostAuthRWallets (query); const result = { 'info': response }; for (let i = 0; i < response.length; i++) { const balance = response[i]; const type = this.safeString (balance, 0); const currencyId = this.safeStringLower (balance, 1, ''); const start = currencyId.length - 2; const isDerivativeCode = currencyId.slice (start) === 'f0'; // this will only filter the derivative codes if the requestedType is 'derivatives' const derivativeCondition = (!isDerivative || isDerivativeCode); if ((accountType === type) && derivativeCondition) { const code = this.safeCurrencyCode (currencyId); const account = this.account (); account['total'] = this.safeString (balance, 2); account['free'] = this.safeString (balance, 4); result[code] = account; } } return this.safeBalance (result); } async transfer (code, amount, fromAccount, toAccount, params = {}) { /** * @method * @name bitfinex2#transfer * @description transfer currency internally between wallets on the same account * @param {string} code unified currency code * @param {float} amount amount to transfer * @param {string} fromAccount account to transfer from * @param {string} toAccount account to transfer to * @param {object} params extra parameters specific to the bitfinex2 api endpoint * @returns {object} a [transfer structure]{@link https://docs.ccxt.com/en/latest/manual.html#transfer-structure} */ // transferring between derivatives wallet and regular wallet is not documented in their API // however we support it in CCXT (from just looking at web inspector) await this.loadMarkets (); const accountsByType = this.safeValue (this.options, 'v2AccountsByType', {}); const fromId = this.safeString (accountsByType, fromAccount); if (fromId === undefined) { const keys = Object.keys (accountsByType); throw new ArgumentsRequired (this.id + ' transfer() fromAccount must be one of ' + keys.join (', ')); } const toId = this.safeString (accountsByType, toAccount); if (toId === undefined) { const keys = Object.keys (accountsByType); throw new ArgumentsRequired (this.id + ' transfer() toAccount must be one of ' + keys.join (', ')); } const currency = this.currency (code); const fromCurrencyId = this.convertDerivativesId (currency, fromAccount); const toCurrencyId = this.convertDerivativesId (currency, toAccount); const requestedAmount = this.currencyToPrecision (code, amount); // this request is slightly different from v1 fromAccount -> from const request = { 'amount': requestedAmount, 'currency': fromCurrencyId, 'currency_to': toCurrencyId, 'from': fromId, 'to': toId, }; const response = await this.privatePostAuthWTransfer (this.extend (request, params)); // // [ // 1616451183763, // "acc_tf", // null, // null, // [ // 1616451183763, // "exchange", // "margin", // null, // "UST", // "UST", // null, // 1 // ], // null, // "SUCCESS", // "1.0 Tether USDt transfered from Exchange to Margin" // ] // const error = this.safeString (response, 0); if (error === 'error') { const message = this.safeString (response, 2, ''); // same message as in v1 this.throwExactlyMatchedException (this.exceptions['exact'], message, this.id + ' ' + message); throw new ExchangeError (this.id + ' ' + message); } return this.parseTransfer (response, currency); } parseTransfer (transfer, currency = undefined) { // // transfer // // [ // 1616451183763, // "acc_tf", // null, // null, // [ // 1616451183763, // "exchange", // "margin", // null, // "UST", // "UST", // null, // 1 // ], // null, // "SUCCESS", // "1.0 Tether USDt transfered from Exchange to Margin" // ] // const timestamp = this.safeInteger (transfer, 0); const info = this.safeValue (transfer, 4); const fromAccount = this.safeString (info, 1); const toAccount = this.safeString (info, 2); const currencyId = this.safeString (info, 5); const status = this.safeString (transfer, 6); return { 'id': undefined, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'status': this.parseTransferStatus (status), 'amount': this.safeNumber (info, 7), 'currency': this.safeCurrencyCode (currencyId, currency), 'fromAccount': fromAccount, 'toAccount': toAccount, 'info': transfer, }; } parseTransferStatus (status) { const statuses = { 'SUCCESS': 'ok', 'ERROR': 'failed', 'FAILURE': 'failed', }; return this.safeString (statuses, status, status); } convertDerivativesId (currency, type) { // there is a difference between this and the v1 api, namely trading wallet is called margin in v2 // { // id: 'fUSTF0', // code: 'USTF0', // info: [ 'USTF0', [], [], [], [ 'USTF0', 'UST' ] ], const info = this.safeValue (currency, 'info'); const transferId = this.safeString (info, 0); const underlying = this.safeValue (info, 4, []); let currencyId = undefined; if (type === 'derivatives') { currencyId = this.safeString (underlying, 0, transferId); const start = currencyId.length - 2; const isDerivativeCode = currencyId.slice (start) === 'F0'; if (!isDerivativeCode) { currencyId = currencyId + 'F0'; } } else if (type !== 'margin') { currencyId = this.safeString (underlying, 1, transferId); } else { currencyId = transferId; } return currencyId; } async fetchOrder (id, symbol = undefined, params = {}) { /** * @method * @name bitfinex2#fetchOrder * @description fetches information on an order made by the user * @param {string|undefined} symbol unified symbol of the market the order was made in * @param {object} params extra parameters specific to the bitfinex2 api endpoint * @returns {object} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure} */ throw new NotSupported (this.id + ' fetchOrder() is not supported yet. Consider using fetchOpenOrder() or fetchClosedOrder() instead.'); } async fetchOrderBook (symbol, limit = undefined, params = {}) { /** * @method * @name bitfinex2#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 bitfinex2 api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols */ await this.loadMarkets (); const precision = this.safeValue (this.options, 'precision', 'R0'); const market = this.market (symbol); const request = { 'symbol': market['id'], 'precision': precision, }; if (limit !== undefined) { request['len'] = limit; // 25 or 100 } const fullRequest = this.extend (request, params); const orderbook = await this.publicGetBookSymbolPrecision (fullRequest); const timestamp = this.milliseconds (); const result = { 'symbol': market['symbol'], 'bids': [], 'asks': [], 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'nonce': undefined, }; const priceIndex = (fullRequest['precision'] === 'R0') ? 1 : 0; for (let i = 0; i < orderbook.length; i++) { const order = orderbook[i]; const price = this.safeNumber (order, priceIndex); const signedAmount = this.safeString (order, 2); const amount = Precise.stringAbs (signedAmount); const side = Precise.stringGt (signedAmount, '0') ? 'bids' : 'asks'; result[side].push ([ price, this.parseNumber (amount) ]); } result['bids'] = this.sortBy (result['bids'], 0, true); result['asks'] = this.sortBy (result['asks'], 0); return result; } parseTicker (ticker, market = undefined) { // // on trading pairs (ex. tBTCUSD) // // [ // SYMBOL, // BID, // BID_SIZE, // ASK, // ASK_SIZE, // DAILY_CHANGE, // DAILY_CHANGE_RELATIVE, // LAST_PRICE, // VOLUME, // HIGH, // LOW // ] // // on funding currencies (ex. fUSD) // // [ // SYMBOL, // FRR, // BID, // BID_PERIOD, // BID_SIZE, // ASK, // ASK_PERIOD, // ASK_SIZE, // DAILY_CHANGE, // DAILY_CHANGE_RELATIVE, // LAST_PRICE, // VOLUME, // HIGH, // LOW, // _PLACEHOLDER, // _PLACEHOLDER, // FRR_AMOUNT_AVAILABLE // ] // const timestamp = this.milliseconds (); const symbol = this.safeSymbol (undefined, market); const length = ticker.length; const last = this.safeString (ticker, length - 4); const percentage = this.safeString (ticker, length - 5); return this.safeTicker ({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601 (timestamp), 'high': this.safeString (ticker, length - 2), 'low': this.safeString (ticker, length - 1), 'bid': this.safeString (ticker, length - 10), 'bidVolume': undefined, 'ask': this.safeString (ticker, length - 8), 'askVolume': undefined, 'vwap': undefined, 'open': undefined, 'close': last, 'last': last, 'previousClose': undefined, 'change': this.safeString (ticker, length - 6), 'percentage': Precise.stringMul (percentage, '100'), 'average': undefined, 'baseVolume': this.safeString (ticker, length - 3), 'quoteVolume': undefined, 'info': ticker, }, market); } async fetchTickers (symbols = undefined, params = {}) { /** * @method * @name bitfinex2#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param