ccxt
Version:
1,155 lines (1,153 loc) • 195 kB
JavaScript
// ----------------------------------------------------------------------------
// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ---------------------------------------------------------------------------
import Exchange from './abstract/hyperliquid.js';
import { ExchangeError, ArgumentsRequired, NotSupported, InvalidOrder, OrderNotFound, BadRequest, InsufficientFunds } from './base/errors.js';
import { Precise } from './base/Precise.js';
import { ROUND, SIGNIFICANT_DIGITS, DECIMAL_PLACES, TICK_SIZE } from './base/functions/number.js';
import { keccak_256 as keccak } from './static_dependencies/noble-hashes/sha3.js';
import { secp256k1 } from './static_dependencies/noble-curves/secp256k1.js';
import { ecdsa } from './base/functions/crypto.js';
// ---------------------------------------------------------------------------
/**
* @class hyperliquid
* @augments Exchange
*/
export default class hyperliquid extends Exchange {
describe() {
return this.deepExtend(super.describe(), {
'id': 'hyperliquid',
'name': 'Hyperliquid',
'countries': [],
'version': 'v1',
'rateLimit': 50,
'certified': true,
'pro': true,
'dex': true,
'has': {
'CORS': undefined,
'spot': true,
'margin': false,
'swap': true,
'future': true,
'option': false,
'addMargin': true,
'borrowCrossMargin': false,
'borrowIsolatedMargin': false,
'cancelAllOrders': false,
'cancelAllOrdersAfter': true,
'cancelOrder': true,
'cancelOrders': true,
'cancelOrdersForSymbols': true,
'closeAllPositions': false,
'closePosition': false,
'createMarketBuyOrderWithCost': false,
'createMarketOrderWithCost': false,
'createMarketSellOrderWithCost': false,
'createOrder': true,
'createOrders': true,
'createOrderWithTakeProfitAndStopLoss': true,
'createReduceOnlyOrder': true,
'createStopOrder': true,
'createTriggerOrder': true,
'editOrder': true,
'editOrders': true,
'fetchAccounts': false,
'fetchBalance': true,
'fetchBorrowInterest': false,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchCanceledAndClosedOrders': true,
'fetchCanceledOrders': true,
'fetchClosedOrders': true,
'fetchCrossBorrowRate': false,
'fetchCrossBorrowRates': false,
'fetchCurrencies': true,
'fetchDepositAddress': false,
'fetchDepositAddresses': false,
'fetchDeposits': true,
'fetchDepositWithdrawFee': 'emulated',
'fetchDepositWithdrawFees': false,
'fetchFundingHistory': true,
'fetchFundingRate': false,
'fetchFundingRateHistory': true,
'fetchFundingRates': true,
'fetchIndexOHLCV': false,
'fetchIsolatedBorrowRate': false,
'fetchIsolatedBorrowRates': false,
'fetchLedger': true,
'fetchLeverage': false,
'fetchLeverageTiers': false,
'fetchLiquidations': false,
'fetchMarginMode': undefined,
'fetchMarketLeverageTiers': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyLiquidations': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenInterest': true,
'fetchOpenInterestHistory': false,
'fetchOpenInterests': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchOrderTrades': false,
'fetchPosition': true,
'fetchPositionMode': false,
'fetchPositions': true,
'fetchPositionsRisk': false,
'fetchPremiumIndexOHLCV': false,
'fetchStatus': true,
'fetchTicker': 'emulated',
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchTradingFee': true,
'fetchTradingFees': false,
'fetchTransfer': false,
'fetchTransfers': false,
'fetchWithdrawal': false,
'fetchWithdrawals': true,
'reduceMargin': true,
'repayCrossMargin': false,
'repayIsolatedMargin': false,
'sandbox': true,
'setLeverage': true,
'setMarginMode': true,
'setPositionMode': false,
'transfer': true,
'withdraw': true,
},
'timeframes': {
'1m': '1m',
'3m': '3m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'2h': '2h',
'4h': '4h',
'8h': '8h',
'12h': '12h',
'1d': '1d',
'3d': '3d',
'1w': '1w',
'1M': '1M',
},
'hostname': 'hyperliquid.xyz',
'urls': {
'logo': 'https://github.com/ccxt/ccxt/assets/43336371/b371bc6c-4a8c-489f-87f4-20a913dd8d4b',
'api': {
'public': 'https://api.{hostname}',
'private': 'https://api.{hostname}',
},
'test': {
'public': 'https://api.hyperliquid-testnet.xyz',
'private': 'https://api.hyperliquid-testnet.xyz',
},
'www': 'https://hyperliquid.xyz',
'doc': 'https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api',
'fees': 'https://hyperliquid.gitbook.io/hyperliquid-docs/trading/fees',
'referral': 'https://app.hyperliquid.xyz/',
},
'api': {
'public': {
'post': {
'info': {
'cost': 20,
'byType': {
'l2Book': 2,
'allMids': 2,
'clearinghouseState': 2,
'orderStatus': 2,
'spotClearinghouseState': 2,
'exchangeStatus': 2,
'candleSnapshot': 4,
},
},
},
},
'private': {
'post': {
'exchange': 1,
},
},
},
'fees': {
'swap': {
'taker': this.parseNumber('0.00045'),
'maker': this.parseNumber('0.00015'),
},
'spot': {
'taker': this.parseNumber('0.0007'),
'maker': this.parseNumber('0.0004'),
},
},
'requiredCredentials': {
'apiKey': false,
'secret': false,
'walletAddress': true,
'privateKey': true,
},
'exceptions': {
'exact': {},
'broad': {
'Price must be divisible by tick size.': InvalidOrder,
'Order must have minimum value of $10': InvalidOrder,
'Insufficient margin to place order.': InsufficientFunds,
'Reduce only order would increase position.': InvalidOrder,
'Post only order would have immediately matched,': InvalidOrder,
'Order could not immediately match against any resting orders.': InvalidOrder,
'Invalid TP/SL price.': InvalidOrder,
'No liquidity available for market order.': InvalidOrder,
'Order was never placed, already canceled, or filled.': OrderNotFound,
'User or API Wallet ': InvalidOrder,
'Order has invalid size': InvalidOrder,
'Order price cannot be more than 80% away from the reference price': InvalidOrder,
'Order has zero size.': InvalidOrder,
'Insufficient spot balance asset': InsufficientFunds,
'Insufficient balance for withdrawal': InsufficientFunds,
'Insufficient balance for token transfer': InsufficientFunds,
},
},
'precisionMode': TICK_SIZE,
'commonCurrencies': {},
'options': {
'defaultType': 'swap',
'sandboxMode': false,
'defaultSlippage': 0.05,
'marketHelperProps': ['hip3TokensByName', 'cachedCurrenciesById'],
'zeroAddress': '0x0000000000000000000000000000000000000000',
// below will be filled automatically
'spotCurrencyMapping': {
'UDZ': '2Z',
'UBONK': 'BONK',
'UBTC': 'BTC',
'UETH': 'ETH',
'UFART': 'FARTCOIN',
'HPENGU': 'PENGU',
'UPUMP': 'PUMP',
'USOL': 'SOL',
'UUUSPX': 'SPX',
'USDT0': 'USDT',
'XAUT0': 'XAUT',
'UXPL': 'XPL',
},
'fetchMarkets': {
'types': ['spot', 'swap', 'hip3'],
'hip3': {
'limit': 10,
'dexes': [], // list of dexes eg flx, xyz, etc
},
},
},
'features': {
'default': {
'sandbox': true,
'createOrder': {
'marginMode': false,
'triggerPrice': false,
'triggerPriceType': undefined,
'triggerDirection': false,
'stopLossPrice': false,
'takeProfitPrice': false,
'attachedStopLossTakeProfit': {
'triggerPriceType': {
'last': false,
'mark': false,
'index': false,
},
'triggerPrice': true,
'type': true,
'price': true,
},
'timeInForce': {
'IOC': true,
'FOK': false,
'PO': true,
'GTD': false,
},
'hedged': false,
'trailing': false,
'leverage': false,
'marketBuyByCost': false,
'marketBuyRequiresPrice': false,
'selfTradePrevention': false,
'iceberg': false,
},
'createOrders': {
'max': 1000,
},
'fetchMyTrades': {
'marginMode': false,
'limit': 2000,
'daysBack': undefined,
'untilDays': undefined,
'symbolRequired': true,
},
'fetchOrder': {
'marginMode': false,
'trigger': false,
'trailing': false,
'symbolRequired': true,
},
'fetchOpenOrders': {
'marginMode': false,
'limit': 2000,
'trigger': false,
'trailing': false,
'symbolRequired': true,
},
'fetchOrders': {
'marginMode': false,
'limit': 2000,
'daysBack': undefined,
'untilDays': undefined,
'trigger': false,
'trailing': false,
'symbolRequired': true,
},
'fetchClosedOrders': {
'marginMode': false,
'limit': 2000,
'daysBack': undefined,
'daysBackCanceled': undefined,
'untilDays': undefined,
'trigger': false,
'trailing': false,
'symbolRequired': true,
},
'fetchOHLCV': {
'limit': 5000,
},
},
'spot': {
'extends': 'default',
},
'forPerps': {
'extends': 'default',
'createOrder': {
'stopLossPrice': true,
'takeProfitPrice': true,
'attachedStopLossTakeProfit': undefined, // todo, in two orders
},
},
'swap': {
'linear': {
'extends': 'forPerps',
},
'inverse': {
'extends': 'forPerps',
},
},
'future': {
'linear': {
'extends': 'forPerps',
},
'inverse': {
'extends': 'forPerps',
},
},
},
});
}
setSandboxMode(enabled) {
super.setSandboxMode(enabled);
this.options['sandboxMode'] = enabled;
}
market(symbol) {
if (this.markets === undefined) {
throw new ExchangeError(this.id + ' markets not loaded');
}
if ((symbol !== undefined) && !(symbol in this.markets)) {
const symbolParts = symbol.split('/');
const baseName = this.safeString(symbolParts, 0);
const spotCurrencyMapping = this.safeDict(this.options, 'spotCurrencyMapping', {});
if (baseName in spotCurrencyMapping) {
const unifiedBaseName = this.safeString(spotCurrencyMapping, baseName);
const quote = this.safeString(symbolParts, 1);
const newSymbol = this.safeCurrencyCode(unifiedBaseName) + '/' + quote;
if (newSymbol in this.markets) {
return this.markets[newSymbol];
}
}
}
return super.market(symbol);
}
/**
* @method
* @name hyperliquid#fetchStatus
* @description the latest known information on the availability of the exchange API
* @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 = {}) {
const request = {
'type': 'exchangeStatus',
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// {
// "status": "ok"
// }
//
const status = this.safeString(response, 'specialStatuses');
return {
'status': (status === undefined) ? 'ok' : 'maintenance',
'updated': this.safeInteger(response, 'time'),
'eta': undefined,
'url': undefined,
'info': response,
};
}
/**
* @method
* @name hyperliquid#fetchTime
* @description fetches the current integer timestamp in milliseconds from the exchange server
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {int} the current integer timestamp in milliseconds from the exchange server
*/
async fetchTime(params = {}) {
const request = {
'type': 'exchangeStatus',
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// { specialStatuses: null, time: '1764617438643' }
//
return this.safeInteger(response, 'time');
}
/**
* @method
* @name hyperliquid#fetchCurrencies
* @description fetches all available currencies on an exchange
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} an associative dictionary of currencies
*/
async fetchCurrencies(params = {}) {
if (this.checkRequiredCredentials(false)) {
await this.initializeClient();
}
const request = {
// 'type': 'meta',
'type': 'spotMeta',
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// [
// {
// "universe": [
// {
// "maxLeverage": 50,
// "name": "SOL",
// "onlyIsolated": false,
// "szDecimals": 2
// }
// ]
// }
// ]
//
// const spotMeta = await this.publicPostInfo ({ 'type': 'spotMeta' });
const tokens = this.safeList(response, 'tokens', []);
// const meta = this.safeList (response, 'universe', []);
this.options['cachedCurrenciesById'] = {}; // used to map hip3 markets
const result = {};
for (let i = 0; i < tokens.length; i++) {
const data = this.safeDict(tokens, i, {});
// const id = i;
const id = this.safeString(data, 'index');
const name = this.safeString(data, 'name');
const code = this.safeCurrencyCode(name);
this.options['cachedCurrenciesById'][id] = name;
result[code] = this.safeCurrencyStructure({
'id': id,
'name': name,
'code': code,
'precision': this.parsePrecision(this.safeString(data, 'weiDecimals')),
'info': data,
'active': undefined,
'deposit': undefined,
'withdraw': undefined,
'networks': undefined,
'fee': undefined,
'type': 'crypto',
'limits': {
'amount': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
},
});
// add in wrapped map
const fullName = this.safeString(data, 'fullName');
if (fullName !== undefined && name !== undefined) {
const isWrapped = fullName.startsWith('Unit ') && name.startsWith('U');
if (isWrapped) {
const parts = name.split('U');
let nameWithoutU = '';
for (let j = 0; j < parts.length; j++) {
nameWithoutU = nameWithoutU + parts[j];
}
const baseCode = this.safeCurrencyCode(nameWithoutU);
this.options['spotCurrencyMapping'][code] = baseCode;
}
}
}
return result;
}
/**
* @method
* @name hyperliquid#fetchMarkets
* @description retrieves data on all markets for hyperliquid
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchMarkets(params = {}) {
const options = this.safeDict(this.options, 'fetchMarkets', {});
const types = this.safeList(options, 'types');
const rawPromises = [];
for (let i = 0; i < types.length; i++) {
const marketType = types[i];
if (marketType === 'swap') {
rawPromises.push(this.fetchSwapMarkets(params));
}
else if (marketType === 'spot') {
rawPromises.push(this.fetchSpotMarkets(params));
}
else if (marketType === 'hip3') {
rawPromises.push(this.fetchHip3Markets(params));
}
}
const promises = await Promise.all(rawPromises);
let result = [];
for (let i = 0; i < promises.length; i++) {
result = this.arrayConcat(result, promises[i]);
}
return result;
}
/**
* @method
* @name hyperliquid#fetchHip3Markets
* @description retrieves data on all hip3 markets for hyperliquid
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-all-perpetual-dexs
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchHip3Markets(params = {}) {
const fetchDexes = await this.publicPostInfo({
'type': 'perpDexs',
});
//
// [
// null,
// {
// "name": "xyz",
// "fullName": "XYZ",
// "deployer": "0x88806a71d74ad0a510b350545c9ae490912f0888",
// "oracleUpdater": "0x1234567890545d1df9ee64b35fdd16966e08acec",
// "feeRecipient": "0x79c0650064b10f73649b7b64c5ebf0b319606140",
// "assetToStreamingOiCap": [
// [
// "xyz:XYZ100",
// "100000000.0"
// ]
// ]
// }
// ]
//
const perpDexesOffset = {};
for (let i = 1; i < fetchDexes.length; i++) {
// builder-deployed perp dexs start at 110000
const dex = fetchDexes[i];
const offset = 110000 + (i - 1) * 10000;
perpDexesOffset[dex['name']] = offset;
}
let fetchDexesList = [];
const options = this.safeDict(this.options, 'fetchMarkets', {});
const hip3 = this.safeDict(options, 'hip3', {});
const dexesProvided = this.safeList(hip3, 'dexes', []); // let users provide their own list of dexes to load
const maxLimit = this.safeInteger(hip3, 'limit', 10);
const userProvidedDexesLength = dexesProvided.length;
if (userProvidedDexesLength > 0) {
if (userProvidedDexesLength > 0) {
fetchDexesList = dexesProvided;
}
}
else {
const fetchDexesLength = fetchDexes.length;
for (let i = 1; i < maxLimit; i++) {
if (i >= fetchDexesLength) {
break;
}
const dex = this.safeDict(fetchDexes, i, {});
if (dex === undefined) {
continue;
}
const dexName = this.safeString(dex, 'name');
fetchDexesList.push(dexName);
}
}
const rawPromises = [];
for (let i = 0; i < fetchDexesList.length; i++) {
const request = {
'type': 'metaAndAssetCtxs',
'dex': this.safeString(fetchDexesList, i),
};
rawPromises.push(this.publicPostInfo(this.extend(request, params)));
}
const promises = await Promise.all(rawPromises);
this.options['hip3TokensByName'] = {};
let markets = [];
for (let i = 0; i < promises.length; i++) {
const dexName = fetchDexesList[i];
const offset = perpDexesOffset[dexName];
const response = promises[i];
const meta = this.safeDict(response, 0, {});
const collateralToken = this.safeString(meta, 'collateralToken');
const universe = this.safeList(meta, 'universe', []);
const assetCtxs = this.safeList(response, 1, []);
const result = [];
// helper because some endpoints return just the coin name like: flx:crcl
// and we don't have the base/settle information and we can't assume it's USDC for hip3 markets
for (let j = 0; j < universe.length; j++) {
const data = this.extend(this.safeDict(universe, j, {}), this.safeDict(assetCtxs, j, {}));
data['baseId'] = j + offset;
data['collateralToken'] = collateralToken;
data['hip3'] = true;
data['dex'] = dexName;
const cachedCurrencies = this.safeDict(this.options, 'cachedCurrenciesById', {});
// injecting collateral token name for further usage in parseMarket, already converted from like '0' to 'USDC', etc
if (collateralToken in cachedCurrencies) {
const name = this.safeString(data, 'name');
const collateralTokenCode = this.safeString(cachedCurrencies, collateralToken);
data['collateralTokenName'] = collateralTokenCode;
// eg: 'flx:crcl' => {'quote': 'USDC', 'code': 'FLX-CRCL'}
const safeCode = this.safeCurrencyCode(name);
this.options['hip3TokensByName'][name] = {
'quote': collateralTokenCode,
'code': safeCode.replace(':', '-'),
};
}
result.push(data);
}
markets = this.arrayConcat(markets, this.parseMarkets(result));
}
//
// [
// {
// "universe": [
// {
// "maxLeverage": 50,
// "name": "SOL",
// "onlyIsolated": false,
// "szDecimals": 2
// }
// ]
// },
// [
// {
// "dayNtlVlm": "9450588.2273",
// "funding": "0.0000198",
// "impactPxs": [
// "108.04",
// "108.06"
// ],
// "markPx": "108.04",
// "midPx": "108.05",
// "openInterest": "10764.48",
// "oraclePx": "107.99",
// "premium": "0.00055561",
// "prevDayPx": "111.81"
// }
// ]
// ]
//
//
return markets;
}
/**
* @method
* @name hyperliquid#fetchSwapMarkets
* @description retrieves data on all swap markets for hyperliquid
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchSwapMarkets(params = {}) {
const request = {
'type': 'metaAndAssetCtxs',
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// [
// {
// "universe": [
// {
// "maxLeverage": 50,
// "name": "SOL",
// "onlyIsolated": false,
// "szDecimals": 2
// }
// ]
// },
// [
// {
// "dayNtlVlm": "9450588.2273",
// "funding": "0.0000198",
// "impactPxs": [
// "108.04",
// "108.06"
// ],
// "markPx": "108.04",
// "midPx": "108.05",
// "openInterest": "10764.48",
// "oraclePx": "107.99",
// "premium": "0.00055561",
// "prevDayPx": "111.81"
// }
// ]
// ]
//
//
const meta = this.safeDict(response, 0, {});
const universe = this.safeList(meta, 'universe', []);
const assetCtxs = this.safeList(response, 1, []);
const result = [];
for (let i = 0; i < universe.length; i++) {
const data = this.extend(this.safeDict(universe, i, {}), this.safeDict(assetCtxs, i, {}));
data['baseId'] = i;
result.push(data);
}
return this.parseMarkets(result);
}
/**
* @method
* @name hyperliquid#calculatePricePrecision
* @description Helper function to calculate the Hyperliquid DECIMAL_PLACES price precision
* @param {float} price the price to use in the calculation
* @param {int} amountPrecision the amountPrecision to use in the calculation
* @param {int} maxDecimals the maxDecimals to use in the calculation
* @returns {int} The calculated price precision
*/
calculatePricePrecision(price, amountPrecision, maxDecimals) {
let pricePrecision = 0;
const priceStr = this.numberToString(price);
if (priceStr === undefined) {
return 0;
}
const priceSplitted = priceStr.split('.');
if (Precise.stringEq(priceStr, '0')) {
// Significant digits is always 5 in this case
const significantDigits = 5;
// Integer digits is always 0 in this case (0 doesn't count)
const integerDigits = 0;
// Calculate the price precision
pricePrecision = Math.min(maxDecimals - amountPrecision, significantDigits - integerDigits);
}
else if (Precise.stringGt(priceStr, '0') && Precise.stringLt(priceStr, '1')) {
// Significant digits, always 5 in this case
const significantDigits = 5;
// Get the part after the decimal separator
const decimalPart = this.safeString(priceSplitted, 1, '');
// Count the number of leading zeros in the decimal part
let leadingZeros = 0;
while ((leadingZeros <= decimalPart.length) && (decimalPart[leadingZeros] === '0')) {
leadingZeros = leadingZeros + 1;
}
// Calculate price precision based on leading zeros and significant digits
pricePrecision = leadingZeros + significantDigits;
// Calculate the price precision based on maxDecimals - szDecimals and the calculated price precision from the previous step
pricePrecision = Math.min(maxDecimals - amountPrecision, pricePrecision);
}
else {
// Count the numbers before the decimal separator
const integerPart = this.safeString(priceSplitted, 0, '');
// Get significant digits, take the max() of 5 and the integer digits count
const significantDigits = Math.max(5, integerPart.length);
// Calculate price precision based on maxDecimals - szDecimals and significantDigits - integerPart.length
pricePrecision = Math.min(maxDecimals - amountPrecision, significantDigits - integerPart.length);
}
return this.parseToInt(pricePrecision);
}
/**
* @method
* @name hyperliquid#fetchSpotMarkets
* @description retrieves data on all spot markets for hyperliquid
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchSpotMarkets(params = {}) {
const request = {
'type': 'spotMetaAndAssetCtxs',
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// [
// {
// "tokens": [
// {
// "name": "USDC",
// "szDecimals": 8,
// "weiDecimals" 8,
// "index": 0,
// "tokenId": "0x6d1e7cde53ba9467b783cb7c530ce054",
// "isCanonical": true,
// "evmContract":null,
// "fullName":null
// },
// {
// "name": "PURR",
// "szDecimals": 0,
// "weiDecimals": 5,
// "index": 1,
// "tokenId": "0xc1fb593aeffbeb02f85e0308e9956a90",
// "isCanonical": true,
// "evmContract":null,
// "fullName":null
// }
// ],
// "universe": [
// {
// "name": "PURR/USDC",
// "tokens": [1, 0],
// "index": 0,
// "isCanonical": true
// }
// ]
// },
// [
// {
// "dayNtlVlm":"8906.0",
// "markPx":"0.14",
// "midPx":"0.209265",
// "prevDayPx":"0.20432"
// }
// ]
// ]
//
const first = this.safeDict(response, 0, {});
const second = this.safeList(response, 1, []);
const meta = this.safeList(first, 'universe', []);
const tokens = this.safeList(first, 'tokens', []);
const markets = [];
for (let i = 0; i < meta.length; i++) {
const market = this.safeDict(meta, i, {});
const index = this.safeInteger(market, 'index');
const extraData = this.safeDict(second, index, {});
const marketName = this.safeString(market, 'name');
// if (marketName.indexOf ('/') < 0) {
// // there are some weird spot markets in testnet, eg @2
// continue;
// }
// const marketParts = marketName.split ('/');
// const baseName = this.safeString (marketParts, 0);
// const quoteId = this.safeString (marketParts, 1);
const fees = this.safeDict(this.fees, 'spot', {});
const taker = this.safeNumber(fees, 'taker');
const maker = this.safeNumber(fees, 'maker');
const tokensPos = this.safeList(market, 'tokens', []);
const baseTokenPos = this.safeInteger(tokensPos, 0);
const quoteTokenPos = this.safeInteger(tokensPos, 1);
const baseTokenInfo = this.safeDict(tokens, baseTokenPos, {});
const quoteTokenInfo = this.safeDict(tokens, quoteTokenPos, {});
const baseName = this.safeString(baseTokenInfo, 'name');
const quoteId = this.safeString(quoteTokenInfo, 'name');
// do spot currency mapping
const spotCurrencyMapping = this.safeDict(this.options, 'spotCurrencyMapping', {});
const mappedBaseName = this.safeString(spotCurrencyMapping, baseName, baseName);
const mappedQuoteId = this.safeString(spotCurrencyMapping, quoteId, quoteId);
const mappedBase = this.safeCurrencyCode(mappedBaseName);
const mappedQuote = this.safeCurrencyCode(mappedQuoteId);
const mappedSymbol = mappedBase + '/' + mappedQuote;
const innerBaseTokenInfo = this.safeDict(baseTokenInfo, 'spec', baseTokenInfo);
// const innerQuoteTokenInfo = this.safeDict (quoteTokenInfo, 'spec', quoteTokenInfo);
const amountPrecisionStr = this.safeString(innerBaseTokenInfo, 'szDecimals');
const amountPrecision = parseInt(amountPrecisionStr);
const price = this.safeNumber(extraData, 'midPx');
let pricePrecision = 0;
if (price !== undefined) {
pricePrecision = this.calculatePricePrecision(price, amountPrecision, 8);
}
const pricePrecisionStr = this.numberToString(pricePrecision);
// const quotePrecision = this.parseNumber (this.parsePrecision (this.safeString (innerQuoteTokenInfo, 'szDecimals')));
const baseId = this.numberToString(index + 10000);
const entry = {
'id': marketName,
'symbol': mappedSymbol,
'base': mappedBase,
'quote': mappedQuote,
'settle': undefined,
'baseId': baseId,
'baseName': baseName,
'quoteId': quoteId,
'settleId': undefined,
'type': 'spot',
'spot': true,
'subType': undefined,
'margin': undefined,
'swap': false,
'future': false,
'option': false,
'active': true,
'contract': false,
'linear': undefined,
'inverse': undefined,
'taker': taker,
'maker': maker,
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.parseNumber(this.parsePrecision(amountPrecisionStr)),
'price': this.parseNumber(this.parsePrecision(pricePrecisionStr)),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.parseNumber('10'),
'max': undefined,
},
},
'created': undefined,
'info': this.extend(extraData, market),
};
markets.push(this.safeMarketStructure(entry));
// // backward support
// const base = this.safeCurrencyCode (baseName);
// const quote = this.safeCurrencyCode (quoteId);
// const newEntry = this.extend ({}, entry);
// const symbol = base + '/' + quote;
// if (symbol !== mappedSymbol) {
// newEntry['symbol'] = symbol;
// newEntry['base'] = base;
// newEntry['quote'] = quote;
// newEntry['baseName'] = baseName;
// markets.push (this.safeMarketStructure (newEntry));
// }
}
return markets;
}
parseMarket(market) {
//
// {
// "maxLeverage": "50",
// "name": "ETH",
// "onlyIsolated": false,
// "szDecimals": "4",
// "dayNtlVlm": "1709813.11535",
// "funding": "0.00004807",
// "impactPxs": [
// "2369.3",
// "2369.6"
// ],
// "markPx": "2369.6",
// "midPx": "2369.45",
// "openInterest": "1815.4712",
// "oraclePx": "2367.3",
// "premium": "0.00090821",
// "prevDayPx": "2381.5"
// "collateralToken": "0" hip3 tokens only
// }
//
const collateralTokenCode = this.safeString(market, 'collateralTokenName');
const quoteId = (collateralTokenCode === undefined) ? 'USDC' : collateralTokenCode;
const settleId = (collateralTokenCode === undefined) ? 'USDC' : collateralTokenCode;
const baseName = this.safeString(market, 'name');
let base = this.safeCurrencyCode(baseName);
base = base.replace(':', '-'); // handle hip3 tokens and converts from like flx:crcl to FLX-CRCL
const quote = this.safeCurrencyCode(quoteId);
const baseId = this.safeString(market, 'baseId');
const settle = this.safeCurrencyCode(settleId);
let symbol = base + '/' + quote;
const contract = true;
const swap = true;
if (contract) {
if (swap) {
symbol = symbol + ':' + settle;
}
}
const fees = this.safeDict(this.fees, 'swap', {});
const taker = this.safeNumber(fees, 'taker');
const maker = this.safeNumber(fees, 'maker');
const amountPrecisionStr = this.safeString(market, 'szDecimals');
const amountPrecision = parseInt(amountPrecisionStr);
const price = this.safeNumber(market, 'markPx', 0);
let pricePrecision = 0;
if (price !== undefined) {
pricePrecision = this.calculatePricePrecision(price, amountPrecision, 6);
}
const pricePrecisionStr = this.numberToString(pricePrecision);
const isDelisted = this.safeBool(market, 'isDelisted');
let active = true;
if (isDelisted !== undefined) {
active = !isDelisted;
}
return this.safeMarketStructure({
'id': baseId,
'symbol': symbol,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'baseName': baseName,
'quoteId': quoteId,
'settleId': settleId,
'type': 'swap',
'spot': false,
'margin': undefined,
'swap': swap,
'future': false,
'option': false,
'active': active,
'contract': contract,
'linear': true,
'inverse': false,
'taker': taker,
'maker': maker,
'contractSize': this.parseNumber('1'),
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.parseNumber(this.parsePrecision(amountPrecisionStr)),
'price': this.parseNumber(this.parsePrecision(pricePrecisionStr)),
},
'limits': {
'leverage': {
'min': undefined,
'max': this.safeInteger(market, 'maxLeverage'),
},
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': this.parseNumber('10'),
'max': undefined,
},
},
'created': undefined,
'info': market,
});
}
updateSpotCurrencyCode(code) {
if (code === undefined) {
return code;
}
const spotCurrencyMapping = this.safeDict(this.options, 'spotCurrencyMapping', {});
return this.safeString(spotCurrencyMapping, code, code);
}
/**
* @method
* @name hyperliquid#fetchBalance
* @description query for balance and get the amount of funds available for trading or funds locked in orders
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-a-users-token-balances
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.user] user address, will default to this.walletAddress if not provided
* @param {string} [params.type] wallet type, ['spot', 'swap'], defaults to swap
* @param {string} [params.marginMode] 'cross' or 'isolated', for margin trading, uses this.options.defaultMarginMode if not passed, defaults to undefined/None/null
* @param {string} [params.dex] for hip3 markets, the dex name, eg: 'xyz'
* @param {string} [params.subAccountAddress] sub account user address
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/?id=balance-structure}
*/
async fetchBalance(params = {}) {
let userAddress = undefined;
[userAddress, params] = this.handlePublicAddress('fetchBalance', params);
let type = undefined;
[type, params] = this.handleMarketTypeAndParams('fetchBalance', undefined, params);
let marginMode = undefined;
[marginMode, params] = this.handleMarginModeAndParams('fetchBalance', params);
const isSpot = (type === 'spot');
const request = {
'type': (isSpot) ? 'spotClearinghouseState' : 'clearinghouseState',
'user': userAddress,
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// {
// "assetPositions": [],
// "crossMaintenanceMarginUsed": "0.0",
// "crossMarginSummary": {
// "accountValue": "100.0",
// "totalMarginUsed": "0.0",
// "totalNtlPos": "0.0",
// "totalRawUsd": "100.0"
// },
// "marginSummary": {
// "accountValue": "100.0",
// "totalMarginUsed": "0.0",
// "totalNtlPos": "0.0",
// "totalRawUsd": "100.0"
// },
// "time": "1704261007014",
// "withdrawable": "100.0"
// }
// spot
//
// {
// "balances":[
// {
// "coin":"USDC",
// "hold":"0.0",
// "total":"1481.844"
// },
// {
// "coin":"PURR",
// "hold":"0.0",
// "total":"999.65004"
// }
// }
//
const balances = this.safeList(response, 'balances');
if (balances !== undefined) {
const spotBalances = { 'info': response };
for (let i = 0; i < balances.length; i++) {
const balance = balances[i];
const unifiedCode = this.safeCurrencyCode(this.safeString(balance, 'coin'));
const code = isSpot ? this.updateSpotCurrencyCode(unifiedCode) : unifiedCode;
const account = this.account();
const total = this.safeString(balance, 'total');
const used = this.safeString(balance, 'hold');
account['total'] = total;
account['used'] = used;
spotBalances[code] = account;
}
return this.safeBalance(spotBalances);
}
const data = this.safeDict(response, 'marginSummary', {});
const usdcBalance = {
'total': this.safeNumber(data, 'accountValue'),
};
if ((marginMode !== undefined) && (marginMode === 'is