ccxt
Version:
1,131 lines (1,129 loc) • 175 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 { 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