@kraken-crypto/ccxt
Version:
A cryptocurrency trading API with more than 100 exchanges in JavaScript / TypeScript / Python / C# / PHP / Go
1,154 lines • 187 kB
JavaScript
// ---------------------------------------------------------------------------
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,
'fetchTicker': 'emulated',
'fetchTickers': true,
'fetchTime': false,
'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,
'zeroAddress': '0x0000000000000000000000000000000000000000',
'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'], // 'spot', 'swap', 'hip3'
// 'hip3': {
// 'limit': 5, // how many dexes to load max if dexes are not specified
// 'dex': [ 'xyz' ],
// },
},
},
'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 in this.markets) {
const market = this.markets[symbol];
if (market['spot']) {
const baseName = this.safeString(market, 'baseName');
const spotCurrencyMapping = this.safeDict(this.options, 'spotCurrencyMapping', {});
if (baseName in spotCurrencyMapping) {
const unifiedBaseName = this.safeString(spotCurrencyMapping, baseName);
const quote = this.safeString(market, 'quote');
const newSymbol = this.safeCurrencyCode(unifiedBaseName) + '/' + quote;
if (newSymbol in this.markets) {
return this.markets[newSymbol];
}
}
}
}
const res = super.market(symbol);
return res;
}
safeMarket(marketId = undefined, market = undefined, delimiter = undefined, marketType = undefined) {
if (marketId !== undefined) {
if ((this.markets_by_id !== undefined) && (marketId in this.markets_by_id)) {
const markets = this.markets_by_id[marketId];
const numMarkets = markets.length;
if (numMarkets === 1) {
return markets[0];
}
else {
if (numMarkets > 2) {
throw new ExchangeError(this.id + ' safeMarket() found more than two markets with the same market id ' + marketId);
}
const firstMarket = markets[0];
const secondMarket = markets[1];
if (this.safeString(firstMarket, 'type') !== this.safeString(secondMarket, 'type')) {
throw new ExchangeError(this.id + ' safeMarket() found two different market types with the same market id ' + marketId);
}
const baseCurrency = this.safeString(firstMarket, 'base');
const spotCurrencyMapping = this.safeDict(this.options, 'spotCurrencyMapping', {});
if (baseCurrency in spotCurrencyMapping) {
return secondMarket;
}
return firstMarket;
}
}
}
return super.safeMarket(marketId, market, delimiter, marketType);
}
/**
* @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',
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// [
// {
// "universe": [
// {
// "maxLeverage": 50,
// "name": "SOL",
// "onlyIsolated": false,
// "szDecimals": 2
// }
// ]
// }
// ]
//
const meta = this.safeList(response, 'universe', []);
const result = {};
for (let i = 0; i < meta.length; i++) {
const data = this.safeDict(meta, i, {});
const id = i;
const name = this.safeString(data, 'name');
const code = this.safeCurrencyCode(name);
result[code] = this.safeCurrencyStructure({
'id': id,
'name': name,
'code': code,
'precision': undefined,
'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,
},
},
});
}
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 defaultLimit = this.safeInteger(hip3, 'limit', 5);
const dexesLength = fetchDexes.length;
if (dexesLength >= defaultLimit) { // first element is null
const defaultDexes = this.safeList(hip3, 'dex', []);
if (defaultDexes.length === 0) {
throw new ArgumentsRequired(this.id + ' fetchHip3Markets() Too many DEXes found. Please specify a list of DEXes in the exchange.options["fetchMarkets"]["hip3"]["dex"] parameter to fetch markets from those DEXes only. The limit is set to ' + defaultLimit.toString() + ' DEXes by default.');
}
else {
fetchDexesList = defaultDexes;
}
}
else {
for (let i = 1; i < fetchDexes.length; i++) {
const dex = this.safeDict(fetchDexes, i, {});
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);
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 universe = this.safeList(meta, 'universe', []);
const assetCtxs = this.safeList(response, 1, []);
const result = [];
for (let j = 0; j < universe.length; j++) {
const data = this.extend(this.safeDict(universe, j, {}), this.safeDict(assetCtxs, j, {}));
data['baseId'] = j + offset;
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"
// }
//
const quoteId = 'USDC';
const baseName = this.safeString(market, 'name');
const base = this.safeCurrencyCode(baseName);
const quote = this.safeCurrencyCode(quoteId);
const baseId = this.safeString(market, 'baseId');
const settleId = 'USDC';
const settle = this.safeCurrencyCode(settleId);
let symbol = base.replace(':', '-') + '/' + 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,
});
}
/**
* @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.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 code = this.safeCurrencyCode(this.safeString(balance, 'coin'));
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 === 'isolated')) {
usdcBalance['free'] = this.safeNumber(response, 'withdrawable');
}
else {
usdcBalance['used'] = this.safeNumber(data, 'totalMarginUsed');
}
const result = {
'info': response,
'USDC': usdcBalance,
};
const timestamp = this.safeInteger(response, 'time');
result['timestamp'] = timestamp;
result['datetime'] = this.iso8601(timestamp);
return this.safeBalance(result);
}
/**
* @method
* @name hyperliquid#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#l2-book-snapshot
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] the maximum amount of order book entries to return
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
async fetchOrderBook(symbol, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const request = {
'type': 'l2Book',
'coin': market['swap'] ? market['baseName'] : market['id'],
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// {
// "coin": "ETH",
// "levels": [
// [
// {
// "n": "2",
// "px": "2216.2",
// "sz": "74.0637"
// }
// ],
// [
// {
// "n": "2",
// "px": "2216.5",
// "sz": "70.5893"
// }
// ]
// ],
// "time": "1704290104840"
// }
//
const data = this.safeList(response, 'levels', []);
const result = {
'bids': this.safeList(data, 0, []),
'asks': this.safeList(data, 1, []),
};
const timestamp = this.safeInteger(response, 'time');
return this.parseOrderBook(result, market['symbol'], timestamp, 'bids', 'asks', 'px', 'sz');
}
/**
* @method
* @name hyperliquid#fetchTickers
* @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
* @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 {string[]} [symbols] unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.type] 'spot' or 'swap', by default fetches both
* @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async fetchTickers(symbols = undefined, params = {}) {
await this.loadMarkets();
s