ccxt
Version:
1,176 lines (1,174 loc) • 168 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,
'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,
},
},
},
},
'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',
},
'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;
}
/**
* @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.handleBuilderFeeApproval();
}
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 rawPromises = [
this.fetchSwapMarkets(params),
this.fetchSpotMarkets(params),
];
const promises = await Promise.all(rawPromises);
const swapMarkets = promises[0];
const spotMarkets = promises[1];
return this.arrayConcat(swapMarkets, spotMarkets);
}
/**
* @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');
const base = this.safeCurrencyCode(baseName);
const quote = this.safeCurrencyCode(quoteId);
const symbol = base + '/' + quote;
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);
markets.push(this.safeMarketStructure({
'id': marketName,
'symbol': symbol,
'base': base,
'quote': quote,
'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),
}));
}
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 + '/' + 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();
symbols = this.marketSymbols(symbols);
// at this stage, to get tickers data, we use fetchMarkets endpoints
let response = [];
const type = this.safeString(params, 'type');
params = this.omit(params, 'type');
if (type === 'spot') {
response = await this.fetchSpotMarkets(params);
}
else if (type === 'swap') {
response = await this.fetchSwapMarkets(params);
}
else {
response = await this.fetchMarkets(params);
}
// same response as under "fetchMarkets"
const result = {};
for (let i = 0; i < response.length; i++) {
const market = response[i];
const info = market['info'];
const ticker = this.parseTicker(info, market);
const symbol = this.safeString(ticker, 'symbol');
result[symbol] = ticker;
}
return this.filterByArrayTickers(result, 'symbol', symbols);
}
/**
* @method
* @name hyperliquid#fetchFundingRates
* @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 {string[]} [symbols] list of unified market symbols
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} an array of objects representing market data
*/
async fetchFundingRates(symbols = undefined, 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, {}));
result.push(data);
}
return this.parseFundingRates(result, symbols);
}
parseFundingRate(info, market = undefined) {
//
// {
// "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 base = this.safeString(info, 'name');
const marketId = this.coinToMarketId(base);
const symbol = this.safeSymbol(marketId, market);
const funding = this.safeNumber(info, 'funding');
const markPx = this.safeNumber(info, 'markPx');
const oraclePx = this.safeNumber(info, 'oraclePx');
const fundingTimestamp = (Math.floor(this.milliseconds() / 60 / 60 / 1000) + 1) * 60 * 60 * 1000;
return {
'info': info,
'symbol': symbol,
'markPrice': markPx,
'indexPrice': oraclePx,
'interestRate': undefined,
'estimatedSettlePrice': undefined,
'timestamp': undefined,
'datetime': undefined,
'fundingRate': funding,
'fundingTimestamp': fundingTimestamp,
'fundingDatetime': this.iso8601(fundingTimestamp),
'nextFundingRate': undefined,
'nextFundingTimestamp': undefined,
'nextFundingDatetime': undefined,
'previousFundingRate': undefined,
'previousFundingTimestamp': undefined,
'previousFundingDatetime': undefined,
'interval': '1h',
};
}
parseTicker(ticker, market = undefined) {
//
// {
// "prevDayPx": "3400.5",
// "dayNtlVlm": "511297257.47936022",
// "markPx": "3464.7",
// "midPx": "3465.05",
// "oraclePx": "3460.1", // only in swap
// "openInterest": "64638.1108", // only in swap
// "premium": "0.00141614", // only in swap
// "funding": "0.00008727", // only in swap
// "impactPxs": [ "3465.0", "3465.1" ], // only in swap
// "coin": "PURR", // only in spot
// "circulatingSupply": "998949190.03400207", // only in spot
// },
//
const bidAsk = this.safeList(ticker, 'impactPxs');
return this.safeTicker({
'symbol': market['symbol'],
'timestamp': undefined,
'datetime': undefined,
'previousClose': this.safeNumber(ticker, 'prevDayPx'),
'close': this.safeNumber(ticker, 'midPx'),
'bid': this.safeNumber(bidAsk, 0),
'ask': this.safeNumber(bidAsk, 1),
'quoteVolume': this.safeNumber(ticker, 'dayNtlVlm'),
'info': ticker,
}, market);
}
/**
* @method
* @name hyperliquid#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @see https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint#candle-snapshot
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents, support '1m', '15m', '1h', '1d'
* @param {int} [since] timestamp in ms of the earliest candle to fetch
* @param {int} [limit] the maximum amount of candles to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.until] timestamp in ms of the latest candle to fetch
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const until = this.safeInteger(params, 'until', this.milliseconds());
let useTail = since === undefined;
const originalSince = since;
if (since === undefined) {
if (limit !== undefined) {
// optimization if limit is provided
const timeframeInMilliseconds = this.parseTimeframe(timeframe) * 1000;
since = this.sum(until, timeframeInMilliseconds * limit * -1);
useTail = false;
}
else {
since = 0;
}
}
params = this.omit(params, ['until']);
const request = {
'type': 'candleSnapshot',
'req': {
'coin': market['swap'] ? market['baseName'] : market['id'],
'interval': this.safeString(this.timeframes, timeframe, timeframe),
'startTime': since,
'endTime': until,
},
};
const response = await this.publicPostInfo(this.extend(request, params));
//
// [
// {
// "T": 1704287699999,
// "c": "2226.4",
// "h": "2247.9",
// "i": "15m",
// "l": "2224.6",
// "n": 46,
// "o": "2247.9",
// "s": "ETH",
// "t": 1704286800000,
// "v": "591.6427"
// }
// ]
//
return this.parseOHLCVs(response, market, timeframe, originalSince, limit, useTail);
}
parseOHLCV(ohlcv, market = undefined) {
//
// {
// "T": 1704287699999,
// "c": "2226.4",
// "h": "2247.9",
// "i": "15m",
// "l": "2224.6",
// "n": 46,
// "o": "2247.9",
// "s": "ETH",
// "t": 1704286800000,
// "v": "591.6427"
// }
//
return [
this.safeInteger(ohlcv, 't'),
this.safeNumber(ohlcv, 'o'),
this.safe