UNPKG

ccxt

Version:

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

1,131 lines (1,129 loc) • 175 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import { ExchangeError, ArgumentsRequired, InsufficientFunds, AuthenticationError, OrderNotFound, InvalidOrder, BadRequest, InvalidNonce, BadSymbol, OnMaintenance, NotSupported, PermissionDenied, ExchangeNotAvailable, RateLimitExceeded } from './base/errors.js'; import { Precise } from './base/Precise.js'; import Exchange from './abstract/bitfinex.js'; import { SIGNIFICANT_DIGITS, DECIMAL_PLACES, TRUNCATE, ROUND } from './base/functions/number.js'; import { sha384 } from './static_dependencies/noble-hashes/sha512.js'; // --------------------------------------------------------------------------- /** * @class bitfinex * @augments Exchange */ export default class bitfinex extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'bitfinex', 'name': 'Bitfinex', 'countries': ['VG'], 'version': 'v2', 'certified': false, 'pro': true, // new metainfo interface 'has': { 'CORS': undefined, 'spot': true, 'margin': true, 'swap': true, 'future': false, 'option': false, 'addMargin': false, 'borrowCrossMargin': false, 'borrowIsolatedMargin': false, 'cancelAllOrders': true, 'cancelOrder': true, 'cancelOrders': true, 'createDepositAddress': true, 'createLimitOrder': true, 'createMarketOrder': true, 'createOrder': true, 'createPostOnlyOrder': true, 'createReduceOnlyOrder': true, 'createStopLimitOrder': true, 'createStopMarketOrder': true, 'createStopOrder': true, 'createTrailingAmountOrder': true, 'createTrailingPercentOrder': false, 'createTriggerOrder': true, 'editOrder': true, 'fetchBalance': true, 'fetchBorrowInterest': false, 'fetchBorrowRate': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchBorrowRates': false, 'fetchBorrowRatesPerSymbol': false, 'fetchClosedOrder': true, 'fetchClosedOrders': true, 'fetchCrossBorrowRate': false, 'fetchCrossBorrowRates': false, 'fetchCurrencies': true, 'fetchDepositAddress': true, 'fetchDepositAddresses': false, 'fetchDepositAddressesByNetwork': false, 'fetchDepositsWithdrawals': true, 'fetchFundingHistory': false, 'fetchFundingRate': 'emulated', 'fetchFundingRateHistory': true, 'fetchFundingRates': true, 'fetchIndexOHLCV': false, 'fetchIsolatedBorrowRate': false, 'fetchIsolatedBorrowRates': false, 'fetchLedger': true, 'fetchLeverage': false, 'fetchLeverageTiers': false, 'fetchLiquidations': true, 'fetchMarginMode': false, 'fetchMarketLeverageTiers': false, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenInterest': true, 'fetchOpenInterestHistory': true, 'fetchOpenInterests': true, 'fetchOpenOrder': true, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrderBooks': false, 'fetchOrderTrades': true, 'fetchPosition': false, 'fetchPositionMode': false, 'fetchPositions': true, 'fetchPremiumIndexOHLCV': false, 'fetchStatus': true, 'fetchTickers': true, 'fetchTime': false, 'fetchTradingFee': false, 'fetchTradingFees': true, 'fetchTransactionFees': undefined, 'fetchTransactions': 'emulated', 'reduceMargin': false, 'repayCrossMargin': false, 'repayIsolatedMargin': false, 'setLeverage': false, 'setMargin': true, 'setMarginMode': false, 'setPositionMode': false, 'signIn': false, 'transfer': 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://github.com/user-attachments/assets/4a8e947f-ab46-481a-a8ae-8b20e9b03178', '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.7, 'conf/pub:{action}:{object}': 2.7, 'conf/pub:{action}:{object}:{detail}': 2.7, 'conf/pub:map:{object}': 2.7, 'conf/pub:map:{object}:{detail}': 2.7, 'conf/pub:map:currency:{detail}': 2.7, 'conf/pub:map:currency:sym': 2.7, 'conf/pub:map:currency:label': 2.7, 'conf/pub:map:currency:unit': 2.7, 'conf/pub:map:currency:undl': 2.7, 'conf/pub:map:currency:pool': 2.7, 'conf/pub:map:currency:explorer': 2.7, 'conf/pub:map:currency:tx:fee': 2.7, 'conf/pub:map:tx:method': 2.7, 'conf/pub:list:{object}': 2.7, 'conf/pub:list:{object}:{detail}': 2.7, 'conf/pub:list:currency': 2.7, 'conf/pub:list:pair:exchange': 2.7, 'conf/pub:list:pair:margin': 2.7, 'conf/pub:list:pair:futures': 2.7, 'conf/pub:list:competitions': 2.7, 'conf/pub:info:{object}': 2.7, 'conf/pub:info:{object}:{detail}': 2.7, 'conf/pub:info:pair': 2.7, 'conf/pub:info:pair:futures': 2.7, 'conf/pub:info:tx:status': 2.7, 'conf/pub:fees': 2.7, 'platform/status': 8, 'tickers': 2.7, 'ticker/{symbol}': 2.7, 'tickers/hist': 2.7, 'trades/{symbol}/hist': 2.7, 'book/{symbol}/{precision}': 1, '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.7, 'stats1/{key}:{size}:{symbol}:{side}/last': 2.7, 'stats1/{key}:{size}:{symbol}:{side}/hist': 2.7, 'stats1/{key}:{size}:{symbol}/{section}': 2.7, 'stats1/{key}:{size}:{symbol}/last': 2.7, 'stats1/{key}:{size}:{symbol}/hist': 2.7, 'stats1/{key}:{size}:{symbol}:long/last': 2.7, 'stats1/{key}:{size}:{symbol}:long/hist': 2.7, 'stats1/{key}:{size}:{symbol}:short/last': 2.7, 'stats1/{key}:{size}:{symbol}:short/hist': 2.7, 'candles/trade:{timeframe}:{symbol}:{period}/{section}': 2.7, 'candles/trade:{timeframe}:{symbol}/{section}': 2.7, 'candles/trade:{timeframe}:{symbol}/last': 2.7, 'candles/trade:{timeframe}:{symbol}/hist': 2.7, 'status/{type}': 2.7, 'status/deriv': 2.7, 'status/deriv/{symbol}/hist': 2.7, 'liquidations/hist': 80, 'rankings/{key}:{timeframe}:{symbol}/{section}': 2.7, 'rankings/{key}:{timeframe}:{symbol}/hist': 2.7, 'pulse/hist': 2.7, 'pulse/profile/{nickname}': 2.7, 'funding/stats/{symbol}/hist': 10, 'ext/vasps': 1, }, 'post': { 'calc/trade/avg': 2.7, 'calc/fx': 2.7, }, }, 'private': { 'post': { // 'auth/r/orders/{symbol}/new', // outdated // 'auth/r/stats/perf:{timeframe}/hist', // outdated 'auth/r/wallets': 2.7, 'auth/r/wallets/hist': 2.7, 'auth/r/orders': 2.7, 'auth/r/orders/{symbol}': 2.7, 'auth/w/order/submit': 2.7, 'auth/w/order/update': 2.7, 'auth/w/order/cancel': 2.7, 'auth/w/order/multi': 2.7, 'auth/w/order/cancel/multi': 2.7, 'auth/r/orders/{symbol}/hist': 2.7, 'auth/r/orders/hist': 2.7, 'auth/r/order/{symbol}:{id}/trades': 2.7, 'auth/r/trades/{symbol}/hist': 2.7, 'auth/r/trades/hist': 2.7, 'auth/r/ledgers/{currency}/hist': 2.7, 'auth/r/ledgers/hist': 2.7, 'auth/r/info/margin/{key}': 2.7, 'auth/r/info/margin/base': 2.7, 'auth/r/info/margin/sym_all': 2.7, 'auth/r/positions': 2.7, 'auth/w/position/claim': 2.7, 'auth/w/position/increase:': 2.7, 'auth/r/position/increase/info': 2.7, 'auth/r/positions/hist': 2.7, 'auth/r/positions/audit': 2.7, 'auth/r/positions/snap': 2.7, 'auth/w/deriv/collateral/set': 2.7, 'auth/w/deriv/collateral/limits': 2.7, 'auth/r/funding/offers': 2.7, 'auth/r/funding/offers/{symbol}': 2.7, 'auth/w/funding/offer/submit': 2.7, 'auth/w/funding/offer/cancel': 2.7, 'auth/w/funding/offer/cancel/all': 2.7, 'auth/w/funding/close': 2.7, 'auth/w/funding/auto': 2.7, 'auth/w/funding/keep': 2.7, 'auth/r/funding/offers/{symbol}/hist': 2.7, 'auth/r/funding/offers/hist': 2.7, 'auth/r/funding/loans': 2.7, 'auth/r/funding/loans/hist': 2.7, 'auth/r/funding/loans/{symbol}': 2.7, 'auth/r/funding/loans/{symbol}/hist': 2.7, 'auth/r/funding/credits': 2.7, 'auth/r/funding/credits/hist': 2.7, 'auth/r/funding/credits/{symbol}': 2.7, 'auth/r/funding/credits/{symbol}/hist': 2.7, 'auth/r/funding/trades/{symbol}/hist': 2.7, 'auth/r/funding/trades/hist': 2.7, 'auth/r/info/funding/{key}': 2.7, 'auth/r/info/user': 2.7, 'auth/r/summary': 2.7, 'auth/r/logins/hist': 2.7, 'auth/r/permissions': 2.7, 'auth/w/token': 2.7, 'auth/r/audit/hist': 2.7, 'auth/w/transfer': 2.7, 'auth/w/deposit/address': 24, 'auth/w/deposit/invoice': 24, 'auth/w/withdraw': 24, 'auth/r/movements/{currency}/hist': 2.7, 'auth/r/movements/hist': 2.7, 'auth/r/alerts': 5.34, 'auth/w/alert/set': 2.7, 'auth/w/alert/price:{symbol}:{price}/del': 2.7, 'auth/w/alert/{type}:{symbol}:{price}/del': 2.7, 'auth/calc/order/avail': 2.7, 'auth/w/settings/set': 2.7, 'auth/r/settings': 2.7, 'auth/w/settings/del': 2.7, 'auth/r/pulse/hist': 2.7, 'auth/w/pulse/add': 16, 'auth/w/pulse/del': 2.7, }, }, }, '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', // convert 'EXCHANGE MARKET' to lowercase 'market' // convert 'EXCHANGE LIMIT' to lowercase 'limit' // everything else remains uppercase 'exchangeTypes': { 'MARKET': 'market', 'EXCHANGE MARKET': 'market', 'LIMIT': 'limit', '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', 'swap': 'margin', }, 'withdraw': { 'includeFee': false, }, 'networks': { 'BTC': 'BITCOIN', 'LTC': 'LITECOIN', 'ERC20': 'ETHEREUM', 'OMNI': 'TETHERUSO', 'LIQUID': 'TETHERUSL', 'TRC20': 'TETHERUSX', 'EOS': 'TETHERUSS', 'AVAX': 'TETHERUSDTAVAX', 'SOL': 'TETHERUSDTSOL', 'ALGO': 'TETHERUSDTALG', 'BCH': 'TETHERUSDTBCH', 'KSM': 'TETHERUSDTKSM', 'DVF': 'TETHERUSDTDVF', 'OMG': 'TETHERUSDTOMG', }, 'networksById': { 'TETHERUSE': 'ERC20', }, }, 'features': { 'default': { 'sandbox': false, 'createOrder': { 'marginMode': true, 'triggerPrice': true, 'triggerPriceType': undefined, 'triggerDirection': false, 'stopLossPrice': true, 'takeProfitPrice': true, 'attachedStopLossTakeProfit': undefined, 'timeInForce': { 'IOC': true, 'FOK': true, 'PO': true, 'GTD': false, }, 'hedged': false, 'trailing': true, 'leverage': true, 'marketBuyRequiresPrice': false, 'marketBuyByCost': true, 'selfTradePrevention': false, 'iceberg': false, }, 'createOrders': { 'max': 75, }, 'fetchMyTrades': { 'marginMode': false, 'limit': 2500, 'daysBack': undefined, 'untilDays': 100000, 'symbolRequired': false, }, 'fetchOrder': { 'marginMode': false, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOpenOrders': { 'marginMode': false, 'limit': undefined, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOrders': undefined, 'fetchClosedOrders': { 'marginMode': false, 'limit': undefined, 'daysBack': undefined, 'daysBackCanceled': undefined, 'untilDays': 100000, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOHLCV': { 'limit': 10000, }, }, 'spot': { 'extends': 'default', }, 'swap': { 'linear': { 'extends': 'default', }, 'inverse': undefined, }, 'future': { 'linear': undefined, 'inverse': undefined, }, }, 'exceptions': { 'exact': { '11010': RateLimitExceeded, '10001': PermissionDenied, '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': { 'available balance is only': InsufficientFunds, 'not enough exchange balance': InsufficientFunds, 'Order not found': OrderNotFound, 'symbol: invalid': BadSymbol, }, }, 'commonCurrencies': { 'UST': 'USDT', 'EUTF0': 'EURT', 'USTF0': 'USDT', 'ALG': 'ALGO', 'AMP': 'AMPL', 'ATO': 'ATOM', 'BCHABC': 'XEC', 'BCHN': 'BCH', 'DAT': 'DATA', 'DOG': 'MDOGE', 'DSH': 'DASH', 'EDO': 'PNT', 'EUS': 'EURS', 'EUT': 'EURT', 'HTX': 'HT', 'IDX': 'ID', 'IOT': 'IOTA', 'IQX': 'IQ', 'LUNA': 'LUNC', 'LUNA2': 'LUNA', 'MNA': 'MANA', 'ORS': 'ORS Group', 'PAS': 'PASS', 'QSH': 'QASH', 'QTM': 'QTUM', 'RBT': 'RBTC', 'SNG': 'SNGLS', 'STJ': 'STORJ', 'TERRAUST': 'USTC', 'TSD': 'TUSD', 'YGG': 'YEED', '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. symbol = this.safeSymbol(symbol); return this.decimalToPrecision(amount, TRUNCATE, this.markets[symbol]['precision']['amount'], DECIMAL_PLACES); } priceToPrecision(symbol, price) { symbol = this.safeSymbol(symbol); 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); } /** * @method * @name bitfinex#fetchStatus * @description the latest known information on the availability of the exchange API * @see https://docs.bitfinex.com/reference/rest-public-platform-status * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [status structure]{@link https://docs.ccxt.com/#/?id=exchange-status-structure} */ async fetchStatus(params = {}) { // // [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, }; } /** * @method * @name bitfinex#fetchMarkets * @description retrieves data on all markets for bitfinex * @see https://docs.bitfinex.com/reference/rest-public-conf * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} an array of objects representing market data */ async fetchMarkets(params = {}) { const spotMarketsInfoPromise = this.publicGetConfPubInfoPair(params); const futuresMarketsInfoPromise = this.publicGetConfPubInfoPairFutures(params); const marginIdsPromise = this.publicGetConfPubListPairMargin(params); let [spotMarketsInfo, futuresMarketsInfo, marginIds] = await Promise.all([spotMarketsInfoPromise, futuresMarketsInfoPromise, marginIdsPromise]); spotMarketsInfo = this.safeList(spotMarketsInfo, 0, []); futuresMarketsInfo = this.safeList(futuresMarketsInfo, 0, []); const markets = this.arrayConcat(spotMarketsInfo, futuresMarketsInfo); 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; let settleId = undefined; if (swap) { settle = quote; settleId = quote; symbol = symbol + ':' + settle; } const minOrderSizeString = this.safeString(market, 3); const maxOrderSizeString = this.safeString(market, 4); let margin = false; if (spot && this.inArray(id, marginIds)) { margin = true; } result.push({ 'id': 't' + id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': settle, 'baseId': baseId, 'quoteId': quoteId, 'settleId': settleId, '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'), '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, }, }, 'created': undefined, 'info': market, }); } return result; } /** * @method * @name bitfinex#fetchCurrencies * @description fetches all available currencies on an exchange * @see https://docs.bitfinex.com/reference/rest-public-conf * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} an associative dictionary of currencies */ async fetchCurrencies(params = {}) { const labels = [ 'pub:list:currency', 'pub:map:currency:sym', 'pub:map:currency:label', 'pub:map:currency:unit', 'pub:map:currency:undl', 'pub:map:currency:pool', 'pub:map:currency:explorer', 'pub:map:currency:tx:fee', 'pub:map:tx:method', 'pub:info:tx:status', // maps withdrawal/deposit statuses, coins: 1 = enabled, 0 = maintenance ]; 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]], // ], // // deposit/withdrawal data // [ // ["BITCOIN", 1, 1, null, null, null, null, 0, 0, null, null, 3], // ... // ] // ] // const indexed = { 'sym': this.indexBy(this.safeList(response, 1, []), 0), 'label': this.indexBy(this.safeList(response, 2, []), 0), 'unit': this.indexBy(this.safeList(response, 3, []), 0), 'undl': this.indexBy(this.safeList(response, 4, []), 0), 'pool': this.indexBy(this.safeList(response, 5, []), 0), 'explorer': this.indexBy(this.safeList(response, 6, []), 0), 'fees': this.indexBy(this.safeList(response, 7, []), 0), 'networks': this.safeList(response, 8, []), 'statuses': this.indexBy(this.safeList(response, 9, []), 0), }; const indexedNetworks = {}; for (let i = 0; i < indexed['networks'].length; i++) { const networkObj = indexed['networks'][i]; const networkId = this.safeString(networkObj, 0); const valuesList = this.safeList(networkObj, 1); const networkName = this.safeString(valuesList, 0); // for GOlang transpiler, do with "safe" method const networksList = this.safeList(indexedNetworks, networkName, []); networksList.push(networkId); indexedNetworks[networkName] = networksList; } const ids = this.safeList(response, 0, []); const result = {}; for (let i = 0; i < ids.length; i++) { const id = ids[i]; if (id.endsWith('F0')) { // we get a lot of F0 currencies, skip those continue; } const code = this.safeCurrencyCode(id); const label = this.safeList(indexed['label'], id, []); const name = this.safeString(label, 1); const pool = this.safeList(indexed['pool'], id, []); const rawType = this.safeString(pool, 1); const isCryptoCoin = (rawType !== undefined) || (id in indexed['explorer']); // "hacky" solution let type = undefined; if (isCryptoCoin) { type = 'crypto'; } const feeValues = this.safeList(indexed['fees'], id, []); const fees = this.safeList(feeValues, 1, []); const fee = this.safeNumber(fees, 1); const undl = this.safeList(indexed['undl'], id, []); const precision = '8'; // default precision, todo: fix "magic constants" const fid = 'f' + id; const dwStatuses = this.safeList(indexed['statuses'], id, []); const depositEnabled = this.safeInteger(dwStatuses, 1) === 1; const withdrawEnabled = this.safeInteger(dwStatuses, 2) === 1; const networks = {}; const netwokIds = this.safeList(indexedNetworks, id, []); for (let j = 0; j < netwokIds.length; j++) { const networkId = netwokIds[j]; const network = this.networkIdToCode(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, }, }, }; } result[code] = this.safeCurrencyStructure({ 'id': fid, 'uppercaseId': id, 'code': code, 'info': [id, label, pool, feeValues, undl], 'type': type, 'name': name, 'active': true, 'deposit': depositEnabled, 'withdraw': withdrawEnabled, 'fee': fee, 'precision': parseInt(precision), 'limits': { 'amount': { 'min': this.parseNumber(this.parsePrecision(precision)), 'max': undefined, }, 'withdraw': { 'min': fee, 'max': undefined, }, }, 'networks': networks, }); } return result; } /** * @method * @name bitfinex#fetchBalance * @description query for balance and get the amount of funds available for trading or funds locked in orders * @see https://docs.bitfinex.com/reference/rest-auth-wallets * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure} */ async fetchBalance(params = {}) { // 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 account = this.account(); const interest = this.safeString(balance, 3); if (interest !== '0') { account['debt'] = interest; } 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); account['total'] = this.safeString(balance, 2); account['free'] = this.safeString(balance, 4); result[code] = account; } } return this.safeBalance(result); } /** * @method * @name bitfinex#transfer * @description transfer currency internally between wallets on the same account * @see https://docs.bitfinex.com/reference/rest-auth-transfer * @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 exchange API endpoint * @returns {object} a [transfer structure]{@link https://docs.ccxt.com/#/?id=transfer-structure} */ async transfer(code, amount, fromAccount, toAccount, params = {}) { // 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({ 'result': 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 result = this.safeList(transfer, 'result'); const timestamp = this.safeInteger(result, 0); const info = this.safeValue(result, 4); const fromAccount = this.safeString(info, 1); const toAccount = this.safeString(info, 2); const currencyId = this.safeString(info, 5); const status = this.safeString(result, 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': result, }; } 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; } /** * @method * @name bitfinex#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.bitfinex.com/reference/rest-public-book * @param {string} symbol unified symb