From 8e8edaca2cd3ebf1aad8c4043b01d77749d0feae Mon Sep 17 00:00:00 2001 From: JJ-Cro Date: Mon, 9 Nov 2026 09:19:36 +0100 Subject: [PATCH 1/2] feat(v1.0.3): add perpetual trailing stop orders - Add submitTrailingStop with typed requests and responses. - Sign trailing stop actions in the documented field order. - Add a testnet trailing stop example. - Bump the SDK patch version to 1.0.3. --- examples/Rest/rest-trailing-stop.ts | 41 ++++++++++++++++++++++++++++ package-lock.json | 4 +-- package.json | 2 +- src/RestClient.ts | 16 +++++++++++ src/lib/action-factory.ts | 9 ++++++ src/types/request/exchange.types.ts | 11 ++++++++ src/types/response/exchange.types.ts | 4 +++ 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 examples/Rest/rest-trailing-stop.ts diff --git a/examples/Rest/rest-trailing-stop.ts b/examples/Rest/rest-trailing-stop.ts new file mode 100644 index 0000000..ea3f74b --- /dev/null +++ b/examples/Rest/rest-trailing-stop.ts @@ -0,0 +1,41 @@ +import { RestClient } from '../../src/index.js'; + +// or +// import { RestClient } from '@siebly/hyperliquid-api'; + +/** + * Place a reduce-only trailing stop for an existing ETH long on testnet. + * Trailing stops are available for perpetuals only. + * + * Unix: + * HYPERLIQUID_PRIVATE_KEY='' npx tsx examples/Rest/rest-trailing-stop.ts + */ +const privateKey = process.env.HYPERLIQUID_PRIVATE_KEY; +if (!privateKey) { + throw new Error('Missing HYPERLIQUID_PRIVATE_KEY.'); +} + +const client = new RestClient({ + privateKey, + testnet: true, +}); + +async function main() { + try { + const asset = await client.resolveAsset('ETH'); + const result = await client.submitTrailingStop({ + asset: asset.assetId, + isBuy: false, + sz: '0.01', + reduceOnly: true, + retracement: { pct: '1.234%' }, + // For a fixed price distance, use retracement: { px: '100' }. + activationPx: null, + }); + console.log('trailing stop:', JSON.stringify(result, null, 2)); + } catch (e) { + console.error('request failed: ', e); + } +} + +main(); diff --git a/package-lock.json b/package-lock.json index a411c56..82e9045 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@siebly/hyperliquid-api", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@siebly/hyperliquid-api", - "version": "1.0.2", + "version": "1.0.3", "license": "MIT", "dependencies": { "@msgpack/msgpack": "^3.1.2", diff --git a/package.json b/package.json index e68eded..95c4246 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@siebly/hyperliquid-api", - "version": "1.0.2", + "version": "1.0.3", "description": "Professional JavaScript & Node.js SDK for Hyperliquid's REST APIs and WebSockets, built in TypeScript with strong end to end tests.", "scripts": { "clean": "node scripts/clean.mjs", diff --git a/src/RestClient.ts b/src/RestClient.ts index ba7dd84..0bd6f41 100644 --- a/src/RestClient.ts +++ b/src/RestClient.ts @@ -36,6 +36,7 @@ import type { SubmitMarketCloseRequest, SubmitMarketOrderRequest, SubmitOrderRequest, + SubmitTrailingStopRequest, SubmitTwapOrderRequest, SubmitUserOutcomeRequest, TransferSpotToOrFromSubAccountRequest, @@ -70,6 +71,7 @@ import type { CancelResponseData, ExchangeResponse, OrderResponseData, + TrailingStopResponseData, TwapCancelResponseData, TwapOrderResponseData, } from './types/response/exchange.types.js'; @@ -1138,6 +1140,20 @@ export class RestClient extends BaseRestClient { return this.l1('twapCancel', params, options); } + /** + * Place a trailing stop order for perpetuals only (`trailingStop`). + * + * `asset` is a numeric asset ID. `retracement` is a percent string such as + * `{ pct: '1.234%' }` or a price distance such as `{ px: '100' }`. + * A successful request returns `{ oid }`. + */ + submitTrailingStop( + params: SubmitTrailingStopRequest, + options: L1RequestOptions = {}, + ): Promise> { + return this.l1('trailingStop', params, options); + } + /** * Schedule a cancel-all, or clear one (`scheduleCancel`). * diff --git a/src/lib/action-factory.ts b/src/lib/action-factory.ts index 434b6de..4008562 100644 --- a/src/lib/action-factory.ts +++ b/src/lib/action-factory.ts @@ -107,6 +107,15 @@ const ACTION_FIELD_ORDER: Readonly> = { ], subAccountTransfer: ['type', 'subAccountUser', 'isDeposit', 'usd'], topUpIsolatedOnlyMargin: ['type', 'asset', 'leverage'], + trailingStop: [ + 'type', + 'asset', + 'isBuy', + 'sz', + 'reduceOnly', + 'retracement', + 'activationPx', + ], twapCancel: ['type', 'a', 't'], twapOrder: ['type', 'twap'], updateIsolatedMargin: ['type', 'asset', 'isBuy', 'ntli'], diff --git a/src/types/request/exchange.types.ts b/src/types/request/exchange.types.ts index b1c61cc..d5d1cb5 100644 --- a/src/types/request/exchange.types.ts +++ b/src/types/request/exchange.types.ts @@ -114,6 +114,17 @@ export interface CancelTwapOrderRequest { t: number; } +export interface SubmitTrailingStopRequest { + activationPx: string | null; + /** Numeric perpetual asset ID. */ + asset: number; + isBuy: boolean; + reduceOnly: boolean; + /** Percent string such as "1.234%", or a price distance. */ + retracement: { pct: string } | { px: string }; + sz: string; +} + export interface TransferUsdToOrFromSubAccountRequest { isDeposit: boolean; subAccountUser: string; diff --git a/src/types/response/exchange.types.ts b/src/types/response/exchange.types.ts index 68ad4f5..1e1279f 100644 --- a/src/types/response/exchange.types.ts +++ b/src/types/response/exchange.types.ts @@ -70,3 +70,7 @@ export interface TwapOrderResponseData { export interface TwapCancelResponseData { status: 'success' | TwapErrorStatus; } + +export interface TrailingStopResponseData { + oid: number; +} From e5a12ef958d26d90704f106424a447a2fa948094 Mon Sep 17 00:00:00 2001 From: JJ-Cro sync bot Date: Mon, 9 Nov 2026 09:20:23 +0100 Subject: [PATCH 2/2] chore: regenerate endpoint docs and examples --- docs/endpointFunctionList.md | 277 +- .../apidoc/RestClient/submitTrailingStop.js | 23 + llms.txt | 4773 +++++++++-------- 3 files changed, 2576 insertions(+), 2497 deletions(-) create mode 100644 examples/apidoc/RestClient/submitTrailingStop.js diff --git a/docs/endpointFunctionList.md b/docs/endpointFunctionList.md index adbd801..f6e7c64 100644 --- a/docs/endpointFunctionList.md +++ b/docs/endpointFunctionList.md @@ -52,141 +52,142 @@ WebsocketAPIClient extends RestClient and exposes the same methods over Hyperliq | Function | AUTH | HTTP Method | Endpoint | | -------- | :------: | :------: | -------- | -| [getAllMids()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L198) | | POST | `/info (allMids)` | -| [getActiveAssetData()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L206) | :closed_lock_with_key: | POST | `/info (activeAssetData)` | -| [getAllBorrowLendReserveStates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L217) | | POST | `/info (allBorrowLendReserveStates)` | -| [getAllPerpMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L227) | | POST | `/info (allPerpMetas)` | -| [getApprovedBuilders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L234) | :closed_lock_with_key: | POST | `/info (approvedBuilders)` | -| [getBorrowLendReserveState()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L241) | | POST | `/info (borrowLendReserveState)` | -| [getUserBorrowLendState()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L252) | :closed_lock_with_key: | POST | `/info (borrowLendUserState)` | -| [getCandles()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L264) | | POST | `/info (candleSnapshot)` | -| [getPerpAccountSummary()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L277) | :closed_lock_with_key: | POST | `/info (clearinghouseState)` | -| [getStakingDelegations()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L286) | :closed_lock_with_key: | POST | `/info (delegations)` | -| [getStakingHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L297) | :closed_lock_with_key: | POST | `/info (delegatorHistory)` | -| [getStakingRewards()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L308) | :closed_lock_with_key: | POST | `/info (delegatorRewards)` | -| [getStakingSummary()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L315) | :closed_lock_with_key: | POST | `/info (delegatorSummary)` | -| [getExchangeStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L319) | | POST | `/info (exchangeStatus)` | -| [getExtraAgents()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L323) | :closed_lock_with_key: | POST | `/info (extraAgents)` | -| [getFrontendOpenOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L332) | :closed_lock_with_key: | POST | `/info (frontendOpenOrders)` | -| [getFundingHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L341) | | POST | `/info (fundingHistory)` | -| [getGossipPriorityAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L350) | | POST | `/info (gossipPriorityAuctionStatus)` | -| [getGossipRootIps()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L354) | | POST | `/info (gossipRootIps)` | -| [getOrderHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L363) | :closed_lock_with_key: | POST | `/info (historicalOrders)` | -| [getVipStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L367) | :closed_lock_with_key: | POST | `/info (isVip)` | -| [getOrderBook()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L377) | | POST | `/info (l2Book)` | -| [getLeadingVaults()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L386) | :closed_lock_with_key: | POST | `/info (leadingVaults)` | -| [getLegalStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L390) | :closed_lock_with_key: | POST | `/info (legalCheck)` | -| [getLiquidatablePositions()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L394) | | POST | `/info (liquidatable)` | -| [getMarginTable()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L398) | | POST | `/info (marginTable)` | -| [getMaxBuilderFee()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L407) | :closed_lock_with_key: | POST | `/info (maxBuilderFee)` | -| [getMaxMarketOrderNotionals()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L411) | | POST | `/info (maxMarketOrderNtls)` | -| [getPerpMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L420) | | POST | `/info (meta)` | -| [getPerpAssetContexts()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L431) | | POST | `/info (metaAndAssetCtxs)` | -| [getOpenOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L443) | :closed_lock_with_key: | POST | `/info (openOrders)` | -| [getOrderStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L453) | :closed_lock_with_key: | POST | `/info (orderStatus)` | -| [getOutcomeMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L460) | | POST | `/info (outcomeMeta)` | -| [getOutcomeDeployerLimits()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L469) | | POST | `/info (outcomeDeployerLimits)` | -| [getPerpAnnotation()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L480) | | POST | `/info (perpAnnotation)` | -| [getPerpCategories()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L489) | | POST | `/info (perpCategories)` | -| [getConcisePerpAnnotations()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L498) | | POST | `/info (perpConciseAnnotations)` | -| [getPerpDeployAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L505) | | POST | `/info (perpDeployAuctionStatus)` | -| [getPerpDexLimits()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L514) | | POST | `/info (perpDexLimits)` | -| [getPerpDexStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L523) | | POST | `/info (perpDexStatus)` | -| [getPerpDexs()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L533) | | POST | `/info (perpDexs)` | -| [getPerpsAtOpenInterestCap()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L542) | | POST | `/info (perpsAtOpenInterestCap)` | -| [getPortfolio()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L552) | :closed_lock_with_key: | POST | `/info (portfolio)` | -| [getPreTransferCheck()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L556) | | POST | `/info (preTransferCheck)` | -| [getPredictedFundingRates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L569) | | POST | `/info (predictedFundings)` | -| [getRecentTrades()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L573) | | POST | `/info (recentTrades)` | -| [getReferralInfo()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L584) | :closed_lock_with_key: | POST | `/info (referral)` | -| [getSettledOutcome()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L593) | | POST | `/info (settledOutcome)` | -| [getBalances()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L599) | :closed_lock_with_key: | POST | `/info (clearinghouseState, spotClearinghouseState)` | -| [getSpotBalances()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L620) | :closed_lock_with_key: | POST | `/info (spotClearinghouseState)` | -| [getSpotDeployAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L627) | :closed_lock_with_key: | POST | `/info (spotDeployState)` | -| [getSpotMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L636) | | POST | `/info (spotMeta)` | -| [getSpotAssetContexts()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L646) | | POST | `/info (spotMetaAndAssetCtxs)` | -| [getSpotPairDeployAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L653) | | POST | `/info (spotPairDeployAuctionStatus)` | -| [getSubAccounts()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L662) | :closed_lock_with_key: | POST | `/info (subAccounts)` | -| [getSubAccountsWithAllDexStates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L666) | :closed_lock_with_key: | POST | `/info (subAccounts2)` | -| [getTokenDetails()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L678) | | POST | `/info (tokenDetails)` | -| [getTwapHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L682) | :closed_lock_with_key: | POST | `/info (twapHistory)` | -| [getUserAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L689) | :closed_lock_with_key: | POST | `/info (userAbstraction)` | -| [getUserBorrowLendInterest()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L693) | :closed_lock_with_key: | POST | `/info (userBorrowLendInterest)` | -| [getUserDexAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L702) | :closed_lock_with_key: | POST | `/info (userDexAbstraction)` | -| [getUserFees()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L709) | :closed_lock_with_key: | POST | `/info (userFees)` | -| [getUserFills()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L719) | :closed_lock_with_key: | POST | `/info (userFills)` | -| [getUserFillsByTime()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L729) | :closed_lock_with_key: | POST | `/info (userFillsByTime)` | -| [getUserFundingHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L738) | :closed_lock_with_key: | POST | `/info (userFunding)` | -| [getUserNonFundingLedgerUpdates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L751) | :closed_lock_with_key: | POST | `/info (userNonFundingLedgerUpdates)` | -| [getUserRateLimit()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L763) | :closed_lock_with_key: | POST | `/info (userRateLimit)` | -| [getUserRole()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L773) | | POST | `/info (userRole)` | -| [getMultiSigSigners()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L777) | :closed_lock_with_key: | POST | `/info (userToMultiSigSigners)` | -| [getUserTwapSliceFills()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L788) | :closed_lock_with_key: | POST | `/info (userTwapSliceFills)` | -| [getUserTwapSliceFillsByTime()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L792) | :closed_lock_with_key: | POST | `/info (userTwapSliceFillsByTime)` | -| [getUserVaultDeposits()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L801) | :closed_lock_with_key: | POST | `/info (userVaultEquities)` | -| [getValidatorL1Votes()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L807) | | POST | `/info (validatorL1Votes)` | -| [getValidatorSummaries()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L811) | | POST | `/info (validatorSummaries)` | -| [getVaultDetails()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L821) | | POST | `/info (vaultDetails)` | -| [getVaultSummaries()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L827) | | POST | `/info (vaultSummaries)` | -| [getWebData2()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L831) | :closed_lock_with_key: | POST | `/info (webData2)` | -| [getAlignedQuoteTokenStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L836) | | POST | `/info (alignedQuoteTokenInfo)` | -| [submitOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L848) | :closed_lock_with_key: | POST | `/exchange (order)` | -| [submitMarketOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L864) | :closed_lock_with_key: | POST | `/exchange (order)` | -| [submitMarketClose()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L919) | :closed_lock_with_key: | POST | `/exchange (order)` | -| [submitLimitOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L963) | :closed_lock_with_key: | POST | `/exchange (order)` | -| [modifyOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1005) | :closed_lock_with_key: | POST | `/exchange (modify)` | -| [modifyOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1021) | :closed_lock_with_key: | POST | `/exchange (batchModify)` | -| [cancelOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1040) | :closed_lock_with_key: | POST | `/exchange (cancel)` | -| [cancelOrdersByClientOrderId()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1058) | :closed_lock_with_key: | POST | `/exchange (cancelByCloid)` | -| [updateLeverage()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1074) | :closed_lock_with_key: | POST | `/exchange (updateLeverage)` | -| [updateIsolatedMargin()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1094) | :closed_lock_with_key: | POST | `/exchange (updateIsolatedMargin)` | -| [setIsolatedMarginLeverage()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1106) | :closed_lock_with_key: | POST | `/exchange (topUpIsolatedOnlyMargin)` | -| [submitTwapOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1122) | :closed_lock_with_key: | POST | `/exchange (twapOrder)` | -| [cancelTwapOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1134) | :closed_lock_with_key: | POST | `/exchange (twapCancel)` | -| [setCancelAllAfter()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1147) | :closed_lock_with_key: | POST | `/exchange (scheduleCancel)` | -| [setReferrer()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1154) | :closed_lock_with_key: | POST | `/exchange (setReferrer)` | -| [registerReferrer()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1160) | :closed_lock_with_key: | POST | `/exchange (registerReferrer)` | -| [approveApiWallet()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1198) | :closed_lock_with_key: | POST | `/exchange (approveAgent)` | -| [sendUsd()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1218) | :closed_lock_with_key: | POST | `/exchange (usdSend)` | -| [sendSpotAsset()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1235) | :closed_lock_with_key: | POST | `/exchange (spotSend)` | -| [withdrawUsd()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1250) | :closed_lock_with_key: | POST | `/exchange (withdraw3)` | -| [transferUsdBetweenSpotAndPerp()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1267) | :closed_lock_with_key: | POST | `/exchange (usdClassTransfer)` | -| [sendAsset()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1285) | :closed_lock_with_key: | POST | `/exchange (sendAsset)` | -| [sendToEvmWithData()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1306) | :closed_lock_with_key: | POST | `/exchange (sendToEvmWithData)` | -| [sendAssetFromApiWallet()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1323) | :closed_lock_with_key: | POST | `/exchange (agentSendAsset)` | -| [depositIntoStaking()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1344) | :closed_lock_with_key: | POST | `/exchange (cDeposit)` | -| [withdrawFromStaking()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1361) | :closed_lock_with_key: | POST | `/exchange (cWithdraw)` | -| [delegateOrUndelegateStake()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1378) | :closed_lock_with_key: | POST | `/exchange (tokenDelegate)` | -| [convertToMultiSigUser()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1390) | :closed_lock_with_key: | POST | `/exchange (convertToMultiSigUser)` | -| [linkStakingAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1413) | :closed_lock_with_key: | POST | `/exchange (linkStakingUser)` | -| [disableStakingTradingLink()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1425) | :closed_lock_with_key: | POST | `/exchange (stakingLinkDisableTradingUser)` | -| [setUserPortfolioMargin()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1437) | :closed_lock_with_key: | POST | `/exchange (userPortfolioMargin)` | -| [setUserAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1454) | :closed_lock_with_key: | POST | `/exchange (userSetAbstraction)` | -| [setUserDexAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1471) | :closed_lock_with_key: | POST | `/exchange (userDexAbstraction)` | -| [enableApiWalletDexAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1488) | :closed_lock_with_key: | POST | `/exchange (agentEnableDexAbstraction)` | -| [setApiWalletAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1499) | :closed_lock_with_key: | POST | `/exchange (agentSetAbstraction)` | -| [executeBorrowLendOperation()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1506) | :closed_lock_with_key: | POST | `/exchange (borrowLend)` | -| [claimRewards()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1527) | :closed_lock_with_key: | POST | `/exchange (claimRewards)` | -| [createSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1531) | :closed_lock_with_key: | POST | `/exchange (createSubAccount)` | -| [createVault()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1538) | :closed_lock_with_key: | POST | `/exchange (createVault)` | -| [setEvmBigBlocks()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1545) | :closed_lock_with_key: | POST | `/exchange (evmUserModify)` | -| [finalizeEvmContract()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1552) | :closed_lock_with_key: | POST | `/exchange (finalizeEvmContract)` | -| [submitGossipPriorityBid()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1565) | :closed_lock_with_key: | POST | `/exchange (gossipPriorityBid)` | -| [transferHip3LiquidatorFunds()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1578) | :closed_lock_with_key: | POST | `/exchange (hip3LiquidatorTransfer)` | -| [invalidateNonce()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1588) | :closed_lock_with_key: | POST | `/exchange (noop)` | -| [deployPerp()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1592) | :closed_lock_with_key: | POST | `/exchange (perpDeploy)` | -| [deploySpot()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1599) | :closed_lock_with_key: | POST | `/exchange (spotDeploy)` | -| [reserveAdditionalActions()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1611) | :closed_lock_with_key: | POST | `/exchange (reserveRequestWeight)` | -| [setDisplayName()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1618) | :closed_lock_with_key: | POST | `/exchange (setDisplayName)` | -| [setSpotDusting()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1625) | :closed_lock_with_key: | POST | `/exchange (spotUser)` | -| [renameSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1632) | :closed_lock_with_key: | POST | `/exchange (subAccountModify)` | -| [transferSpotToOrFromSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1639) | :closed_lock_with_key: | POST | `/exchange (subAccountSpotTransfer)` | -| [transferUsdToOrFromSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1650) | :closed_lock_with_key: | POST | `/exchange (subAccountTransfer)` | -| [transferVaultFunds()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1660) | :closed_lock_with_key: | POST | `/exchange (vaultTransfer)` | -| [distributeVaultFunds()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1667) | :closed_lock_with_key: | POST | `/exchange (vaultDistribute)` | -| [modifyVault()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1674) | :closed_lock_with_key: | POST | `/exchange (vaultModify)` | -| [updateValidatorL1Stream()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1698) | :closed_lock_with_key: | POST | `/exchange (validatorL1Stream)` | -| [submitSignerAction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1709) | :closed_lock_with_key: | POST | `/exchange (CSignerAction)` | -| [submitValidatorAction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1716) | :closed_lock_with_key: | POST | `/exchange (CValidatorAction)` | -| [authorizeAqav2Role()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1729) | :closed_lock_with_key: | POST | `/exchange (authorizeAqav2Role)` | -| [submitUserOutcome()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1744) | :closed_lock_with_key: | POST | `/exchange (userOutcome)` | \ No newline at end of file +| [getAllMids()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L200) | | POST | `/info (allMids)` | +| [getActiveAssetData()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L208) | :closed_lock_with_key: | POST | `/info (activeAssetData)` | +| [getAllBorrowLendReserveStates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L219) | | POST | `/info (allBorrowLendReserveStates)` | +| [getAllPerpMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L229) | | POST | `/info (allPerpMetas)` | +| [getApprovedBuilders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L236) | :closed_lock_with_key: | POST | `/info (approvedBuilders)` | +| [getBorrowLendReserveState()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L243) | | POST | `/info (borrowLendReserveState)` | +| [getUserBorrowLendState()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L254) | :closed_lock_with_key: | POST | `/info (borrowLendUserState)` | +| [getCandles()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L266) | | POST | `/info (candleSnapshot)` | +| [getPerpAccountSummary()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L279) | :closed_lock_with_key: | POST | `/info (clearinghouseState)` | +| [getStakingDelegations()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L288) | :closed_lock_with_key: | POST | `/info (delegations)` | +| [getStakingHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L299) | :closed_lock_with_key: | POST | `/info (delegatorHistory)` | +| [getStakingRewards()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L310) | :closed_lock_with_key: | POST | `/info (delegatorRewards)` | +| [getStakingSummary()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L317) | :closed_lock_with_key: | POST | `/info (delegatorSummary)` | +| [getExchangeStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L321) | | POST | `/info (exchangeStatus)` | +| [getExtraAgents()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L325) | :closed_lock_with_key: | POST | `/info (extraAgents)` | +| [getFrontendOpenOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L334) | :closed_lock_with_key: | POST | `/info (frontendOpenOrders)` | +| [getFundingHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L343) | | POST | `/info (fundingHistory)` | +| [getGossipPriorityAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L352) | | POST | `/info (gossipPriorityAuctionStatus)` | +| [getGossipRootIps()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L356) | | POST | `/info (gossipRootIps)` | +| [getOrderHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L365) | :closed_lock_with_key: | POST | `/info (historicalOrders)` | +| [getVipStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L369) | :closed_lock_with_key: | POST | `/info (isVip)` | +| [getOrderBook()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L379) | | POST | `/info (l2Book)` | +| [getLeadingVaults()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L388) | :closed_lock_with_key: | POST | `/info (leadingVaults)` | +| [getLegalStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L392) | :closed_lock_with_key: | POST | `/info (legalCheck)` | +| [getLiquidatablePositions()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L396) | | POST | `/info (liquidatable)` | +| [getMarginTable()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L400) | | POST | `/info (marginTable)` | +| [getMaxBuilderFee()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L409) | :closed_lock_with_key: | POST | `/info (maxBuilderFee)` | +| [getMaxMarketOrderNotionals()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L413) | | POST | `/info (maxMarketOrderNtls)` | +| [getPerpMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L422) | | POST | `/info (meta)` | +| [getPerpAssetContexts()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L433) | | POST | `/info (metaAndAssetCtxs)` | +| [getOpenOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L445) | :closed_lock_with_key: | POST | `/info (openOrders)` | +| [getOrderStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L455) | :closed_lock_with_key: | POST | `/info (orderStatus)` | +| [getOutcomeMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L462) | | POST | `/info (outcomeMeta)` | +| [getOutcomeDeployerLimits()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L471) | | POST | `/info (outcomeDeployerLimits)` | +| [getPerpAnnotation()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L482) | | POST | `/info (perpAnnotation)` | +| [getPerpCategories()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L491) | | POST | `/info (perpCategories)` | +| [getConcisePerpAnnotations()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L500) | | POST | `/info (perpConciseAnnotations)` | +| [getPerpDeployAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L507) | | POST | `/info (perpDeployAuctionStatus)` | +| [getPerpDexLimits()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L516) | | POST | `/info (perpDexLimits)` | +| [getPerpDexStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L525) | | POST | `/info (perpDexStatus)` | +| [getPerpDexs()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L535) | | POST | `/info (perpDexs)` | +| [getPerpsAtOpenInterestCap()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L544) | | POST | `/info (perpsAtOpenInterestCap)` | +| [getPortfolio()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L554) | :closed_lock_with_key: | POST | `/info (portfolio)` | +| [getPreTransferCheck()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L558) | | POST | `/info (preTransferCheck)` | +| [getPredictedFundingRates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L571) | | POST | `/info (predictedFundings)` | +| [getRecentTrades()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L575) | | POST | `/info (recentTrades)` | +| [getReferralInfo()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L586) | :closed_lock_with_key: | POST | `/info (referral)` | +| [getSettledOutcome()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L595) | | POST | `/info (settledOutcome)` | +| [getBalances()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L601) | :closed_lock_with_key: | POST | `/info (clearinghouseState, spotClearinghouseState)` | +| [getSpotBalances()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L622) | :closed_lock_with_key: | POST | `/info (spotClearinghouseState)` | +| [getSpotDeployAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L629) | :closed_lock_with_key: | POST | `/info (spotDeployState)` | +| [getSpotMetadata()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L638) | | POST | `/info (spotMeta)` | +| [getSpotAssetContexts()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L648) | | POST | `/info (spotMetaAndAssetCtxs)` | +| [getSpotPairDeployAuctionStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L655) | | POST | `/info (spotPairDeployAuctionStatus)` | +| [getSubAccounts()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L664) | :closed_lock_with_key: | POST | `/info (subAccounts)` | +| [getSubAccountsWithAllDexStates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L668) | :closed_lock_with_key: | POST | `/info (subAccounts2)` | +| [getTokenDetails()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L680) | | POST | `/info (tokenDetails)` | +| [getTwapHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L684) | :closed_lock_with_key: | POST | `/info (twapHistory)` | +| [getUserAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L691) | :closed_lock_with_key: | POST | `/info (userAbstraction)` | +| [getUserBorrowLendInterest()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L695) | :closed_lock_with_key: | POST | `/info (userBorrowLendInterest)` | +| [getUserDexAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L704) | :closed_lock_with_key: | POST | `/info (userDexAbstraction)` | +| [getUserFees()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L711) | :closed_lock_with_key: | POST | `/info (userFees)` | +| [getUserFills()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L721) | :closed_lock_with_key: | POST | `/info (userFills)` | +| [getUserFillsByTime()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L731) | :closed_lock_with_key: | POST | `/info (userFillsByTime)` | +| [getUserFundingHistory()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L740) | :closed_lock_with_key: | POST | `/info (userFunding)` | +| [getUserNonFundingLedgerUpdates()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L753) | :closed_lock_with_key: | POST | `/info (userNonFundingLedgerUpdates)` | +| [getUserRateLimit()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L765) | :closed_lock_with_key: | POST | `/info (userRateLimit)` | +| [getUserRole()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L775) | | POST | `/info (userRole)` | +| [getMultiSigSigners()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L779) | :closed_lock_with_key: | POST | `/info (userToMultiSigSigners)` | +| [getUserTwapSliceFills()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L790) | :closed_lock_with_key: | POST | `/info (userTwapSliceFills)` | +| [getUserTwapSliceFillsByTime()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L794) | :closed_lock_with_key: | POST | `/info (userTwapSliceFillsByTime)` | +| [getUserVaultDeposits()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L803) | :closed_lock_with_key: | POST | `/info (userVaultEquities)` | +| [getValidatorL1Votes()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L809) | | POST | `/info (validatorL1Votes)` | +| [getValidatorSummaries()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L813) | | POST | `/info (validatorSummaries)` | +| [getVaultDetails()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L823) | | POST | `/info (vaultDetails)` | +| [getVaultSummaries()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L829) | | POST | `/info (vaultSummaries)` | +| [getWebData2()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L833) | :closed_lock_with_key: | POST | `/info (webData2)` | +| [getAlignedQuoteTokenStatus()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L838) | | POST | `/info (alignedQuoteTokenInfo)` | +| [submitOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L850) | :closed_lock_with_key: | POST | `/exchange (order)` | +| [submitMarketOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L866) | :closed_lock_with_key: | POST | `/exchange (order)` | +| [submitMarketClose()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L921) | :closed_lock_with_key: | POST | `/exchange (order)` | +| [submitLimitOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L965) | :closed_lock_with_key: | POST | `/exchange (order)` | +| [modifyOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1007) | :closed_lock_with_key: | POST | `/exchange (modify)` | +| [modifyOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1023) | :closed_lock_with_key: | POST | `/exchange (batchModify)` | +| [cancelOrders()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1042) | :closed_lock_with_key: | POST | `/exchange (cancel)` | +| [cancelOrdersByClientOrderId()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1060) | :closed_lock_with_key: | POST | `/exchange (cancelByCloid)` | +| [updateLeverage()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1076) | :closed_lock_with_key: | POST | `/exchange (updateLeverage)` | +| [updateIsolatedMargin()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1096) | :closed_lock_with_key: | POST | `/exchange (updateIsolatedMargin)` | +| [setIsolatedMarginLeverage()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1108) | :closed_lock_with_key: | POST | `/exchange (topUpIsolatedOnlyMargin)` | +| [submitTwapOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1124) | :closed_lock_with_key: | POST | `/exchange (twapOrder)` | +| [cancelTwapOrder()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1136) | :closed_lock_with_key: | POST | `/exchange (twapCancel)` | +| [submitTrailingStop()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1150) | :closed_lock_with_key: | POST | `/exchange (trailingStop)` | +| [setCancelAllAfter()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1163) | :closed_lock_with_key: | POST | `/exchange (scheduleCancel)` | +| [setReferrer()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1170) | :closed_lock_with_key: | POST | `/exchange (setReferrer)` | +| [registerReferrer()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1176) | :closed_lock_with_key: | POST | `/exchange (registerReferrer)` | +| [approveApiWallet()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1214) | :closed_lock_with_key: | POST | `/exchange (approveAgent)` | +| [sendUsd()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1234) | :closed_lock_with_key: | POST | `/exchange (usdSend)` | +| [sendSpotAsset()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1251) | :closed_lock_with_key: | POST | `/exchange (spotSend)` | +| [withdrawUsd()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1266) | :closed_lock_with_key: | POST | `/exchange (withdraw3)` | +| [transferUsdBetweenSpotAndPerp()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1283) | :closed_lock_with_key: | POST | `/exchange (usdClassTransfer)` | +| [sendAsset()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1301) | :closed_lock_with_key: | POST | `/exchange (sendAsset)` | +| [sendToEvmWithData()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1322) | :closed_lock_with_key: | POST | `/exchange (sendToEvmWithData)` | +| [sendAssetFromApiWallet()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1339) | :closed_lock_with_key: | POST | `/exchange (agentSendAsset)` | +| [depositIntoStaking()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1360) | :closed_lock_with_key: | POST | `/exchange (cDeposit)` | +| [withdrawFromStaking()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1377) | :closed_lock_with_key: | POST | `/exchange (cWithdraw)` | +| [delegateOrUndelegateStake()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1394) | :closed_lock_with_key: | POST | `/exchange (tokenDelegate)` | +| [convertToMultiSigUser()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1406) | :closed_lock_with_key: | POST | `/exchange (convertToMultiSigUser)` | +| [linkStakingAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1429) | :closed_lock_with_key: | POST | `/exchange (linkStakingUser)` | +| [disableStakingTradingLink()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1441) | :closed_lock_with_key: | POST | `/exchange (stakingLinkDisableTradingUser)` | +| [setUserPortfolioMargin()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1453) | :closed_lock_with_key: | POST | `/exchange (userPortfolioMargin)` | +| [setUserAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1470) | :closed_lock_with_key: | POST | `/exchange (userSetAbstraction)` | +| [setUserDexAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1487) | :closed_lock_with_key: | POST | `/exchange (userDexAbstraction)` | +| [enableApiWalletDexAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1504) | :closed_lock_with_key: | POST | `/exchange (agentEnableDexAbstraction)` | +| [setApiWalletAbstraction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1515) | :closed_lock_with_key: | POST | `/exchange (agentSetAbstraction)` | +| [executeBorrowLendOperation()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1522) | :closed_lock_with_key: | POST | `/exchange (borrowLend)` | +| [claimRewards()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1543) | :closed_lock_with_key: | POST | `/exchange (claimRewards)` | +| [createSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1547) | :closed_lock_with_key: | POST | `/exchange (createSubAccount)` | +| [createVault()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1554) | :closed_lock_with_key: | POST | `/exchange (createVault)` | +| [setEvmBigBlocks()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1561) | :closed_lock_with_key: | POST | `/exchange (evmUserModify)` | +| [finalizeEvmContract()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1568) | :closed_lock_with_key: | POST | `/exchange (finalizeEvmContract)` | +| [submitGossipPriorityBid()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1581) | :closed_lock_with_key: | POST | `/exchange (gossipPriorityBid)` | +| [transferHip3LiquidatorFunds()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1594) | :closed_lock_with_key: | POST | `/exchange (hip3LiquidatorTransfer)` | +| [invalidateNonce()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1604) | :closed_lock_with_key: | POST | `/exchange (noop)` | +| [deployPerp()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1608) | :closed_lock_with_key: | POST | `/exchange (perpDeploy)` | +| [deploySpot()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1615) | :closed_lock_with_key: | POST | `/exchange (spotDeploy)` | +| [reserveAdditionalActions()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1627) | :closed_lock_with_key: | POST | `/exchange (reserveRequestWeight)` | +| [setDisplayName()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1634) | :closed_lock_with_key: | POST | `/exchange (setDisplayName)` | +| [setSpotDusting()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1641) | :closed_lock_with_key: | POST | `/exchange (spotUser)` | +| [renameSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1648) | :closed_lock_with_key: | POST | `/exchange (subAccountModify)` | +| [transferSpotToOrFromSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1655) | :closed_lock_with_key: | POST | `/exchange (subAccountSpotTransfer)` | +| [transferUsdToOrFromSubAccount()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1666) | :closed_lock_with_key: | POST | `/exchange (subAccountTransfer)` | +| [transferVaultFunds()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1676) | :closed_lock_with_key: | POST | `/exchange (vaultTransfer)` | +| [distributeVaultFunds()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1683) | :closed_lock_with_key: | POST | `/exchange (vaultDistribute)` | +| [modifyVault()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1690) | :closed_lock_with_key: | POST | `/exchange (vaultModify)` | +| [updateValidatorL1Stream()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1714) | :closed_lock_with_key: | POST | `/exchange (validatorL1Stream)` | +| [submitSignerAction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1725) | :closed_lock_with_key: | POST | `/exchange (CSignerAction)` | +| [submitValidatorAction()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1732) | :closed_lock_with_key: | POST | `/exchange (CValidatorAction)` | +| [authorizeAqav2Role()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1745) | :closed_lock_with_key: | POST | `/exchange (authorizeAqav2Role)` | +| [submitUserOutcome()](https://github.com/sieblyio/hyperliquid-api/blob/main/src/RestClient.ts#L1760) | :closed_lock_with_key: | POST | `/exchange (userOutcome)` | \ No newline at end of file diff --git a/examples/apidoc/RestClient/submitTrailingStop.js b/examples/apidoc/RestClient/submitTrailingStop.js new file mode 100644 index 0000000..ca5e3c3 --- /dev/null +++ b/examples/apidoc/RestClient/submitTrailingStop.js @@ -0,0 +1,23 @@ +import { RestClient } from '@siebly/hyperliquid-api'; +// or, if require is preferred: +// const { RestClient } = require('@siebly/hyperliquid-api'); + +// This example shows how to call this Hyperliquid API endpoint with either node.js, javascript (js) or typescript (ts) with the npm module "@siebly/hyperliquid-api" for Hyperliquid exchange +// This Hyperliquid API SDK is available on npm via "npm install @siebly/hyperliquid-api" +// ENDPOINT: /exchange (trailingStop) +// METHOD: POST +// PUBLIC: NO + +const client = new RestClient({ + privateKey: 'insert_private_key_here', + // If this is an API/agent wallet, also pass the master account: + // accountAddress: '0x...', +}); + +client.submitTrailingStop(params) + .then((response) => { + console.log(response); + }) + .catch((error) => { + console.error(error); + }); diff --git a/llms.txt b/llms.txt index 35f308c..45bf15a 100644 --- a/llms.txt +++ b/llms.txt @@ -62,6 +62,7 @@ examples/ rest-schedule-cancel.ts rest-spot-order.ts rest-tpsl.ts + rest-trailing-stop.ts Websocket/ WS-API/ ws-api-client.ts @@ -109,6 +110,26 @@ tsconfig.linting.json Files ================================================================ +================ +File: examples/Rest/rest-trailing-stop.ts +================ +import { RestClient } from '../../src/index.js'; +⋮---- +// or +// import { RestClient } from '@siebly/hyperliquid-api'; +⋮---- +/** + * Place a reduce-only trailing stop for an existing ETH long on testnet. + * Trailing stops are available for perpetuals only. + * + * Unix: + * HYPERLIQUID_PRIVATE_KEY='' npx tsx examples/Rest/rest-trailing-stop.ts + */ +⋮---- +async function main() +⋮---- +// For a fixed price distance, use retracement: { px: '100' }. + ================ File: scripts/clean.mjs ================ @@ -1066,6 +1087,35 @@ import { DefaultLogger, WebsocketClient } from '../../src/index.js'; ⋮---- async function main() +================ +File: examples/Websocket/ws-public.ts +================ +import { DefaultLogger, WebsocketClient } from '../../src/index.js'; +⋮---- +// or +// import { DefaultLogger, WebsocketClient } from '@siebly/hyperliquid-api'; +⋮---- +/** + * Public market-data subscriptions. + * + * Listen for data with on('message') and acknowledgements with on('response'). + * Every event includes the connection's wsKey. + * + * npx tsx examples/Websocket/ws-public.ts + */ +⋮---- +// Uncomment to log connection lifecycle events: +⋮---- +// testnet: true, +⋮---- +async function main() +⋮---- +// Native Spot universe names pass through without metadata requests. +⋮---- +// For a readable alias, resolve it explicitly before subscribing: +// const coin = await wsClient.nameToCoin('HYPE/USDC'); +// await wsClient.subscribe({ type: 'trades', coin }); + ================ File: src/types/request/exchange.types.ts ================ @@ -1185,6 +1235,21 @@ export interface CancelTwapOrderRequest { t: number; } ⋮---- +export interface SubmitTrailingStopRequest { + activationPx: string | null; + /** Numeric perpetual asset ID. */ + asset: number; + isBuy: boolean; + reduceOnly: boolean; + /** Percent string such as "1.234%", or a price distance. */ + retracement: { pct: string } | { px: string }; + sz: string; +} +⋮---- +/** Numeric perpetual asset ID. */ +⋮---- +/** Percent string such as "1.234%", or a price distance. */ +⋮---- export interface TransferUsdToOrFromSubAccountRequest { isDeposit: boolean; subAccountUser: string; @@ -1354,2328 +1419,2303 @@ export interface TwapOrderResponseData { export interface TwapCancelResponseData { status: 'success' | TwapErrorStatus; } +⋮---- +export interface TrailingStopResponseData { + oid: number; +} ================ -File: docs/HYPERLIQUID_SDK_QUICKSTART_GUIDE.md +File: src/types/websockets/ws-general.ts ================ - - -# Hyperliquid API JavaScript Tutorial for Node.js - - - -> [!TIP] -> Read this guide in tutorial format on the Siebly website: [Hyperliquid JavaScript REST API and WebSocket Tutorial](https://siebly.io/sdk/hyperliquid/javascript/tutorial) - - - -This tutorial uses [`@siebly/hyperliquid-api`](https://www.npmjs.com/package/@siebly/hyperliquid-api), Siebly's Node.js and JavaScript SDK for Hyperliquid. It covers public market data, account state, perpetual and Spot orders, public and account WebSocket streams, WebSocket API requests, Testnet, network routing, and proxies. - -The SDK handles asset resolution, action signing, exact wire formatting, nonce allocation, WebSocket heartbeats, reconnects, resubscriptions, and WebSocket request matching. TypeScript declarations are included for projects that use them. - -**Key links** - -- Hyperliquid JavaScript SDK by Siebly: [`@siebly/hyperliquid-api`](https://siebly.io/sdk/hyperliquid/javascript) -- npm package: [`@siebly/hyperliquid-api`](https://www.npmjs.com/package/@siebly/hyperliquid-api) -- GitHub repository: [`sieblyio/hyperliquid-api`](https://github.com/sieblyio/hyperliquid-api) -- SDK examples: [Hyperliquid SDK examples](https://siebly.io/examples/Hyperliquid) -- Repository examples: [JavaScript and TypeScript examples](../examples) -- Hyperliquid API documentation: [Official API docs](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) -- Trading-system terms: [Siebly glossary](https://siebly.io/reference/glossary) -- More JavaScript and TypeScript SDKs: [Siebly.io](https://siebly.io) - -## Why use `@siebly/hyperliquid-api`? - -Hyperliquid uses one Info endpoint for market and account reads, one Exchange endpoint for signed actions, and one WebSocket endpoint for both subscriptions and request-response traffic. Asset identifiers and signing formats also vary by operation. - -`@siebly/hyperliquid-api` presents those surfaces through three clients: - -| Client | Use it for | -| --- | --- | -| `RestClient` | Market data, account state, orders, transfers, staking, vaults, subaccounts, and other REST API operations | -| `WebsocketClient` | Public market-data and address-scoped account subscriptions | -| `WebsocketAPIClient` | The same awaitable Info and signed Exchange methods over Hyperliquid's WebSocket POST protocol | - -`RestClient` also provides higher-level helpers such as `submitMarketOrder()`, `submitLimitOrder()`, `submitMarketClose()`, and `getBalances()`. Lower-level methods remain available for batching, TP/SL orders, transfers, vault operations, and other advanced workflows. - -## Install the SDK - -```bash -npm install @siebly/hyperliquid-api -``` - -Node.js 22 or newer is supported. Every example below is plain JavaScript. - -## Set up a wallet and API wallet - -Public market data and account-state reads do not require a private key. Hyperliquid account state is queried by public address. - -Orders and other Exchange actions require a signer. For an automated process, create and approve an API wallet from the [Hyperliquid API page](https://app.hyperliquid.xyz/API). Hyperliquid also calls these agent wallets. - -Keep two values separate: - -- The API wallet private key signs actions. -- The master account, subaccount, or vault address identifies the account being managed. - -Store them in a server-side environment or secret manager: - -- `HYPERLIQUID_API_WALLET_KEY` -- `HYPERLIQUID_ACCOUNT_ADDRESS` - -For Testnet examples, use separate values: - -- `HYPERLIQUID_TESTNET_API_WALLET_KEY` -- `HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS` - -An API wallet address is not the account address. Passing the API wallet address to account queries usually returns empty state. See Hyperliquid's [nonces and API wallets](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets) documentation for the full model. - -Use one API wallet per trading process. If several processes or subaccounts share a signer, they also share its nonce set. Separate API wallets avoid nonce collisions and make process ownership clearer. - -Never put a private key in browser code, logs, or committed configuration. - -## Hyperliquid products and request vocabulary - -| Field or value | Example | Meaning | -| --- | --- | --- | -| Perpetual symbol | `BTC` | A perpetual market from `getPerpMetadata()` | -| Spot pair | `HYPE/USDC` | A readable Spot alias resolved from current metadata | -| Native Spot name | `PURR/USDC` or `@107` | The name used by HyperCore for a Spot pair | -| HIP-3 symbol | `dex:COIN` | A market on a HIP-3 perpetual DEX | -| Outcome asset | `#10` | An outcome-market identifier | -| `szDecimals` | `5` | The allowed number of decimal places in order size | -| `side` | `buy` or `sell` | The side accepted by the SDK's order helpers | -| `clientOrderId` | `0x` plus 32 hex characters | Your [custom order ID](https://siebly.io/reference/glossary#custom-order-id), called `cloid` by Hyperliquid | -| `accountAddress` | `HYPERLIQUID_ACCOUNT_ADDRESS` | The master account, subaccount, or vault whose state is queried | -| `vaultAddress` | `HYPERLIQUID_VAULT_ADDRESS` | Optional routing for actions signed on behalf of a vault or subaccount | -| `wsKey` | `public`, `api`, or `user:
` | The [WebSocket key](https://siebly.io/reference/glossary#ws-key) included with every emitted event | - -Perpetual sizes are quantities of the underlying asset. Spot sizes are quantities of the base asset. Prices, sizes, transfer amounts, and trigger prices are exact decimal strings in SDK requests. - -Hyperliquid prices may use up to five significant figures, subject to the asset's decimal limit. Order sizes use the asset's `szDecimals`. Read the current [tick and lot-size rules](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size) before constructing values yourself. - -Info responses are endpoint-specific. Some methods return objects, some return arrays, and `getOrderBook()` may return `null` for an unavailable book. There is no shared success envelope for Info responses. - - - -## Start building: first REST API calls - -Run each example on its own. Start with public market data, then read account state and inspect asset metadata. - -### 1. Make public REST API calls - -Public Info calls need no wallet or API key. - - - -```javascript -import { RestClient } from '@siebly/hyperliquid-api'; - -const client = new RestClient(); - -async function main() { - try { - const status = await client.getExchangeStatus(); - console.log('Exchange status:', status); - } catch (error) { - console.error('Exchange-status request failed:', error); - } - - try { - const mids = await client.getAllMids(); - console.log('BTC midpoint:', mids.BTC); - } catch (error) { - console.error('Midpoint request failed:', error); - } - - try { - const metadata = await client.getPerpMetadata(); - const btc = metadata.universe.find((asset) => asset.name === 'BTC'); - console.log('BTC perpetual metadata:', btc); - } catch (error) { - console.error('Perpetual metadata request failed:', error); - } - - try { - const orderBook = await client.getOrderBook({ - coin: 'BTC', - }); - console.log('Best bid:', orderBook?.levels[0][0]); - console.log('Best ask:', orderBook?.levels[1][0]); - } catch (error) { - console.error('Order-book request failed:', error); - } - - try { - const candles = await client.getCandles({ - coin: 'BTC', - interval: '1h', - startTime: Date.now() - 24 * 60 * 60 * 1000, - }); - console.log('Latest hourly candle:', candles.at(-1)); - } catch (error) { - console.error('Candle request failed:', error); - } +import type { + RestClientNetworkOptions, + RestClientOptions, +} from '../shared.types.js'; +import type { + WebsocketMessageEvent, + WebsocketResponseEvent, +} from './ws-events.js'; +import type { WebSocketConstructor, WebSocketLike } from './ws-portable.js'; +⋮---- +export type WsKey = 'api' | 'public' | `user:${string}`; +⋮---- +export interface WebsocketClientOptions { + assetCacheTtlMs?: number; + /** Reconnect automatically after an unexpected disconnect. Default: true. */ + autoReconnect?: boolean; + baseUrl?: string; + /** Maximum time to open a connection, in milliseconds. Default: 1,000. */ + connectionTimeout?: number; + /** Maximum reconnect delay in milliseconds. Default: 10,000. Zero means no delay. */ + maxReconnectTimeout?: number; + /** Ping interval in milliseconds. Default: 10,000. Zero disables pings. */ + pingInterval?: number; + /** Pong deadline in milliseconds. Default: 1,000. Zero disables the deadline. */ + pongTimeout?: number; + /** Initial reconnect delay in milliseconds. Default: 500. Zero means no delay. */ + reconnectTimeout?: number; + /** Maximum request duration in milliseconds, including connection setup. Default: 10,000. */ + requestTimeoutMs?: number; + requestOptions?: RestClientNetworkOptions; + testnet?: boolean; + webSocketConstructor?: WebSocketConstructor; + wsUrl?: string; +} +⋮---- +/** Reconnect automatically after an unexpected disconnect. Default: true. */ +⋮---- +/** Maximum time to open a connection, in milliseconds. Default: 1,000. */ +⋮---- +/** Maximum reconnect delay in milliseconds. Default: 10,000. Zero means no delay. */ +⋮---- +/** Ping interval in milliseconds. Default: 10,000. Zero disables pings. */ +⋮---- +/** Pong deadline in milliseconds. Default: 1,000. Zero disables the deadline. */ +⋮---- +/** Initial reconnect delay in milliseconds. Default: 500. Zero means no delay. */ +⋮---- +/** Maximum request duration in milliseconds, including connection setup. Default: 10,000. */ +⋮---- +export type WebsocketAPIClientOptions = RestClientOptions & + Omit; +⋮---- +export type WebsocketLifecycleEvent = + 'close' | 'exception' | 'open' | 'reconnected' | 'reconnecting'; +⋮---- +export interface WebsocketLifecycleContext { + wsKey: TWsKey; + event?: unknown; +} +⋮---- +export interface WebsocketConnectionContext< + TWsKey extends string = WsKey, +> extends WebsocketLifecycleContext { + ws: WebSocketLike; + wsUrl: string; +} +⋮---- +export interface WebsocketExceptionEvent< + TWsKey extends string = WsKey, +> extends WebsocketLifecycleContext { + channel?: 'error' | 'post'; + data?: unknown; +} +⋮---- +/** Argument tuples for the standard EventEmitter API. */ +export interface WebsocketClientEventMap { + open: [event: WebsocketConnectionContext]; + reconnecting: [event: WebsocketLifecycleContext]; + reconnected: [event: WebsocketConnectionContext]; + close: [event: WebsocketLifecycleContext]; + message: [event: WebsocketMessageEvent]; + response: [event: WebsocketResponseEvent]; + exception: [event: WebsocketExceptionEvent]; +} +⋮---- +export type WebsocketLifecycleCallback = ( + context: WebsocketLifecycleContext, +) => void; - try { - const trades = await client.getRecentTrades({ - coin: 'BTC', - }); - console.log('Most recent trade:', trades[0]); - } catch (error) { - console.error('Recent-trade request failed:', error); - } +================ +File: src/types/websockets/ws-subscriptions.ts +================ +import type { + ActiveAssetData, + Candle, + HistoricalOrder, + LeadingVault, + OpenOrder, + OrderBookResponse, + OrderState, + PerpAccountSummaryResponse, + PerpAssetContext, + SpotAssetContext, + SpotBalancesResponse, + Trade, + TwapHistoryEntry, + TwapSliceFill, + TwapState, + UserFill, + UserNonFundingLedgerUpdate, +} from '../response/info.types.js'; +⋮---- +export type CandleInterval = + | '1d' + | '12h' + | '15m' + | '1h' + | '1m' + | '1M' + | '2h' + | '30m' + | '3d' + | '3m' + | '4h' + | '5m' + | '8h' + | '1w'; +⋮---- +export type UserSubscriptionType = + | 'activeAssetData' + | 'allDexsClearinghouseState' + | 'clearinghouseState' + | 'notification' + | 'openOrders' + | 'orderUpdates' + | 'spotState' + | 'twapStates' + | 'userEvents' + | 'userFills' + | 'userFundings' + | 'userHistoricalOrders' + | 'userNonFundingLedgerUpdates' + | 'userTwapHistory' + | 'userTwapSliceFills' + | 'webData3'; +⋮---- +export type WebsocketSubscription = + | { dex?: string; type: 'allMids' } + | { dex?: string; type: 'assetCtxs' } + | { coin: string; type: 'activeAssetCtx' } + | { coin: string; user: string; type: 'activeAssetData' } + | { type: 'allDexsAssetCtxs' } + | { user: string; type: 'allDexsClearinghouseState' } + | { coin: string; type: 'bbo' } + | { coin: string; interval: CandleInterval; type: 'candle' } + | { dex?: string; user: string; type: 'clearinghouseState' } + | { type: 'fastAssetCtxs' } + | { + coin: string; + fast?: boolean; + mantissa?: 1 | 2 | 5 | null; + nSigFigs?: 2 | 3 | 4 | 5 | null; + type: 'l2Book'; + } + | { user: string; type: 'notification' } + | { dex?: string; user: string; type: 'openOrders' } + | { user: string; type: 'orderUpdates' } + | { type: 'outcomeMetaUpdates' } + | { type: 'spotAssetCtxs' } + | { + ignorePortfolioMargin?: boolean; + isPortfolioMargin?: never; + user: string; + type: 'spotState'; + } + | { + ignorePortfolioMargin?: never; + isPortfolioMargin?: boolean; + user: string; + type: 'spotState'; + } + | { coin: string; type: 'trades' } + | { dex?: string; user: string; type: 'twapStates' } + | { user: string; type: 'userEvents' } + | { aggregateByTime?: boolean; user: string; type: 'userFills' } + | { user: string; type: 'userFundings' } + | { user: string; type: 'userHistoricalOrders' } + | { user: string; type: 'userNonFundingLedgerUpdates' } + | { user: string; type: 'userTwapHistory' } + | { user: string; type: 'userTwapSliceFills' } + | { user: string; type: 'webData3' }; +⋮---- +export interface AllMidsEvent { + dex?: string; + mids: Record; +} +⋮---- +export interface AssetContextEvent { + coin: string; + ctx: PerpAssetContext | SpotAssetContext; } - -main(); -``` - -The response shapes reflect the requested data: - -- `getExchangeStatus()` returns an object with `time` and `specialStatuses`. -- `getAllMids()` returns an object whose keys are asset names and whose values are decimal strings. -- `getPerpMetadata()` returns an object with `universe`, `marginTables`, and `collateralToken`. -- `getOrderBook()` returns `coin`, `time`, and two arrays under `levels`. Index `0` contains bids and index `1` contains asks. Each level has `px`, `sz`, and `n`. -- `getCandles()` returns candle objects with fields such as `t`, `T`, `o`, `c`, `h`, `l`, `v`, and `n`. -- `getRecentTrades()` returns an array. `side: 'B'` is a buy and `side: 'A'` is a sell. - -### 2. Read account state by address - -Balances, positions, orders, and fills are Info queries keyed by a public address. No signer is needed for these reads. - - - -```javascript -import { RestClient } from '@siebly/hyperliquid-api'; - -const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; - -if (!accountAddress) { - throw new Error('Set HYPERLIQUID_ACCOUNT_ADDRESS before starting.'); +⋮---- +export interface ActivePerpAssetContextEvent extends AssetContextEvent { + ctx: PerpAssetContext; } - -const client = new RestClient({ accountAddress }); - -async function main() { - try { - const balances = await client.getBalances(); - console.log('Account:', balances.user); - console.log('Spot balances:', balances.spot.balances); - console.log('Perpetual positions:', balances.perp.assetPositions); - } catch (error) { - console.error('Balance request failed:', error); - } - - try { - const openOrders = await client.getOpenOrders(); - console.log('Open orders:', openOrders); - } catch (error) { - console.error('Open-order request failed:', error); - } - - try { - const fills = await client.getUserFills({ - aggregateByTime: true, - }); - console.log('Recent fills:', fills); - } catch (error) { - console.error('Fill request failed:', error); - } - - try { - const funding = await client.getUserFundingHistory({ - startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, - }); - console.log('Recent funding payments:', funding); - } catch (error) { - console.error('Funding-history request failed:', error); - } +⋮---- +export interface ActiveSpotAssetContextEvent extends AssetContextEvent { + ctx: SpotAssetContext; } - -main(); -``` - -`getBalances()` combines the Spot clearinghouse state and the default perpetual clearinghouse state. The perpetual response includes margin summaries, `withdrawable`, and `assetPositions`. A signed position size is available as `position.szi`: positive is long, negative is short, and zero is flat. Under unified account or portfolio margin, Spot state is the relevant balance view. This helper does not aggregate every HIP-3 DEX. - -A client configured with `accountAddress` may omit `user` from account getters. A public client can instead pass `{ user: accountAddress }` to each account method. - -Time-ranged Info methods have response limits. Paginate long histories by moving `startTime` to the last returned timestamp. See the official [Info endpoint documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint) for each method's current limit. - -### 3. Resolve perpetual, Spot, HIP-3, and outcome assets - -The SDK loads current Mainnet or Testnet metadata when an operation needs an asset ID. The cache lasts five minutes by default. - - - -```javascript -import { RestClient } from '@siebly/hyperliquid-api'; - -const client = new RestClient(); - -async function main() { - try { - const btcPerpetual = await client.resolveAsset('BTC'); - console.log('BTC perpetual:', btcPerpetual); - } catch (error) { - console.error('Perpetual resolution failed:', error); - } - - try { - const hypeSpot = await client.resolveAsset('HYPE/USDC'); - console.log('HYPE/USDC Spot:', hypeSpot); - } catch (error) { - console.error('Spot resolution failed:', error); - } - - try { - const nativeSpotName = await client.nameToCoin('HYPE/USDC'); - console.log('Native Spot name:', nativeSpotName); - } catch (error) { - console.error('Spot-name lookup failed:', error); - } - - try { - await client.refreshAssetMetadata(); - console.log('Asset metadata refreshed.'); - } catch (error) { - console.error('Metadata refresh failed:', error); - } +⋮---- +export interface AssetCtxsEvent { + ctxs: PerpAssetContext[]; + dex: string; } - -main(); -``` - -`resolveAsset()` returns the numeric `assetId`, canonical name, asset kind, and `szDecimals` where available. It accepts current metadata names, readable Spot aliases, numeric IDs, HIP-3 names such as `dex:COIN`, and outcome identifiers such as `#10`. - -Asset IDs differ across Mainnet and Testnet. Resolve them against the same network that will receive the request. Do not add `-PERP` or `-SPOT` suffixes. - -`getOrderBook()`, `getCandles()`, and `getFundingHistory()` apply Hyperliquid's native name mapping where required. For example, a readable Spot alias such as `HYPE/USDC` resolves to its current `@` name, while `PURR/USDC` is already a native universe name. Streaming subscriptions, `getRecentTrades()`, and `getActiveAssetData()` expect an official coin name. Call `nameToCoin()` explicitly when starting with a readable Spot alias, then pass the result as `coin`. Subscribing with a native coin name does not make a REST API metadata request. - - - -## Build with Hyperliquid WebSocket streams - -`WebsocketClient` manages connections, JSON parsing, heartbeats, reconnects, and resubscriptions. Subscriptions are objects that match Hyperliquid's documented channel types. - -### 4. Subscribe to public market data - - - -```javascript -import { WebsocketClient } from '@siebly/hyperliquid-api'; - -const client = new WebsocketClient(); - -async function main() { - client - .on('open', ({ wsKey }) => { - console.log('WebSocket opened:', wsKey); - }) - .on('reconnecting', ({ wsKey, event }) => { - console.log('WebSocket reconnecting:', wsKey, event); - }) - .on('reconnected', ({ wsKey }) => { - console.log('WebSocket reconnected:', wsKey); - }) - .on('close', ({ wsKey }) => { - console.log('WebSocket closed:', wsKey); - }) - .on('exception', ({ wsKey, event, data }) => { - console.error('WebSocket exception:', wsKey, event ?? data); - }); - - client.on('message', ({ channel, data, wsKey }) => { - switch (channel) { - case 'allMids': - console.log('BTC midpoint:', wsKey, data.mids.BTC); - break; - case 'l2Book': - console.log('Best bid:', data.levels[0][0]); - console.log('Best ask:', data.levels[1][0]); - break; - case 'trades': - console.log('Trades:', data); - break; - case 'candle': - console.log('One-minute candle:', data); - break; - } - }); - - client.on('response', ({ channel, data, wsKey }) => { - if (channel === 'subscriptionResponse') { - console.log('Subscription acknowledgement:', wsKey, data); - } - }); - - try { - await client.subscribe([ - { type: 'allMids' }, - { type: 'l2Book', coin: 'BTC' }, - { type: 'trades', coin: 'BTC' }, - { type: 'candle', coin: 'BTC', interval: '1m' }, - ]); - } catch (error) { - console.error('Market subscription failed:', error); - } +⋮---- +export interface AllDexsAssetCtxsEvent { + ctxs: Array<[dex: string, contexts: PerpAssetContext[]]>; } - -main(); -``` - -Hyperliquid sends a [subscription acknowledgement](https://siebly.io/reference/glossary#subscription-acknowledgement) when the subscription is registered. The SDK emits that protocol message through `response`. Market and account updates arrive through `message`, preserving the full `{ channel, data, wsKey }` envelope. - -Register listeners before subscribing. Each incoming data message is emitted once, and every registered `message` listener receives it. Filter by `channel` and the payload's coin or user when consuming several feeds. - -The promise returned by `subscribe()` resolves after the socket opens and the request is sent. It does not wait for the server acknowledgement, so do not use that promise alone as a stream-readiness signal. - -Some channels send an initial snapshot before later updates. `l2Book` sends a current book, while `allMids`, trades, and candles continue with new values. The SDK sends regular heartbeat pings to check connection health. - -`subscribe()` and `unsubscribe()` accept one topic or an array of topics. Equivalent topics are deduplicated by value. Listeners are independent of subscriptions: use `off('message', listener)` to remove a listener and `unsubscribe(topic)` to stop a stream. Call `client.closeAll()` when the application needs to close its WebSocket connections. - -`connectionTimeout` defaults to 1,000 ms. Heartbeats use a 10,000 ms ping interval and a 1,000 ms pong deadline. Increase `connectionTimeout` or `pongTimeout` for a higher-latency connection. Reconnect delays start at 500 ms and double up to 10,000 ms. - -#### Subscribe to a Spot market - -Resolve a readable Spot pair once, then subscribe with its native coin name. The lookup uses the REST API; `subscribe()` itself only sends WebSocket messages. - -```javascript -import { WebsocketClient } from '@siebly/hyperliquid-api'; - -const client = new WebsocketClient(); - -client.on('message', ({ channel, data }) => { - if (channel === 'l2Book') { - console.log('Spot book:', data.coin, data.levels); - } -}); - -client.on('exception', ({ wsKey, event, data }) => { - console.error('WebSocket exception:', wsKey, event ?? data); -}); - -async function main() { - let coin; - - try { - coin = await client.nameToCoin('HYPE/USDC'); - } catch (error) { - console.error('Spot-name lookup failed:', error); - return; - } - - try { - await client.subscribe({ type: 'l2Book', coin }); - } catch (error) { - console.error('Spot subscription failed:', error); - } +⋮---- +export interface ClearinghouseStateEvent { + clearinghouseState: PerpAccountSummaryResponse; + dex: string; + user: string; } - -main(); -``` - -### 5. Subscribe to account updates - -Account subscriptions use the account's public address. They do not need a signer. - - - -```javascript -import { WebsocketClient } from '@siebly/hyperliquid-api'; - -const user = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; - -if (!user) { - throw new Error('Set HYPERLIQUID_ACCOUNT_ADDRESS before starting.'); +⋮---- +export interface AllDexsClearinghouseStateEvent { + clearinghouseStates: Array<[dex: string, state: PerpAccountSummaryResponse]>; + user: string; } - -const client = new WebsocketClient(); - -async function main() { - client - .on('open', ({ wsKey }) => { - console.log('WebSocket opened:', wsKey); - }) - .on('reconnected', ({ wsKey }) => { - console.log('WebSocket reconnected:', wsKey); - }) - .on('exception', ({ wsKey, event, data }) => { - console.error('WebSocket exception:', wsKey, event ?? data); - }); - - client.on('message', ({ channel, data, wsKey }) => { - switch (channel) { - case 'orderUpdates': - console.log('Order updates:', wsKey, data); - break; - case 'userFills': - console.log('Fill snapshot:', data.isSnapshot === true); - console.log('Fills:', data.fills); - break; - case 'clearinghouseState': - console.log('Perpetual state:', data.clearinghouseState); - break; - case 'spotState': - console.log('Spot state:', data.spotState); - break; - } - }); - - try { - await client.subscribe([ - { type: 'orderUpdates', user }, - { type: 'userFills', user, aggregateByTime: true }, - { type: 'clearinghouseState', user }, - { type: 'spotState', user }, - ]); - } catch (error) { - console.error('Account subscription failed:', error); - } +⋮---- +export type ActiveAssetDataEvent = ActiveAssetData; +⋮---- +export interface BboEvent { + bbo: [ + bid: { n: number; px: string; sz: string } | null, + offer: { n: number; px: string; sz: string } | null, + ]; + coin: string; + time: number; } - -main(); -``` - -`userFills`, `userFundings`, and similar time-series streams tag their initial payload with `isSnapshot: true`. Use the snapshot to initialize state, then process later payloads as changes. Avoid applying the same snapshot twice after a reconnect. - -The `orderUpdates` stream reports order transitions. Use it with a REST API query when reconciling [pending confirmation](https://siebly.io/reference/glossary#pending-confirmation) after an uncertain order or cancellation response. - -Every lifecycle, message, response, and exception event includes `wsKey`. Most subscriptions share `public`. `orderUpdates`, `notification`, and `userEvents` use `user:` connections because their server messages omit the address. Use that key to identify the account. The `userEvents` subscription emits messages on Hyperliquid's `user` channel. - - - -## Place and manage Testnet orders - -Use Hyperliquid Testnet to run the following workflows. Create and approve an API wallet from the [Testnet API page](https://app.hyperliquid-testnet.xyz/API), fund the account from the [Testnet faucet](https://app.hyperliquid-testnet.xyz/drip), then set `HYPERLIQUID_TESTNET_API_WALLET_KEY` and `HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS`. The faucet currently requires the same address to have deposited on Mainnet. - -The SDK signs Exchange actions with the API wallet and sends account queries for the configured account address. It lowercases EVM addresses and formats action fields in the order required by Hyperliquid's signing rules. - -Order responses need two checks: - -1. `status` must be `ok`. -2. Each entry under `response.data.statuses` must be inspected. - -An order status entry may contain `resting`, `filled`, or `error`. A successful request can still contain an order-specific error. - -The SDK makes a passive, one-time attempt to apply the `SIEBLY` referral code around the first trading action from a client instance when the account has no existing referrer. It does not replace an existing referrer, delay the order, or retry the referral request. Hyperliquid's current [referral terms](https://hyperliquid.gitbook.io/hyperliquid-docs/referrals) describe any user discount. - -### 6. Run a perpetual REST API order lifecycle - -This example reads current BTC metadata and book data, places a post-only Testnet order, queries it, cancels it by client order ID, and queries it again. - - - -```javascript -import { randomBytes } from 'node:crypto'; -import { RestClient } from '@siebly/hyperliquid-api'; - -const privateKey = process.env.HYPERLIQUID_TESTNET_API_WALLET_KEY; -const accountAddress = process.env.HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS; - -if (!privateKey || !accountAddress) { - throw new Error( - 'Set HYPERLIQUID_TESTNET_API_WALLET_KEY and HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS before starting.', - ); +⋮---- +export type TradeEvent = Trade; +⋮---- +export interface NotificationEvent { + notification: string; } - -const client = new RestClient({ - privateKey, - accountAddress, - testnet: true, -}); - -function trimDecimal(value) { - if (!value.includes('.')) { - return value; - } - return value.replace(/0+$/, '').replace(/\.$/, ''); +⋮---- +export interface OrderUpdateEvent { + order: OpenOrder; + status: OrderState; + statusTimestamp: number; } - -function sizeForNotional(price, szDecimals, targetNotional) { - const factor = 10 ** szDecimals; - const size = Math.ceil((targetNotional / Number(price)) * factor) / factor; - return trimDecimal(size.toFixed(szDecimals)); +⋮---- +export interface FastAssetCtxsEvent { + [coin: string]: { + markPx?: string; + midPx?: string | null; + }; } - -async function main() { - let btc; - let orderBook; - - try { - const metadata = await client.getPerpMetadata(); - btc = metadata.universe.find((asset) => asset.name === 'BTC'); - console.log('BTC metadata:', btc); - } catch (error) { - console.error('Perpetual metadata request failed:', error); - return; - } - - if (!btc) { - console.error('BTC was not returned by the Testnet perpetual metadata.'); - return; - } - - try { - orderBook = await client.getOrderBook({ coin: 'BTC' }); - console.log('BTC order book received at:', orderBook?.time); - } catch (error) { - console.error('Order-book request failed:', error); - return; - } - - const bid = orderBook?.levels[0][4] ?? orderBook?.levels[0][0]; - - if (!bid) { - console.error('The BTC order book did not contain a bid.'); - return; - } - - const clientOrderId = `0x${randomBytes(16).toString('hex')}`; - const size = sizeForNotional(bid.px, btc.szDecimals, 12); - let orderId; - - try { - const result = await client.submitLimitOrder({ - symbol: 'BTC', - side: 'buy', - price: bid.px, - size, - postOnly: true, - clientOrderId, - }); - console.log('Order response:', result); - - if (result.status !== 'ok') { - console.error('Hyperliquid rejected the order request:', result.response); - return; - } - - const orderStatus = result.response.data?.statuses[0]; - - if (!orderStatus) { - console.error('The order response did not contain an order status.'); - return; - } - - if ('error' in orderStatus) { - console.error('The order was rejected:', orderStatus.error); - return; - } - - if ('filled' in orderStatus) { - console.log('The order filled immediately:', orderStatus.filled); - return; - } - - orderId = orderStatus.resting.oid; - console.log('Resting order ID:', orderId); - } catch (error) { - console.error('Order submission failed:', error); - return; - } - - try { - const currentOrder = await client.getOrderStatus({ oid: orderId }); - console.log('Order before cancellation:', currentOrder); - } catch (error) { - console.error('Order-status request failed:', error); - } - - try { - const cancellation = await client.cancelOrdersByClientOrderId({ - cancels: [{ asset: 'BTC', cloid: clientOrderId }], - }); - console.log('Cancellation response:', cancellation); - } catch (error) { - console.error('Cancellation failed:', error); - return; - } - - try { - const finalOrder = await client.getOrderStatus({ oid: clientOrderId }); - console.log('Final order state:', finalOrder); - } catch (error) { - console.error('Final order-status request failed:', error); - } +⋮---- +export type SpotAssetCtxsEvent = SpotAssetContext[]; +⋮---- +export interface OutcomeMetaUpdatesEvent { + updates: Array< + | { + outcomeCreated: { + description: string; + name: string; + outcome: number; + quoteToken: string; + sideSpecs: Array<{ name: string; token?: number }>; + }; + } + | { outcomeSettled: { outcome: number } } + | { + questionUpdated: { + description: string; + fallbackOutcome: number; + name: string; + namedOutcomes: number[]; + question: number; + settledNamedOutcomes: number[]; + }; + } + | { questionSettled: { question: number } } + >; } - -main(); -``` - -The fifth bid is already a valid Hyperliquid price and normally remains below the best ask, which makes it suitable for an add-liquidity-only order. The size targets at least 12 USDC of notional and rounds up to the asset's `szDecimals`, keeping it above Hyperliquid's current 10 USDC minimum order value. - -`postOnly: true` maps to Hyperliquid's `Alo` time in force. Without `postOnly`, `submitLimitOrder()` defaults to good-till-cancelled. Set `timeInForce: 'IOC'` for immediate-or-cancel. - -The `clientOrderId` is 16 random bytes encoded as a 32-character hexadecimal string. Keep it with the local order record. It can be used with `getOrderStatus()` and `cancelOrdersByClientOrderId()` when the numeric exchange order ID is not yet known. - -### 7. Run a Spot REST API order lifecycle - -Spot orders use the same helper, but the symbol resolves to a Spot asset ID and size means base-asset quantity. This example places a post-only PURR/USDC buy on Testnet. - - - -```javascript -import { randomBytes } from 'node:crypto'; -import { RestClient } from '@siebly/hyperliquid-api'; - -const privateKey = process.env.HYPERLIQUID_TESTNET_API_WALLET_KEY; -const accountAddress = process.env.HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS; - -if (!privateKey || !accountAddress) { - throw new Error( - 'Set HYPERLIQUID_TESTNET_API_WALLET_KEY and HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS before starting.', - ); +⋮---- +export interface UserFillsEvent { + fills: UserFill[]; + isSnapshot?: boolean; + user: string; } - -const client = new RestClient({ - privateKey, - accountAddress, - testnet: true, -}); - -function trimDecimal(value) { - if (!value.includes('.')) { - return value; - } - return value.replace(/0+$/, '').replace(/\.$/, ''); +⋮---- +export interface OpenOrdersEvent { + dex: string; + orders: OpenOrder[]; + user: string; } - -function sizeForNotional(price, szDecimals, targetNotional) { - const factor = 10 ** szDecimals; - const size = Math.ceil((targetNotional / Number(price)) * factor) / factor; - return trimDecimal(size.toFixed(szDecimals)); +⋮---- +export interface UserFundingsEvent { + fundings: Array<{ + coin: string; + fundingRate: string; + nSamples: number | null; + szi: string; + time: number; + usdc: string; + }>; + isSnapshot?: boolean; + user: string; } - -async function main() { - let asset; - let orderBook; - - try { - asset = await client.resolveAsset('PURR/USDC'); - console.log('PURR/USDC asset:', asset); - } catch (error) { - console.error('Spot asset resolution failed:', error); - return; - } - - if (asset.szDecimals === undefined) { - console.error('PURR/USDC size metadata was not available.'); - return; - } - - try { - orderBook = await client.getOrderBook({ coin: 'PURR/USDC' }); - console.log('PURR/USDC order book received at:', orderBook?.time); - } catch (error) { - console.error('Spot order-book request failed:', error); - return; - } - - const bid = orderBook?.levels[0][4] ?? orderBook?.levels[0][0]; - - if (!bid) { - console.error('The PURR/USDC order book did not contain a bid.'); - return; - } - - const clientOrderId = `0x${randomBytes(16).toString('hex')}`; - const size = sizeForNotional(bid.px, asset.szDecimals, 12); - let orderId; - - try { - const result = await client.submitLimitOrder({ - symbol: 'PURR/USDC', - side: 'buy', - price: bid.px, - size, - postOnly: true, - clientOrderId, - }); - console.log('Spot order response:', result); - - if (result.status !== 'ok') { - console.error('Hyperliquid rejected the order request:', result.response); - return; - } - - const orderStatus = result.response.data?.statuses[0]; - - if (!orderStatus) { - console.error('The order response did not contain an order status.'); - return; - } - - if ('error' in orderStatus) { - console.error('The order was rejected:', orderStatus.error); - return; - } - - if ('filled' in orderStatus) { - console.log('The order filled immediately:', orderStatus.filled); - return; - } - - orderId = orderStatus.resting.oid; - console.log('Resting Spot order ID:', orderId); - } catch (error) { - console.error('Spot order submission failed:', error); - return; - } - - try { - const currentOrder = await client.getOrderStatus({ oid: orderId }); - console.log('Spot order before cancellation:', currentOrder); - } catch (error) { - console.error('Spot order-status request failed:', error); - } - - try { - const cancellation = await client.cancelOrdersByClientOrderId({ - cancels: [{ asset: 'PURR/USDC', cloid: clientOrderId }], - }); - console.log('Spot cancellation response:', cancellation); - } catch (error) { - console.error('Spot cancellation failed:', error); - return; - } - - try { - const finalOrder = await client.getOrderStatus({ oid: clientOrderId }); - console.log('Final Spot order state:', finalOrder); - } catch (error) { - console.error('Final Spot order-status request failed:', error); - } +⋮---- +export interface UserHistoricalOrdersEvent { + isSnapshot?: boolean; + orderHistory: HistoricalOrder[]; + user: string; } - -main(); -``` - -The account needs enough Testnet USDC to reserve the order. A Spot sell needs the corresponding base asset. Hyperliquid may return internal token names in balances, so use the names returned by the API when reconciling funds. - -### Market orders, closing positions, and leverage - -`submitMarketOrder()` is an immediate-or-cancel limit order with a protection price. When `price` is omitted, the SDK loads the current midpoint, applies `maxSlippage`, rounds the protection price to Hyperliquid's rules, and rounds size down to `szDecimals`. - -Call `submitMarketOrder()` with `symbol`, `side`, `size`, and an optional `maxSlippage`. For example, a BTC buy can use `{ symbol: 'BTC', side: 'buy', size: '0.001', maxSlippage: 0.01 }`. - -`submitMarketClose()` reads the current perpetual position, chooses the closing side, and submits a reduce-only immediate-or-cancel order. Omit `size` to close the full position. - -Call `submitMarketClose()` with the perpetual symbol and an optional `maxSlippage`. Add `size` when only part of the position should be closed. - -Call `updateLeverage()` with `{ asset: 'BTC', isCross: true, leverage: 3 }` to select three-times cross leverage for BTC. Use the current asset metadata and account limits when choosing leverage. - -These are signed production actions on Mainnet and signed test actions on Testnet. Read current account and market state before calling them. - - - -## Send Info and Exchange requests with the WebSocket API - -`WebsocketAPIClient` exposes the same promise-based methods as `RestClient`, but sends supported Info and Exchange requests through Hyperliquid's WebSocket POST protocol. - -The SDK opens the connection when needed, assigns a unique request ID, matches the response to the promise, and rejects protocol errors. The raw `id` and response wrapper do not need to be managed by application code. - -This example reads market data, submits a post-only BTC Testnet order, and cancels it through the WebSocket API. - - - -```javascript -import { randomBytes } from 'node:crypto'; -import { WebsocketAPIClient } from '@siebly/hyperliquid-api'; - -const privateKey = process.env.HYPERLIQUID_TESTNET_API_WALLET_KEY; -const accountAddress = process.env.HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS; - -if (!privateKey || !accountAddress) { - throw new Error( - 'Set HYPERLIQUID_TESTNET_API_WALLET_KEY and HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS before starting.', - ); +⋮---- +export interface UserNonFundingLedgerUpdatesEvent { + isSnapshot?: boolean; + nonFundingLedgerUpdates: UserNonFundingLedgerUpdate[]; + user: string; +} +⋮---- +export interface UserTwapHistoryEvent { + history: TwapHistoryEntry[]; + isSnapshot?: boolean; + user: string; +} +⋮---- +export interface UserTwapSliceFillsEvent { + isSnapshot?: boolean; + twapSliceFills: TwapSliceFill[]; + user: string; } - -const client = new WebsocketAPIClient({ - privateKey, - accountAddress, - testnet: true, -}); - -function trimDecimal(value) { - if (!value.includes('.')) { - return value; - } - return value.replace(/0+$/, '').replace(/\.$/, ''); +⋮---- +export interface SpotStateEvent { + spotState: SpotBalancesResponse; + user: string; } - -function sizeForNotional(price, szDecimals, targetNotional) { - const factor = 10 ** szDecimals; - const size = Math.ceil((targetNotional / Number(price)) * factor) / factor; - return trimDecimal(size.toFixed(szDecimals)); +⋮---- +export interface TwapStatesEvent { + dex: string; + states: Array<[twapId: number, state: TwapState]>; + user: string; } - -async function main() { - client - .on('open', ({ wsKey }) => { - console.log('WebSocket API opened:', wsKey); - }) - .on('reconnected', ({ wsKey }) => { - console.log('WebSocket API reconnected:', wsKey); - }) - .on('exception', ({ wsKey, event, data }) => { - console.error('WebSocket API exception:', wsKey, event ?? data); - }); - - let btc; - let orderBook; - - try { - const metadata = await client.getPerpMetadata(); - btc = metadata.universe.find((asset) => asset.name === 'BTC'); - console.log('BTC metadata:', btc); - } catch (error) { - console.error('WebSocket metadata request failed:', error); - client.closeAll(); - return; - } - - if (!btc) { - console.error('BTC was not returned by the Testnet perpetual metadata.'); - client.closeAll(); - return; - } - - try { - orderBook = await client.getOrderBook({ coin: 'BTC' }); - console.log('BTC order book received at:', orderBook?.time); - } catch (error) { - console.error('WebSocket order-book request failed:', error); - client.closeAll(); - return; - } - - const bid = orderBook?.levels[0][4] ?? orderBook?.levels[0][0]; - - if (!bid) { - console.error('The BTC order book did not contain a bid.'); - client.closeAll(); - return; - } - - const clientOrderId = `0x${randomBytes(16).toString('hex')}`; - const size = sizeForNotional(bid.px, btc.szDecimals, 12); - let orderId; - - try { - const result = await client.submitLimitOrder({ - symbol: 'BTC', - side: 'buy', - price: bid.px, - size, - postOnly: true, - clientOrderId, - }); - console.log('WebSocket order response:', result); - - if (result.status !== 'ok') { - console.error('Hyperliquid rejected the order request:', result.response); - client.closeAll(); - return; - } - - const orderStatus = result.response.data?.statuses[0]; - - if (!orderStatus) { - console.error('The order response did not contain an order status.'); - client.closeAll(); - return; - } - - if ('error' in orderStatus) { - console.error('The order was rejected:', orderStatus.error); - client.closeAll(); - return; +⋮---- +export type UserEvent = + | { fills: UserFill[] } + | { + funding: { + coin: string; + fundingRate: string; + nSamples: number | null; + szi: string; + time: number; + usdc: string; + }; } - - if ('filled' in orderStatus) { - console.log('The order filled immediately:', orderStatus.filled); - client.closeAll(); - return; + | { + liquidation: { + lid: number; + liquidated_account_value: string; + liquidated_ntl_pos: string; + liquidated_user: string; + liquidator: string; + }; } - - orderId = orderStatus.resting.oid; - console.log('Resting order ID:', orderId); - } catch (error) { - console.error('WebSocket order submission failed:', error); - client.closeAll(); - return; - } - - try { - const currentOrder = await client.getOrderStatus({ oid: orderId }); - console.log('Order before cancellation:', currentOrder); - } catch (error) { - console.error('WebSocket order-status request failed:', error); - } - - try { - const cancellation = await client.cancelOrdersByClientOrderId({ - cancels: [{ asset: 'BTC', cloid: clientOrderId }], - }); - console.log('WebSocket cancellation response:', cancellation); - } catch (error) { - console.error('WebSocket cancellation failed:', error); - } - - try { - const finalOrder = await client.getOrderStatus({ oid: clientOrderId }); - console.log('Final WebSocket order state:', finalOrder); - } catch (error) { - console.error('Final WebSocket order-status request failed:', error); - } - - client.closeAll(); + | { nonUserCancel: Array<{ coin: string; oid: number }> } + | { twapHistory: TwapHistoryEntry[] } + | { twapSliceFills: TwapSliceFill[] }; +⋮---- +export interface WebData3PerpDexState { + leadingVaults?: LeadingVault[]; + perpsAtOpenInterestCap?: string[]; + totalVaultEquity: string; } - -main(); -``` - -WebSocket POST supports Info requests and signed Exchange actions. Explorer requests are not supported. Use `WebsocketClient` for subscriptions and `WebsocketAPIClient` for awaitable request-response calls. - -Successful POST replies also emit `response` events with `channel: 'post'` and `wsKey: 'api'`. Protocol errors and top-level Exchange rejections emit `exception` and reject the matching promise. An accepted action can still contain individual order errors, so inspect its status entries. Use `try`/`catch` for each command; event listeners provide additional connection and protocol diagnostics. - -`requestTimeoutMs` defaults to 10,000 ms and includes connection setup. Requests interrupted by a disconnect are rejected, not replayed after reconnection. Query the order's status before retrying a command whose outcome is uncertain. - -An order-command response acknowledges the action and reports its immediate status. Confirm later changes through `orderUpdates` or `getOrderStatus()`. That distinction is important when an order fills or is cancelled immediately after its first response. - -## Explore more REST API methods - -`RestClient` covers Hyperliquid's wider Info and Exchange surfaces. The following groups are a starting map rather than a complete list. - -### Market and account information - -| Workflow | Representative methods | -| --- | --- | -| Perpetual markets | `getPerpMetadata()`, `getPerpAssetContexts()`, `getFundingHistory()`, `getPredictedFundingRates()` | -| Spot markets | `getSpotMetadata()`, `getSpotAssetContexts()`, `getTokenDetails()` | -| Books and trades | `getOrderBook()`, `getAllMids()`, `getCandles()`, `getRecentTrades()` | -| Orders and fills | `getOpenOrders()`, `getFrontendOpenOrders()`, `getOrderStatus()`, `getOrderHistory()`, `getUserFills()` | -| Account and risk | `getPerpAccountSummary()`, `getSpotBalances()`, `getPortfolio()`, `getUserFees()`, `getUserRateLimit()` | -| HIP-3 perpetual DEXs | `getPerpDexs()`, `getPerpDexStatus()`, `getPerpDexLimits()`, `getAllPerpMetadata()` | -| Outcome markets | `getOutcomeMetadata()`, `getSettledOutcome()` | -| Vaults and staking | `getVaultDetails()`, `getVaultSummaries()`, `getStakingSummary()`, `getStakingDelegations()` | -| Borrow and lend | `getAllBorrowLendReserveStates()`, `getBorrowLendReserveState()`, `getUserBorrowLendState()` | -| Subaccounts and signers | `getSubAccounts()`, `getExtraAgents()`, `getMultiSigSigners()`, `getUserRole()` | - -### Signed actions - -| Workflow | Representative methods | -| --- | --- | -| Orders | `submitOrder()`, `submitMarketOrder()`, `submitLimitOrder()`, `modifyOrder()`, `cancelOrders()` | -| Position management | `submitMarketClose()`, `updateLeverage()`, `updateIsolatedMargin()`, `setIsolatedMarginLeverage()` | -| TWAP and safety | `submitTwapOrder()`, `cancelTwapOrder()`, `setCancelAllAfter()` | -| Transfers | `sendUsd()`, `sendSpotAsset()`, `sendAsset()`, `transferUsdBetweenSpotAndPerp()`, `withdrawUsd()` | -| Subaccounts and vaults | `createSubAccount()`, `transferUsdToOrFromSubAccount()`, `createVault()`, `transferVaultFunds()` | -| Staking | `depositIntoStaking()`, `withdrawFromStaking()`, `delegateOrUndelegateStake()`, `claimRewards()` | -| Account configuration | `approveApiWallet()`, `setUserPortfolioMargin()`, `setUserAbstraction()`, `setUserDexAbstraction()` | -| Advanced deployment | `deployPerp()`, `deploySpot()`, `submitUserOutcome()`, validator and multisig methods | - -Use the focused [repository examples](../examples) and exported request types when moving beyond the workflows in this guide. Transfers, withdrawals, staking, account configuration, deployer operations, and multisig actions change funds or account settings, so confirm their current official requirements before using them. - -### Batch orders, TP/SL, and dead-man switches - -Use `submitOrder()` when sending native wire-shaped batches or TP/SL orders. A batch is one request for IP limits but each order still counts toward address-based limits. Inspect every returned status, because errors usually align with individual batch entries. - -`setCancelAllAfter()` schedules cancellation of open orders if the account stops refreshing the deadline. Hyperliquid currently requires the trigger to be at least five seconds in the future and limits each account to ten scheduled-cancel triggers per UTC day. Refresh it from a monitored process, and call the method without `time` to remove the schedule when the strategy stops normally. - -Set `expiresAfter: Date.now() + 10_000` in the second argument of a signed method when an action should expire after ten seconds. This adds an action deadline. It does not replace order time in force or a dead-man switch. - -### Vaults and subaccounts - -Set `vaultAddress` on the client when all signed actions belong to one vault or subaccount: - -```javascript -import { RestClient } from '@siebly/hyperliquid-api'; - -const privateKey = process.env.HYPERLIQUID_API_WALLET_KEY; -const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; -const vaultAddress = process.env.HYPERLIQUID_VAULT_ADDRESS; - -if (!privateKey || !accountAddress || !vaultAddress) { - throw new Error( - 'Set HYPERLIQUID_API_WALLET_KEY, HYPERLIQUID_ACCOUNT_ADDRESS, and HYPERLIQUID_VAULT_ADDRESS before starting.', - ); +⋮---- +export interface WebData3Event { + perpDexStates: WebData3PerpDexState[]; + userState: { + abstraction?: 'disabled' | 'portfolioMargin' | 'unifiedAccount'; + agentAddress: string | null; + agentValidUntil: number | null; + cumLedger: string; + dexAbstractionEnabled?: boolean; + isVault: boolean; + optOutOfSpotDusting?: true; + serverTime: number; + user: string; + }; } - -const client = new RestClient({ - privateKey, - accountAddress, - vaultAddress, -}); -``` - -The signer still owns the nonce. A single API wallet used across several vaults or subaccounts shares one nonce set. Separate signers are easier to operate safely when those accounts trade concurrently. - -### Custom signers - -`privateKey` is the simplest server-side option. Applications that keep keys in another wallet system can pass a signer with `signTypedData` instead: - -```javascript -import { RestClient } from '@siebly/hyperliquid-api'; - -export function createHyperliquidClient(walletClient, walletAddress) { - return new RestClient({ - accountAddress: walletAddress, - signer: { - address: walletAddress, - signTypedData: (domain, types, value) => - walletClient.signTypedData({ - account: walletAddress, - domain, - types, - primaryType: Object.keys(types)[0], - message: value, - }), - }, - }); +⋮---- +export type GenericWebsocketEvent = + Record | unknown[] | string | number | boolean | null; +⋮---- +/** Payload types keyed by subscription type. Wire envelopes are defined in ws-events. */ +export interface WebsocketSubscriptionData { + activeAssetCtx: AssetContextEvent; + activeAssetData: ActiveAssetDataEvent; + allDexsAssetCtxs: AllDexsAssetCtxsEvent; + allDexsClearinghouseState: AllDexsClearinghouseStateEvent; + allMids: AllMidsEvent; + assetCtxs: AssetCtxsEvent; + bbo: BboEvent; + candle: Candle; + clearinghouseState: ClearinghouseStateEvent; + fastAssetCtxs: FastAssetCtxsEvent; + l2Book: OrderBookResponse; + notification: NotificationEvent; + openOrders: OpenOrdersEvent; + orderUpdates: OrderUpdateEvent[]; + outcomeMetaUpdates: OutcomeMetaUpdatesEvent; + spotAssetCtxs: SpotAssetCtxsEvent; + spotState: SpotStateEvent; + trades: TradeEvent[]; + twapStates: TwapStatesEvent; + userEvents: UserEvent; + userFills: UserFillsEvent; + userFundings: UserFundingsEvent; + userHistoricalOrders: UserHistoricalOrdersEvent; + userNonFundingLedgerUpdates: UserNonFundingLedgerUpdatesEvent; + userTwapHistory: UserTwapHistoryEvent; + userTwapSliceFills: UserTwapSliceFillsEvent; + webData3: WebData3Event; } -``` - -The signer may expose `address` or `getAddress()`. The SDK handles Hyperliquid's two signature schemes and required MessagePack field ordering. - -## Recover account state after a reconnect - -Subscriptions are restored after a reconnect, but updates that occurred during the gap still need to be reconciled. Reload the account state required by the process, build a replacement snapshot, then publish it only after every required read succeeds. +⋮---- +export type WebsocketEventFor = + WebsocketSubscriptionData[S['type']]; -This example refreshes balances, open orders, and recent fills when an account-stream connection returns. +================ +File: src/index.ts +================ - -```javascript -import { RestClient, WebsocketClient } from '@siebly/hyperliquid-api'; +================ +File: src/WebsocketAPIClient.ts +================ +import { HyperliquidWebSocketError } from './lib/websocket/errors.js'; +import { + DefaultLogger, + type DefaultLogger as Logger, +} from './lib/websocket/logger.js'; +import { RestClient } from './RestClient.js'; +import type { + WebsocketAPIClientOptions, + WebsocketClientEventMap, +} from './types/websockets/ws-general.js'; +import { WebsocketClient } from './WebsocketClient.js'; +⋮---- +export class WebsocketAPIClient extends RestClient +⋮---- +constructor( + options: WebsocketAPIClientOptions = {}, + logger: Logger = DefaultLogger, + ) +⋮---- +getWSClient(): WebsocketClient +⋮---- +connect(): Promise +⋮---- +on( + event: K, + listener: (...args: WebsocketClientEventMap[K]) => void, + ): this +⋮---- +once( + event: K, + listener: (...args: WebsocketClientEventMap[K]) => void, + ): this +⋮---- +off( + event: K, + listener: (...args: WebsocketClientEventMap[K]) => void, + ): this +⋮---- +isConnected(): boolean +⋮---- +close(): void +⋮---- +closeAll(): void +⋮---- +protected override post(path: string, body: unknown): Promise -const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; +================ +File: docs/HYPERLIQUID_SDK_QUICKSTART_GUIDE.md +================ + -if (!accountAddress) { - throw new Error('Set HYPERLIQUID_ACCOUNT_ADDRESS before starting.'); -} +# Hyperliquid API JavaScript Tutorial for Node.js -const restClient = new RestClient({ accountAddress }); -const websocketClient = new WebsocketClient(); + -let accountState = { - balances: undefined, - openOrders: [], - recentFills: [], -}; -let recoveryVersion = 0; +> [!TIP] +> Read this guide in tutorial format on the Siebly website: [Hyperliquid JavaScript REST API and WebSocket Tutorial](https://siebly.io/sdk/hyperliquid/javascript/tutorial) -async function reloadAccountState() { - const version = ++recoveryVersion; - let balances; - let openOrders; - let recentFills; + - try { - balances = await restClient.getBalances(); - } catch (error) { - console.error('Balance recovery failed:', error); - return; - } +This tutorial uses [`@siebly/hyperliquid-api`](https://www.npmjs.com/package/@siebly/hyperliquid-api), Siebly's Node.js and JavaScript SDK for Hyperliquid. It covers public market data, account state, perpetual and Spot orders, public and account WebSocket streams, WebSocket API requests, Testnet, network routing, and proxies. - try { - openOrders = await restClient.getOpenOrders(); - } catch (error) { - console.error('Open-order recovery failed:', error); - return; - } +The SDK handles asset resolution, action signing, exact wire formatting, nonce allocation, WebSocket heartbeats, reconnects, resubscriptions, and WebSocket request matching. TypeScript declarations are included for projects that use them. - try { - recentFills = await restClient.getUserFills({ - aggregateByTime: true, - }); - } catch (error) { - console.error('Fill recovery failed:', error); - return; - } +**Key links** - // A newer recovery must not be overwritten by an older request sequence. - if (version !== recoveryVersion) { - return; - } +- Hyperliquid JavaScript SDK by Siebly: [`@siebly/hyperliquid-api`](https://siebly.io/sdk/hyperliquid/javascript) +- npm package: [`@siebly/hyperliquid-api`](https://www.npmjs.com/package/@siebly/hyperliquid-api) +- GitHub repository: [`sieblyio/hyperliquid-api`](https://github.com/sieblyio/hyperliquid-api) +- SDK examples: [Hyperliquid SDK examples](https://siebly.io/examples/Hyperliquid) +- Repository examples: [JavaScript and TypeScript examples](../examples) +- Hyperliquid API documentation: [Official API docs](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) +- Trading-system terms: [Siebly glossary](https://siebly.io/reference/glossary) +- More JavaScript and TypeScript SDKs: [Siebly.io](https://siebly.io) - accountState = { - balances, - openOrders, - recentFills, - }; +## Why use `@siebly/hyperliquid-api`? - console.log('Recovered account state:', accountState); -} +Hyperliquid uses one Info endpoint for market and account reads, one Exchange endpoint for signed actions, and one WebSocket endpoint for both subscriptions and request-response traffic. Asset identifiers and signing formats also vary by operation. -async function main() { - websocketClient - .on('reconnected', async ({ wsKey }) => { - console.log('WebSocket reconnected:', wsKey); - await reloadAccountState(); - }) - .on('exception', ({ wsKey, event, data }) => { - console.error('WebSocket exception:', wsKey, event ?? data); - }); +`@siebly/hyperliquid-api` presents those surfaces through three clients: - websocketClient.on('message', ({ channel, data, wsKey }) => { - if (channel === 'orderUpdates') { - console.log('Order updates:', wsKey, data); - } else if (channel === 'userFills') { - console.log('Fill update:', data); - } - }); +| Client | Use it for | +| --- | --- | +| `RestClient` | Market data, account state, orders, transfers, staking, vaults, subaccounts, and other REST API operations | +| `WebsocketClient` | Public market-data and address-scoped account subscriptions | +| `WebsocketAPIClient` | The same awaitable Info and signed Exchange methods over Hyperliquid's WebSocket POST protocol | - try { - await websocketClient.subscribe([ - { type: 'orderUpdates', user: accountAddress }, - { type: 'userFills', user: accountAddress, aggregateByTime: true }, - ]); - } catch (error) { - console.error('Account subscription failed:', error); - } +`RestClient` also provides higher-level helpers such as `submitMarketOrder()`, `submitLimitOrder()`, `submitMarketClose()`, and `getBalances()`. Lower-level methods remain available for batching, TP/SL orders, transfers, vault operations, and other advanced workflows. - await reloadAccountState(); -} +## Install the SDK -main(); +```bash +npm install @siebly/hyperliquid-api ``` -This is [REST API hydration](https://siebly.io/reference/glossary#rest-hydration): rebuilding trusted [account state](https://siebly.io/reference/glossary#accountstate) from current REST API reads after a stream gap. Use [Scoped Recovery](https://siebly.io/reference/glossary#scoped-recovery) to reload only the state owned by the affected connection, and see [Exchange State](https://siebly.io/reference/exchange-state) and [Runtime Workflows](https://siebly.io/reference/runtime-workflows) for wider state-management patterns. - -If the process stores a durable fill cursor, reload by time and deduplicate by trade ID before replacing state. The initial `userFills` snapshot after resubscription may overlap with the REST API result. - -Here, `orderUpdates` uses `user:
` while `userFills` uses `public`, so either connection can trigger recovery. The version counter prevents overlapping recoveries from publishing out of order. This example logs stream events separately; a live state cache also needs to reconcile events received while its REST API reads are in flight. - - - -## Choose Mainnet, Testnet, or custom API hosts - -| Network | REST API host | WebSocket host | -| --- | --- | --- | -| Mainnet | `https://api.hyperliquid.xyz` | `wss://api.hyperliquid.xyz/ws` | -| Testnet | `https://api.hyperliquid-testnet.xyz` | `wss://api.hyperliquid-testnet.xyz/ws` | - -Mainnet is the default. Set `testnet: true` on every client that belongs to a Testnet workflow. - - - -```javascript -import { - RestClient, - WebsocketAPIClient, - WebsocketClient, -} from '@siebly/hyperliquid-api'; - -const mainnetRest = new RestClient(); -const mainnetStreams = new WebsocketClient(); -const mainnetWebsocketApi = new WebsocketAPIClient(); - -const testnetRest = new RestClient({ testnet: true }); -const testnetStreams = new WebsocketClient({ testnet: true }); -const testnetWebsocketApi = new WebsocketAPIClient({ testnet: true }); - -const restApiUrl = process.env.HYPERLIQUID_REST_API_URL; -const websocketUrl = process.env.HYPERLIQUID_WEBSOCKET_URL; - -if (!restApiUrl || !websocketUrl) { - throw new Error( - 'Set HYPERLIQUID_REST_API_URL and HYPERLIQUID_WEBSOCKET_URL before using custom hosts.', - ); -} +Node.js 22 or newer is supported. Every example below is plain JavaScript. -const customRest = new RestClient({ - baseUrl: restApiUrl, -}); +## Set up a wallet and API wallet -const customStreams = new WebsocketClient({ - baseUrl: restApiUrl, - wsUrl: websocketUrl, -}); +Public market data and account-state reads do not require a private key. Hyperliquid account state is queried by public address. -console.log({ - mainnetRest, - mainnetStreams, - mainnetWebsocketApi, - testnetRest, - testnetStreams, - testnetWebsocketApi, - customRest, - customStreams, -}); -``` +Orders and other Exchange actions require a signer. For an automated process, create and approve an API wallet from the [Hyperliquid API page](https://app.hyperliquid.xyz/API). Hyperliquid also calls these agent wallets. -`baseUrl` changes REST API routing. `wsUrl` changes WebSocket routing. The explicit `WebsocketClient.nameToCoin()` and `resolveAsset()` helpers use REST API metadata, so set both values if those helpers also need custom routing. Native streaming subscriptions use only `wsUrl`. +Keep two values separate: -Keep Mainnet and Testnet private keys, account addresses, client order IDs, and persistent state in separate configuration. Asset IDs and available markets can differ between the two networks. +- The API wallet private key signs actions. +- The master account, subaccount, or vault address identifies the account being managed. -## Use a proxy with the REST API and WebSockets +Store them in a server-side environment or secret manager: -Install the proxy agent needed by your network: +- `HYPERLIQUID_API_WALLET_KEY` +- `HYPERLIQUID_ACCOUNT_ADDRESS` -```bash -npm install https-proxy-agent socks-proxy-agent ws -``` +For Testnet examples, use separate values: -The REST API client accepts Axios network options as its second constructor argument. WebSocket clients accept a custom WebSocket constructor. These are separate routes, so configure and test each one. +- `HYPERLIQUID_TESTNET_API_WALLET_KEY` +- `HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS` -See [Using a Proxy with Siebly SDKs](https://siebly.io/blog/using-proxy-with-siebly-sdks) for deployment and troubleshooting guidance. +An API wallet address is not the account address. Passing the API wallet address to account queries usually returns empty state. See Hyperliquid's [nonces and API wallets](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets) documentation for the full model. -### HTTP or HTTPS proxy +Use one API wallet per trading process. If several processes or subaccounts share a signer, they also share its nonce set. Separate API wallets avoid nonce collisions and make process ownership clearer. -`HttpsProxyAgent` supports both HTTP and HTTPS proxy URLs. +Never put a private key in browser code, logs, or committed configuration. - +## Hyperliquid products and request vocabulary -```javascript -import { HttpsProxyAgent } from 'https-proxy-agent'; -import WebSocket from 'ws'; -import { - RestClient, - WebsocketAPIClient, - WebsocketClient, -} from '@siebly/hyperliquid-api'; +| Field or value | Example | Meaning | +| --- | --- | --- | +| Perpetual symbol | `BTC` | A perpetual market from `getPerpMetadata()` | +| Spot pair | `HYPE/USDC` | A readable Spot alias resolved from current metadata | +| Native Spot name | `PURR/USDC` or `@107` | The name used by HyperCore for a Spot pair | +| HIP-3 symbol | `dex:COIN` | A market on a HIP-3 perpetual DEX | +| Outcome asset | `#10` | An outcome-market identifier | +| `szDecimals` | `5` | The allowed number of decimal places in order size | +| `side` | `buy` or `sell` | The side accepted by the SDK's order helpers | +| `clientOrderId` | `0x` plus 32 hex characters | Your [custom order ID](https://siebly.io/reference/glossary#custom-order-id), called `cloid` by Hyperliquid | +| `accountAddress` | `HYPERLIQUID_ACCOUNT_ADDRESS` | The master account, subaccount, or vault whose state is queried | +| `vaultAddress` | `HYPERLIQUID_VAULT_ADDRESS` | Optional routing for actions signed on behalf of a vault or subaccount | +| `wsKey` | `public`, `api`, or `user:
` | The [WebSocket key](https://siebly.io/reference/glossary#ws-key) included with every emitted event | -const proxyUrl = process.env.HYPERLIQUID_PROXY_URL; +Perpetual sizes are quantities of the underlying asset. Spot sizes are quantities of the base asset. Prices, sizes, transfer amounts, and trigger prices are exact decimal strings in SDK requests. -if (!proxyUrl) { - throw new Error('Set HYPERLIQUID_PROXY_URL before starting.'); -} +Hyperliquid prices may use up to five significant figures, subject to the asset's decimal limit. Order sizes use the asset's `szDecimals`. Read the current [tick and lot-size rules](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/tick-and-lot-size) before constructing values yourself. -const agent = new HttpsProxyAgent(proxyUrl); -const parsedProxy = new URL(proxyUrl); -const axiosProxy = { - protocol: parsedProxy.protocol.slice(0, -1), - host: parsedProxy.hostname, - port: Number( - parsedProxy.port || (parsedProxy.protocol === 'https:' ? 443 : 80), - ), - ...(parsedProxy.username - ? { - auth: { - username: decodeURIComponent(parsedProxy.username), - password: decodeURIComponent(parsedProxy.password), - }, - } - : {}), -}; +Info responses are endpoint-specific. Some methods return objects, some return arrays, and `getOrderBook()` may return `null` for an unavailable book. There is no shared success envelope for Info responses. -class ProxyWebSocket { - constructor(url) { - this.socket = Reflect.construct(WebSocket, [url, { agent }]); - this.onopen = null; - this.onmessage = null; - this.onerror = null; - this.onclose = null; + - this.socket.onopen = () => this.onopen?.(); - this.socket.onmessage = (event) => this.onmessage?.({ data: event.data }); - this.socket.onerror = (event) => this.onerror?.(event); - this.socket.onclose = (event) => - this.onclose?.({ code: event.code, reason: String(event.reason) }); - } +## Start building: first REST API calls - get readyState() { - return this.socket.readyState; - } +Run each example on its own. Start with public market data, then read account state and inspect asset metadata. - send(data) { - this.socket.send(data); - } +### 1. Make public REST API calls - close(code, reason) { - this.socket.close(code, reason); - } -} +Public Info calls need no wallet or API key. -const restClient = new RestClient( - { keepAlive: false }, - { - httpsAgent: agent, - proxy: false, - }, -); + -const websocketClient = new WebsocketClient({ - requestOptions: { - proxy: axiosProxy, - }, - webSocketConstructor: ProxyWebSocket, -}); +```javascript +import { RestClient } from '@siebly/hyperliquid-api'; -const websocketApiClient = new WebsocketAPIClient({ - webSocketConstructor: ProxyWebSocket, -}); +const client = new RestClient(); async function main() { try { - const status = await restClient.getExchangeStatus(); - console.log('REST API through proxy:', status); + const status = await client.getExchangeStatus(); + console.log('Exchange status:', status); } catch (error) { - console.error('Proxied REST API request failed:', error); + console.error('Exchange-status request failed:', error); } - websocketClient.on('message', ({ channel, data }) => { - if (channel === 'allMids') { - console.log('Stream through proxy:', data.mids.BTC); - } - }); + try { + const mids = await client.getAllMids(); + console.log('BTC midpoint:', mids.BTC); + } catch (error) { + console.error('Midpoint request failed:', error); + } try { - await websocketClient.subscribe({ type: 'allMids' }); + const metadata = await client.getPerpMetadata(); + const btc = metadata.universe.find((asset) => asset.name === 'BTC'); + console.log('BTC perpetual metadata:', btc); } catch (error) { - console.error('Proxied stream subscription failed:', error); + console.error('Perpetual metadata request failed:', error); } try { - const mids = await websocketApiClient.getAllMids(); - console.log('WebSocket API through proxy:', mids.BTC); + const orderBook = await client.getOrderBook({ + coin: 'BTC', + }); + console.log('Best bid:', orderBook?.levels[0][0]); + console.log('Best ask:', orderBook?.levels[1][0]); } catch (error) { - console.error('Proxied WebSocket API request failed:', error); + console.error('Order-book request failed:', error); + } + + try { + const candles = await client.getCandles({ + coin: 'BTC', + interval: '1h', + startTime: Date.now() - 24 * 60 * 60 * 1000, + }); + console.log('Latest hourly candle:', candles.at(-1)); + } catch (error) { + console.error('Candle request failed:', error); + } + + try { + const trades = await client.getRecentTrades({ + coin: 'BTC', + }); + console.log('Most recent trade:', trades[0]); + } catch (error) { + console.error('Recent-trade request failed:', error); } } main(); ``` -Set `keepAlive: false` so the SDK preserves the custom agent, and set `proxy: false` so Axios does not apply separate environment-proxy handling on top of it. +The response shapes reflect the requested data: -`webSocketConstructor` routes the socket itself. Streaming subscriptions send native topics without REST API metadata requests. `requestOptions.proxy` routes REST API metadata requests if you explicitly call `nameToCoin()` or `resolveAsset()` on the streaming client. +- `getExchangeStatus()` returns an object with `time` and `specialStatuses`. +- `getAllMids()` returns an object whose keys are asset names and whose values are decimal strings. +- `getPerpMetadata()` returns an object with `universe`, `marginTables`, and `collateralToken`. +- `getOrderBook()` returns `coin`, `time`, and two arrays under `levels`. Index `0` contains bids and index `1` contains asks. Each level has `px`, `sz`, and `n`. +- `getCandles()` returns candle objects with fields such as `t`, `T`, `o`, `c`, `h`, `l`, `v`, and `n`. +- `getRecentTrades()` returns an array. `side: 'B'` is a buy and `side: 'A'` is a sell. -### SOCKS5 proxy +### 2. Read account state by address - +Balances, positions, orders, and fills are Info queries keyed by a public address. No signer is needed for these reads. + + ```javascript -import { SocksProxyAgent } from 'socks-proxy-agent'; -import WebSocket from 'ws'; -import { - RestClient, - WebsocketAPIClient, - WebsocketClient, -} from '@siebly/hyperliquid-api'; +import { RestClient } from '@siebly/hyperliquid-api'; -const proxyUrl = process.env.HYPERLIQUID_SOCKS_PROXY_URL; +const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; -if (!proxyUrl) { - throw new Error('Set HYPERLIQUID_SOCKS_PROXY_URL before starting.'); +if (!accountAddress) { + throw new Error('Set HYPERLIQUID_ACCOUNT_ADDRESS before starting.'); } -const agent = new SocksProxyAgent(proxyUrl); +const client = new RestClient({ accountAddress }); -class ProxyWebSocket { - constructor(url) { - this.socket = Reflect.construct(WebSocket, [url, { agent }]); - this.onopen = null; - this.onmessage = null; - this.onerror = null; - this.onclose = null; +async function main() { + try { + const balances = await client.getBalances(); + console.log('Account:', balances.user); + console.log('Spot balances:', balances.spot.balances); + console.log('Perpetual positions:', balances.perp.assetPositions); + } catch (error) { + console.error('Balance request failed:', error); + } - this.socket.onopen = () => this.onopen?.(); - this.socket.onmessage = (event) => this.onmessage?.({ data: event.data }); - this.socket.onerror = (event) => this.onerror?.(event); - this.socket.onclose = (event) => - this.onclose?.({ code: event.code, reason: String(event.reason) }); + try { + const openOrders = await client.getOpenOrders(); + console.log('Open orders:', openOrders); + } catch (error) { + console.error('Open-order request failed:', error); } - get readyState() { - return this.socket.readyState; + try { + const fills = await client.getUserFills({ + aggregateByTime: true, + }); + console.log('Recent fills:', fills); + } catch (error) { + console.error('Fill request failed:', error); } - send(data) { - this.socket.send(data); + try { + const funding = await client.getUserFundingHistory({ + startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, + }); + console.log('Recent funding payments:', funding); + } catch (error) { + console.error('Funding-history request failed:', error); } +} + +main(); +``` + +`getBalances()` combines the Spot clearinghouse state and the default perpetual clearinghouse state. The perpetual response includes margin summaries, `withdrawable`, and `assetPositions`. A signed position size is available as `position.szi`: positive is long, negative is short, and zero is flat. Under unified account or portfolio margin, Spot state is the relevant balance view. This helper does not aggregate every HIP-3 DEX. + +A client configured with `accountAddress` may omit `user` from account getters. A public client can instead pass `{ user: accountAddress }` to each account method. + +Time-ranged Info methods have response limits. Paginate long histories by moving `startTime` to the last returned timestamp. See the official [Info endpoint documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint) for each method's current limit. - close(code, reason) { - this.socket.close(code, reason); - } -} +### 3. Resolve perpetual, Spot, HIP-3, and outcome assets -const restClient = new RestClient( - { keepAlive: false }, - { - httpsAgent: agent, - proxy: false, - }, -); +The SDK loads current Mainnet or Testnet metadata when an operation needs an asset ID. The cache lasts five minutes by default. -const websocketClient = new WebsocketClient({ - webSocketConstructor: ProxyWebSocket, -}); + -const websocketApiClient = new WebsocketAPIClient({ - webSocketConstructor: ProxyWebSocket, -}); +```javascript +import { RestClient } from '@siebly/hyperliquid-api'; + +const client = new RestClient(); async function main() { try { - const status = await restClient.getExchangeStatus(); - console.log('REST API through SOCKS5:', status); + const btcPerpetual = await client.resolveAsset('BTC'); + console.log('BTC perpetual:', btcPerpetual); } catch (error) { - console.error('SOCKS5 REST API request failed:', error); + console.error('Perpetual resolution failed:', error); } - websocketClient.on('message', ({ channel, data }) => { - if (channel === 'allMids') { - console.log('WebSocket through SOCKS5:', data.mids.BTC); - } - }); + try { + const hypeSpot = await client.resolveAsset('HYPE/USDC'); + console.log('HYPE/USDC Spot:', hypeSpot); + } catch (error) { + console.error('Spot resolution failed:', error); + } try { - await websocketClient.subscribe({ type: 'allMids' }); + const nativeSpotName = await client.nameToCoin('HYPE/USDC'); + console.log('Native Spot name:', nativeSpotName); } catch (error) { - console.error('SOCKS5 stream subscription failed:', error); + console.error('Spot-name lookup failed:', error); } try { - const mids = await websocketApiClient.getAllMids(); - console.log('WebSocket API through SOCKS5:', mids.BTC); + await client.refreshAssetMetadata(); + console.log('Asset metadata refreshed.'); } catch (error) { - console.error('SOCKS5 WebSocket API request failed:', error); + console.error('Metadata refresh failed:', error); } } main(); ``` -Set `keepAlive: false` so the REST API client preserves the SOCKS agent. Streaming subscriptions do not trigger REST API metadata lookups. Resolve readable aliases with the configured `restClient.nameToCoin()` first, then pass the result to `websocketClient.subscribe()`, so alias resolution also follows the SOCKS route. +`resolveAsset()` returns the numeric `assetId`, canonical name, asset kind, and `szDecimals` where available. It accepts current metadata names, readable Spot aliases, numeric IDs, HIP-3 names such as `dex:COIN`, and outcome identifiers such as `#10`. -A proxy changes the network route, not the account, network, or available products. Monitor latency, disconnects, and the proxy's egress IP. +Asset IDs differ across Mainnet and Testnet. Resolve them against the same network that will receive the request. Do not add `-PERP` or `-SPOT` suffixes. -## Understand Siebly referral and builder settings +`getOrderBook()`, `getCandles()`, and `getFundingHistory()` apply Hyperliquid's native name mapping where required. For example, a readable Spot alias such as `HYPE/USDC` resolves to its current `@` name, while `PURR/USDC` is already a native universe name. Streaming subscriptions, `getRecentTrades()`, and `getActiveAssetData()` expect an official coin name. Call `nameToCoin()` explicitly when starting with a readable Spot alias, then pass the result as `coin`. Subscribing with a native coin name does not make a REST API metadata request. -The SDK includes two separate Hyperliquid features: + -- Referral code: around the first supported trading action from a client instance, the SDK makes one passive attempt to apply `SIEBLY`. Hyperliquid leaves an existing referrer unchanged. Trading continues if the referral request fails. -- Builder code: after the account has approved Siebly's builder fee, the SDK can include the builder code on eligible orders. The configured maximum is 0.1%. Approval must be signed by the main wallet, not an API wallet. If a main-wallet private key is configured and approval is missing, the SDK may attempt that approval in the background. An API wallet cannot grant it. +## Build with Hyperliquid WebSocket streams -Builder approval is optional. Call `approveBuilderFee()` only from a client configured with the main wallet signer and only after reviewing Hyperliquid's current [builder-code documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/builder-codes). The approval sets a maximum and can be revoked through Hyperliquid. +`WebsocketClient` manages connections, JSON parsing, heartbeats, reconnects, and resubscriptions. Subscriptions are objects that match Hyperliquid's documented channel types. -## Production checklist +### 4. Subscribe to public market data -- **Keep signers isolated.** Use a separate approved API wallet per trading process and, where practical, per subaccount. -- **Query the correct address.** Account reads use the master account, subaccount, or vault address rather than the API wallet address. -- **Keep time synchronized.** Signed nonces are millisecond timestamps and must remain inside Hyperliquid's accepted time window. -- **Use exact decimal strings.** Avoid JavaScript floating-point arithmetic for stored financial values. Trim trailing decimal zeroes before native wire-shaped actions. -- **Read current metadata.** Resolve assets and use the latest `szDecimals`, universe names, margin tables, and market status. -- **Meet minimum notional.** Hyperliquid currently requires at least 10 units of the quote asset for an order. -- **Check every action status.** A top-level `ok` response may contain an order-specific `error` entry. -- **Reconcile uncertain writes.** Query by [custom order ID](https://siebly.io/reference/glossary#custom-order-id) before retrying a timed-out submission. -- **Confirm final order state.** Treat the first response as [pending confirmation](https://siebly.io/reference/glossary#pending-confirmation), then use `getOrderStatus()` or `orderUpdates`. -- **Use action deadlines where useful.** `expiresAfter` limits how long a signed action remains valid. A stale deadline rejection currently consumes extra address-based rate-limit allowance. -- **Use a dead-man switch.** Refresh `setCancelAllAfter()` from a monitored process when unattended resting orders need automatic cancellation. -- **Recover after gaps.** Rebuild the affected balances, positions, orders, and fills after a reconnect. -- **Respect current limits.** Hyperliquid applies IP, address, open-order, connection, subscription, and in-flight request limits. Check the current [rate-limit documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/rate-limits-and-user-limits). -- **Keep keys server-side.** Browser use should be limited to public REST API calls and public-address WebSocket subscriptions. -- **Monitor the route.** Track latency, reconnect frequency, and egress IP when using a proxy or custom endpoint. + -## Frequently asked questions +```javascript +import { WebsocketClient } from '@siebly/hyperliquid-api'; -### Can I use the SDK with plain JavaScript? +const client = new WebsocketClient(); -Yes. All examples in this guide are JavaScript. The package also includes TypeScript declarations. +async function main() { + client + .on('open', ({ wsKey }) => { + console.log('WebSocket opened:', wsKey); + }) + .on('reconnecting', ({ wsKey, event }) => { + console.log('WebSocket reconnecting:', wsKey, event); + }) + .on('reconnected', ({ wsKey }) => { + console.log('WebSocket reconnected:', wsKey); + }) + .on('close', ({ wsKey }) => { + console.log('WebSocket closed:', wsKey); + }) + .on('exception', ({ wsKey, event, data }) => { + console.error('WebSocket exception:', wsKey, event ?? data); + }); -### Which client should I start with? + client.on('message', ({ channel, data, wsKey }) => { + switch (channel) { + case 'allMids': + console.log('BTC midpoint:', wsKey, data.mids.BTC); + break; + case 'l2Book': + console.log('Best bid:', data.levels[0][0]); + console.log('Best ask:', data.levels[1][0]); + break; + case 'trades': + console.log('Trades:', data); + break; + case 'candle': + console.log('One-minute candle:', data); + break; + } + }); -Start with `RestClient` for public market data and account state. Add `WebsocketClient` for live subscriptions. Use `WebsocketAPIClient` when you need awaitable Info or Exchange requests over WebSocket. + client.on('response', ({ channel, data, wsKey }) => { + if (channel === 'subscriptionResponse') { + console.log('Subscription acknowledgement:', wsKey, data); + } + }); -### Why do account reads work without a private key? + try { + await client.subscribe([ + { type: 'allMids' }, + { type: 'l2Book', coin: 'BTC' }, + { type: 'trades', coin: 'BTC' }, + { type: 'candle', coin: 'BTC', interval: '1m' }, + ]); + } catch (error) { + console.error('Market subscription failed:', error); + } +} -Hyperliquid Info queries use a public account address. A signer is needed for Exchange actions that change orders, funds, leverage, or account settings. +main(); +``` -### Why are my balances empty when I use an API wallet? +Hyperliquid sends a [subscription acknowledgement](https://siebly.io/reference/glossary#subscription-acknowledgement) when the subscription is registered. The SDK emits that protocol message through `response`. Market and account updates arrive through `message`, preserving the full `{ channel, data, wsKey }` envelope. -Account queries need the master account, subaccount, or vault address. The API wallet address identifies the signer, not the account whose state is being queried. Set `accountAddress` on the client. +Register listeners before subscribing. Each incoming data message is emitted once, and every registered `message` listener receives it. Filter by `channel` and the payload's coin or user when consuming several feeds. -### What is the difference between a wallet and an API wallet? +The promise returned by `subscribe()` resolves after the socket opens and the request is sent. It does not wait for the server acknowledgement, so do not use that promise alone as a stream-readiness signal. -The main wallet owns the account. An approved API wallet signs actions on its behalf. Use an API wallet for an automated process and keep the main wallet key outside that process. +Some channels send an initial snapshot before later updates. `l2Book` sends a current book, while `allMids`, trades, and candles continue with new values. The SDK sends regular heartbeat pings to check connection health. -### Why are REST API response shapes different? +`subscribe()` and `unsubscribe()` accept one topic or an array of topics. Equivalent topics are deduplicated by value. Listeners are independent of subscriptions: use `off('message', listener)` to remove a listener and `unsubscribe(topic)` to stop a stream. Call `client.closeAll()` when the application needs to close its WebSocket connections. -Hyperliquid's Info endpoint selects an operation through the request body, and each operation has its own response schema. The SDK returns that response directly. +`connectionTimeout` defaults to 1,000 ms. Heartbeats use a 10,000 ms ping interval and a 1,000 ms pong deadline. Increase `connectionTimeout` or `pongTimeout` for a higher-latency connection. Reconnect delays start at 500 ms and double up to 10,000 ms. -### Why did an order fail when the response status was `ok`? +#### Subscribe to a Spot market -The top-level status describes the action request. Each order has its own entry under `response.data.statuses`, which may contain `resting`, `filled`, or `error`. +Resolve a readable Spot pair once, then subscribe with its native coin name. The lookup uses the REST API; `subscribe()` itself only sends WebSocket messages. -### Does `submitMarketOrder()` send a native market order? +```javascript +import { WebsocketClient } from '@siebly/hyperliquid-api'; -It sends an immediate-or-cancel limit order with a protection price. The SDK derives and rounds that price from the midpoint when one is not supplied. +const client = new WebsocketClient(); -### How do I tell perpetual and Spot assets apart? +client.on('message', ({ channel, data }) => { + if (channel === 'l2Book') { + console.log('Spot book:', data.coin, data.levels); + } +}); -Perpetuals commonly use names such as `BTC`. Spot markets use native universe names such as `PURR/USDC` or `@107`. REST API helpers can resolve readable metadata-derived aliases such as `HYPE/USDC`. For streaming subscriptions, call `nameToCoin()` explicitly to resolve an alias, then subscribe with the returned native coin name. +client.on('exception', ({ wsKey, event, data }) => { + console.error('WebSocket exception:', wsKey, event ?? data); +}); -### Do account WebSocket streams require authentication? +async function main() { + let coin; -They use the public account address. The SDK isolates user-scoped channels when needed and resubscribes after reconnecting. + try { + coin = await client.nameToCoin('HYPE/USDC'); + } catch (error) { + console.error('Spot-name lookup failed:', error); + return; + } -### How do subscription acknowledgements appear? + try { + await client.subscribe({ type: 'l2Book', coin }); + } catch (error) { + console.error('Spot subscription failed:', error); + } +} -Listen for `response` events with `channel === 'subscriptionResponse'`. Snapshot and update envelopes arrive through `message`. Both events include `wsKey`, and the promise returned by `subscribe()` only confirms that the subscription request was sent. +main(); +``` -### What is the difference between `WebsocketClient` and `WebsocketAPIClient`? +### 5. Subscribe to account updates -`WebsocketClient` consumes ongoing subscriptions. `WebsocketAPIClient` sends one Info or Exchange request and resolves the matching response as a promise. +Account subscriptions use the account's public address. They do not need a signer. -### Should I retry a timed-out order submission? + -Not immediately. Query `getOrderStatus()` with the client order ID first. A request can reach Hyperliquid even when the response does not reach your process. +```javascript +import { WebsocketClient } from '@siebly/hyperliquid-api'; -### How do I use a vault or subaccount? +const user = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; -Set its address as `vaultAddress` for signed actions and use the same address for its account-state queries. The API wallet must be approved to act for the relevant account. +if (!user) { + throw new Error('Set HYPERLIQUID_ACCOUNT_ADDRESS before starting.'); +} -### Can I use Mainnet credentials on Testnet? +const client = new WebsocketClient(); + +async function main() { + client + .on('open', ({ wsKey }) => { + console.log('WebSocket opened:', wsKey); + }) + .on('reconnected', ({ wsKey }) => { + console.log('WebSocket reconnected:', wsKey); + }) + .on('exception', ({ wsKey, event, data }) => { + console.error('WebSocket exception:', wsKey, event ?? data); + }); + + client.on('message', ({ channel, data, wsKey }) => { + switch (channel) { + case 'orderUpdates': + console.log('Order updates:', wsKey, data); + break; + case 'userFills': + console.log('Fill snapshot:', data.isSnapshot === true); + console.log('Fills:', data.fills); + break; + case 'clearinghouseState': + console.log('Perpetual state:', data.clearinghouseState); + break; + case 'spotState': + console.log('Spot state:', data.spotState); + break; + } + }); + + try { + await client.subscribe([ + { type: 'orderUpdates', user }, + { type: 'userFills', user, aggregateByTime: true }, + { type: 'clearinghouseState', user }, + { type: 'spotState', user }, + ]); + } catch (error) { + console.error('Account subscription failed:', error); + } +} + +main(); +``` -Keep the networks separate. Approval state, account balances, assets, and API-wallet registration differ between Mainnet and Testnet. +`userFills`, `userFundings`, and similar time-series streams tag their initial payload with `isSnapshot: true`. Use the snapshot to initialize state, then process later payloads as changes. Avoid applying the same snapshot twice after a reconnect. -### Does a proxy change which network I use? +The `orderUpdates` stream reports order transitions. Use it with a REST API query when reconciling [pending confirmation](https://siebly.io/reference/glossary#pending-confirmation) after an uncertain order or cancellation response. -No. `testnet`, `baseUrl`, and `wsUrl` select the destination. A proxy only changes the network path used to reach it. +Every lifecycle, message, response, and exception event includes `wsKey`. Most subscriptions share `public`. `orderUpdates`, `notification`, and `userEvents` use `user:` connections because their server messages omit the address. Use that key to identify the account. The `userEvents` subscription emits messages on Hyperliquid's `user` channel. -## Next steps + -1. Run the public REST API example and inspect the current BTC metadata and order book. -2. Set a public account address and load balances, positions, open orders, and fills. -3. Add public and account WebSocket subscriptions, including reconnect handling. -4. Approve a Testnet API wallet and run the perpetual order lifecycle. -5. Add client order IDs, action deadlines, dead-man-switch refresh, and scoped recovery before moving a strategy to Mainnet. +## Place and manage Testnet orders -Continue with the [Hyperliquid SDK examples](https://siebly.io/examples/Hyperliquid), browse the [source repository](https://github.com/sieblyio/hyperliquid-api), and check the [official Hyperliquid API documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) for current exchange behavior. +Use Hyperliquid Testnet to run the following workflows. Create and approve an API wallet from the [Testnet API page](https://app.hyperliquid-testnet.xyz/API), fund the account from the [Testnet faucet](https://app.hyperliquid-testnet.xyz/drip), then set `HYPERLIQUID_TESTNET_API_WALLET_KEY` and `HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS`. The faucet currently requires the same address to have deposited on Mainnet. -================ -File: examples/Websocket/ws-public.ts -================ -import { DefaultLogger, WebsocketClient } from '../../src/index.js'; -⋮---- -// or -// import { DefaultLogger, WebsocketClient } from '@siebly/hyperliquid-api'; -⋮---- -/** - * Public market-data subscriptions. - * - * Listen for data with on('message') and acknowledgements with on('response'). - * Every event includes the connection's wsKey. - * - * npx tsx examples/Websocket/ws-public.ts - */ -⋮---- -// Uncomment to log connection lifecycle events: -⋮---- -// testnet: true, -⋮---- -async function main() -⋮---- -// Native Spot universe names pass through without metadata requests. -⋮---- -// For a readable alias, resolve it explicitly before subscribing: -// const coin = await wsClient.nameToCoin('HYPE/USDC'); -// await wsClient.subscribe({ type: 'trades', coin }); +The SDK signs Exchange actions with the API wallet and sends account queries for the configured account address. It lowercases EVM addresses and formats action fields in the order required by Hyperliquid's signing rules. -================ -File: src/types/websockets/ws-general.ts -================ -import type { - RestClientNetworkOptions, - RestClientOptions, -} from '../shared.types.js'; -import type { - WebsocketMessageEvent, - WebsocketResponseEvent, -} from './ws-events.js'; -import type { WebSocketConstructor, WebSocketLike } from './ws-portable.js'; -⋮---- -export type WsKey = 'api' | 'public' | `user:${string}`; -⋮---- -export interface WebsocketClientOptions { - assetCacheTtlMs?: number; - /** Reconnect automatically after an unexpected disconnect. Default: true. */ - autoReconnect?: boolean; - baseUrl?: string; - /** Maximum time to open a connection, in milliseconds. Default: 1,000. */ - connectionTimeout?: number; - /** Maximum reconnect delay in milliseconds. Default: 10,000. Zero means no delay. */ - maxReconnectTimeout?: number; - /** Ping interval in milliseconds. Default: 10,000. Zero disables pings. */ - pingInterval?: number; - /** Pong deadline in milliseconds. Default: 1,000. Zero disables the deadline. */ - pongTimeout?: number; - /** Initial reconnect delay in milliseconds. Default: 500. Zero means no delay. */ - reconnectTimeout?: number; - /** Maximum request duration in milliseconds, including connection setup. Default: 10,000. */ - requestTimeoutMs?: number; - requestOptions?: RestClientNetworkOptions; - testnet?: boolean; - webSocketConstructor?: WebSocketConstructor; - wsUrl?: string; -} -⋮---- -/** Reconnect automatically after an unexpected disconnect. Default: true. */ -⋮---- -/** Maximum time to open a connection, in milliseconds. Default: 1,000. */ -⋮---- -/** Maximum reconnect delay in milliseconds. Default: 10,000. Zero means no delay. */ -⋮---- -/** Ping interval in milliseconds. Default: 10,000. Zero disables pings. */ -⋮---- -/** Pong deadline in milliseconds. Default: 1,000. Zero disables the deadline. */ -⋮---- -/** Initial reconnect delay in milliseconds. Default: 500. Zero means no delay. */ -⋮---- -/** Maximum request duration in milliseconds, including connection setup. Default: 10,000. */ -⋮---- -export type WebsocketAPIClientOptions = RestClientOptions & - Omit; -⋮---- -export type WebsocketLifecycleEvent = - 'close' | 'exception' | 'open' | 'reconnected' | 'reconnecting'; -⋮---- -export interface WebsocketLifecycleContext { - wsKey: TWsKey; - event?: unknown; -} -⋮---- -export interface WebsocketConnectionContext< - TWsKey extends string = WsKey, -> extends WebsocketLifecycleContext { - ws: WebSocketLike; - wsUrl: string; -} -⋮---- -export interface WebsocketExceptionEvent< - TWsKey extends string = WsKey, -> extends WebsocketLifecycleContext { - channel?: 'error' | 'post'; - data?: unknown; -} -⋮---- -/** Argument tuples for the standard EventEmitter API. */ -export interface WebsocketClientEventMap { - open: [event: WebsocketConnectionContext]; - reconnecting: [event: WebsocketLifecycleContext]; - reconnected: [event: WebsocketConnectionContext]; - close: [event: WebsocketLifecycleContext]; - message: [event: WebsocketMessageEvent]; - response: [event: WebsocketResponseEvent]; - exception: [event: WebsocketExceptionEvent]; -} -⋮---- -export type WebsocketLifecycleCallback = ( - context: WebsocketLifecycleContext, -) => void; +Order responses need two checks: -================ -File: src/types/websockets/ws-subscriptions.ts -================ -import type { - ActiveAssetData, - Candle, - HistoricalOrder, - LeadingVault, - OpenOrder, - OrderBookResponse, - OrderState, - PerpAccountSummaryResponse, - PerpAssetContext, - SpotAssetContext, - SpotBalancesResponse, - Trade, - TwapHistoryEntry, - TwapSliceFill, - TwapState, - UserFill, - UserNonFundingLedgerUpdate, -} from '../response/info.types.js'; -⋮---- -export type CandleInterval = - | '1d' - | '12h' - | '15m' - | '1h' - | '1m' - | '1M' - | '2h' - | '30m' - | '3d' - | '3m' - | '4h' - | '5m' - | '8h' - | '1w'; -⋮---- -export type UserSubscriptionType = - | 'activeAssetData' - | 'allDexsClearinghouseState' - | 'clearinghouseState' - | 'notification' - | 'openOrders' - | 'orderUpdates' - | 'spotState' - | 'twapStates' - | 'userEvents' - | 'userFills' - | 'userFundings' - | 'userHistoricalOrders' - | 'userNonFundingLedgerUpdates' - | 'userTwapHistory' - | 'userTwapSliceFills' - | 'webData3'; -⋮---- -export type WebsocketSubscription = - | { dex?: string; type: 'allMids' } - | { dex?: string; type: 'assetCtxs' } - | { coin: string; type: 'activeAssetCtx' } - | { coin: string; user: string; type: 'activeAssetData' } - | { type: 'allDexsAssetCtxs' } - | { user: string; type: 'allDexsClearinghouseState' } - | { coin: string; type: 'bbo' } - | { coin: string; interval: CandleInterval; type: 'candle' } - | { dex?: string; user: string; type: 'clearinghouseState' } - | { type: 'fastAssetCtxs' } - | { - coin: string; - fast?: boolean; - mantissa?: 1 | 2 | 5 | null; - nSigFigs?: 2 | 3 | 4 | 5 | null; - type: 'l2Book'; - } - | { user: string; type: 'notification' } - | { dex?: string; user: string; type: 'openOrders' } - | { user: string; type: 'orderUpdates' } - | { type: 'outcomeMetaUpdates' } - | { type: 'spotAssetCtxs' } - | { - ignorePortfolioMargin?: boolean; - isPortfolioMargin?: never; - user: string; - type: 'spotState'; - } - | { - ignorePortfolioMargin?: never; - isPortfolioMargin?: boolean; - user: string; - type: 'spotState'; - } - | { coin: string; type: 'trades' } - | { dex?: string; user: string; type: 'twapStates' } - | { user: string; type: 'userEvents' } - | { aggregateByTime?: boolean; user: string; type: 'userFills' } - | { user: string; type: 'userFundings' } - | { user: string; type: 'userHistoricalOrders' } - | { user: string; type: 'userNonFundingLedgerUpdates' } - | { user: string; type: 'userTwapHistory' } - | { user: string; type: 'userTwapSliceFills' } - | { user: string; type: 'webData3' }; -⋮---- -export interface AllMidsEvent { - dex?: string; - mids: Record; -} -⋮---- -export interface AssetContextEvent { - coin: string; - ctx: PerpAssetContext | SpotAssetContext; -} -⋮---- -export interface ActivePerpAssetContextEvent extends AssetContextEvent { - ctx: PerpAssetContext; +1. `status` must be `ok`. +2. Each entry under `response.data.statuses` must be inspected. + +An order status entry may contain `resting`, `filled`, or `error`. A successful request can still contain an order-specific error. + +The SDK makes a passive, one-time attempt to apply the `SIEBLY` referral code around the first trading action from a client instance when the account has no existing referrer. It does not replace an existing referrer, delay the order, or retry the referral request. Hyperliquid's current [referral terms](https://hyperliquid.gitbook.io/hyperliquid-docs/referrals) describe any user discount. + +### 6. Run a perpetual REST API order lifecycle + +This example reads current BTC metadata and book data, places a post-only Testnet order, queries it, cancels it by client order ID, and queries it again. + + + +```javascript +import { randomBytes } from 'node:crypto'; +import { RestClient } from '@siebly/hyperliquid-api'; + +const privateKey = process.env.HYPERLIQUID_TESTNET_API_WALLET_KEY; +const accountAddress = process.env.HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS; + +if (!privateKey || !accountAddress) { + throw new Error( + 'Set HYPERLIQUID_TESTNET_API_WALLET_KEY and HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS before starting.', + ); } -⋮---- -export interface ActiveSpotAssetContextEvent extends AssetContextEvent { - ctx: SpotAssetContext; + +const client = new RestClient({ + privateKey, + accountAddress, + testnet: true, +}); + +function trimDecimal(value) { + if (!value.includes('.')) { + return value; + } + return value.replace(/0+$/, '').replace(/\.$/, ''); } -⋮---- -export interface AssetCtxsEvent { - ctxs: PerpAssetContext[]; - dex: string; + +function sizeForNotional(price, szDecimals, targetNotional) { + const factor = 10 ** szDecimals; + const size = Math.ceil((targetNotional / Number(price)) * factor) / factor; + return trimDecimal(size.toFixed(szDecimals)); } -⋮---- -export interface AllDexsAssetCtxsEvent { - ctxs: Array<[dex: string, contexts: PerpAssetContext[]]>; + +async function main() { + let btc; + let orderBook; + + try { + const metadata = await client.getPerpMetadata(); + btc = metadata.universe.find((asset) => asset.name === 'BTC'); + console.log('BTC metadata:', btc); + } catch (error) { + console.error('Perpetual metadata request failed:', error); + return; + } + + if (!btc) { + console.error('BTC was not returned by the Testnet perpetual metadata.'); + return; + } + + try { + orderBook = await client.getOrderBook({ coin: 'BTC' }); + console.log('BTC order book received at:', orderBook?.time); + } catch (error) { + console.error('Order-book request failed:', error); + return; + } + + const bid = orderBook?.levels[0][4] ?? orderBook?.levels[0][0]; + + if (!bid) { + console.error('The BTC order book did not contain a bid.'); + return; + } + + const clientOrderId = `0x${randomBytes(16).toString('hex')}`; + const size = sizeForNotional(bid.px, btc.szDecimals, 12); + let orderId; + + try { + const result = await client.submitLimitOrder({ + symbol: 'BTC', + side: 'buy', + price: bid.px, + size, + postOnly: true, + clientOrderId, + }); + console.log('Order response:', result); + + if (result.status !== 'ok') { + console.error('Hyperliquid rejected the order request:', result.response); + return; + } + + const orderStatus = result.response.data?.statuses[0]; + + if (!orderStatus) { + console.error('The order response did not contain an order status.'); + return; + } + + if ('error' in orderStatus) { + console.error('The order was rejected:', orderStatus.error); + return; + } + + if ('filled' in orderStatus) { + console.log('The order filled immediately:', orderStatus.filled); + return; + } + + orderId = orderStatus.resting.oid; + console.log('Resting order ID:', orderId); + } catch (error) { + console.error('Order submission failed:', error); + return; + } + + try { + const currentOrder = await client.getOrderStatus({ oid: orderId }); + console.log('Order before cancellation:', currentOrder); + } catch (error) { + console.error('Order-status request failed:', error); + } + + try { + const cancellation = await client.cancelOrdersByClientOrderId({ + cancels: [{ asset: 'BTC', cloid: clientOrderId }], + }); + console.log('Cancellation response:', cancellation); + } catch (error) { + console.error('Cancellation failed:', error); + return; + } + + try { + const finalOrder = await client.getOrderStatus({ oid: clientOrderId }); + console.log('Final order state:', finalOrder); + } catch (error) { + console.error('Final order-status request failed:', error); + } } -⋮---- -export interface ClearinghouseStateEvent { - clearinghouseState: PerpAccountSummaryResponse; - dex: string; - user: string; + +main(); +``` + +The fifth bid is already a valid Hyperliquid price and normally remains below the best ask, which makes it suitable for an add-liquidity-only order. The size targets at least 12 USDC of notional and rounds up to the asset's `szDecimals`, keeping it above Hyperliquid's current 10 USDC minimum order value. + +`postOnly: true` maps to Hyperliquid's `Alo` time in force. Without `postOnly`, `submitLimitOrder()` defaults to good-till-cancelled. Set `timeInForce: 'IOC'` for immediate-or-cancel. + +The `clientOrderId` is 16 random bytes encoded as a 32-character hexadecimal string. Keep it with the local order record. It can be used with `getOrderStatus()` and `cancelOrdersByClientOrderId()` when the numeric exchange order ID is not yet known. + +### 7. Run a Spot REST API order lifecycle + +Spot orders use the same helper, but the symbol resolves to a Spot asset ID and size means base-asset quantity. This example places a post-only PURR/USDC buy on Testnet. + + + +```javascript +import { randomBytes } from 'node:crypto'; +import { RestClient } from '@siebly/hyperliquid-api'; + +const privateKey = process.env.HYPERLIQUID_TESTNET_API_WALLET_KEY; +const accountAddress = process.env.HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS; + +if (!privateKey || !accountAddress) { + throw new Error( + 'Set HYPERLIQUID_TESTNET_API_WALLET_KEY and HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS before starting.', + ); } -⋮---- -export interface AllDexsClearinghouseStateEvent { - clearinghouseStates: Array<[dex: string, state: PerpAccountSummaryResponse]>; - user: string; + +const client = new RestClient({ + privateKey, + accountAddress, + testnet: true, +}); + +function trimDecimal(value) { + if (!value.includes('.')) { + return value; + } + return value.replace(/0+$/, '').replace(/\.$/, ''); } -⋮---- -export type ActiveAssetDataEvent = ActiveAssetData; -⋮---- -export interface BboEvent { - bbo: [ - bid: { n: number; px: string; sz: string } | null, - offer: { n: number; px: string; sz: string } | null, - ]; - coin: string; - time: number; + +function sizeForNotional(price, szDecimals, targetNotional) { + const factor = 10 ** szDecimals; + const size = Math.ceil((targetNotional / Number(price)) * factor) / factor; + return trimDecimal(size.toFixed(szDecimals)); } -⋮---- -export type TradeEvent = Trade; -⋮---- -export interface NotificationEvent { - notification: string; + +async function main() { + let asset; + let orderBook; + + try { + asset = await client.resolveAsset('PURR/USDC'); + console.log('PURR/USDC asset:', asset); + } catch (error) { + console.error('Spot asset resolution failed:', error); + return; + } + + if (asset.szDecimals === undefined) { + console.error('PURR/USDC size metadata was not available.'); + return; + } + + try { + orderBook = await client.getOrderBook({ coin: 'PURR/USDC' }); + console.log('PURR/USDC order book received at:', orderBook?.time); + } catch (error) { + console.error('Spot order-book request failed:', error); + return; + } + + const bid = orderBook?.levels[0][4] ?? orderBook?.levels[0][0]; + + if (!bid) { + console.error('The PURR/USDC order book did not contain a bid.'); + return; + } + + const clientOrderId = `0x${randomBytes(16).toString('hex')}`; + const size = sizeForNotional(bid.px, asset.szDecimals, 12); + let orderId; + + try { + const result = await client.submitLimitOrder({ + symbol: 'PURR/USDC', + side: 'buy', + price: bid.px, + size, + postOnly: true, + clientOrderId, + }); + console.log('Spot order response:', result); + + if (result.status !== 'ok') { + console.error('Hyperliquid rejected the order request:', result.response); + return; + } + + const orderStatus = result.response.data?.statuses[0]; + + if (!orderStatus) { + console.error('The order response did not contain an order status.'); + return; + } + + if ('error' in orderStatus) { + console.error('The order was rejected:', orderStatus.error); + return; + } + + if ('filled' in orderStatus) { + console.log('The order filled immediately:', orderStatus.filled); + return; + } + + orderId = orderStatus.resting.oid; + console.log('Resting Spot order ID:', orderId); + } catch (error) { + console.error('Spot order submission failed:', error); + return; + } + + try { + const currentOrder = await client.getOrderStatus({ oid: orderId }); + console.log('Spot order before cancellation:', currentOrder); + } catch (error) { + console.error('Spot order-status request failed:', error); + } + + try { + const cancellation = await client.cancelOrdersByClientOrderId({ + cancels: [{ asset: 'PURR/USDC', cloid: clientOrderId }], + }); + console.log('Spot cancellation response:', cancellation); + } catch (error) { + console.error('Spot cancellation failed:', error); + return; + } + + try { + const finalOrder = await client.getOrderStatus({ oid: clientOrderId }); + console.log('Final Spot order state:', finalOrder); + } catch (error) { + console.error('Final Spot order-status request failed:', error); + } } -⋮---- -export interface OrderUpdateEvent { - order: OpenOrder; - status: OrderState; - statusTimestamp: number; + +main(); +``` + +The account needs enough Testnet USDC to reserve the order. A Spot sell needs the corresponding base asset. Hyperliquid may return internal token names in balances, so use the names returned by the API when reconciling funds. + +### Market orders, closing positions, and leverage + +`submitMarketOrder()` is an immediate-or-cancel limit order with a protection price. When `price` is omitted, the SDK loads the current midpoint, applies `maxSlippage`, rounds the protection price to Hyperliquid's rules, and rounds size down to `szDecimals`. + +Call `submitMarketOrder()` with `symbol`, `side`, `size`, and an optional `maxSlippage`. For example, a BTC buy can use `{ symbol: 'BTC', side: 'buy', size: '0.001', maxSlippage: 0.01 }`. + +`submitMarketClose()` reads the current perpetual position, chooses the closing side, and submits a reduce-only immediate-or-cancel order. Omit `size` to close the full position. + +Call `submitMarketClose()` with the perpetual symbol and an optional `maxSlippage`. Add `size` when only part of the position should be closed. + +Call `updateLeverage()` with `{ asset: 'BTC', isCross: true, leverage: 3 }` to select three-times cross leverage for BTC. Use the current asset metadata and account limits when choosing leverage. + +These are signed production actions on Mainnet and signed test actions on Testnet. Read current account and market state before calling them. + + + +## Send Info and Exchange requests with the WebSocket API + +`WebsocketAPIClient` exposes the same promise-based methods as `RestClient`, but sends supported Info and Exchange requests through Hyperliquid's WebSocket POST protocol. + +The SDK opens the connection when needed, assigns a unique request ID, matches the response to the promise, and rejects protocol errors. The raw `id` and response wrapper do not need to be managed by application code. + +This example reads market data, submits a post-only BTC Testnet order, and cancels it through the WebSocket API. + + + +```javascript +import { randomBytes } from 'node:crypto'; +import { WebsocketAPIClient } from '@siebly/hyperliquid-api'; + +const privateKey = process.env.HYPERLIQUID_TESTNET_API_WALLET_KEY; +const accountAddress = process.env.HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS; + +if (!privateKey || !accountAddress) { + throw new Error( + 'Set HYPERLIQUID_TESTNET_API_WALLET_KEY and HYPERLIQUID_TESTNET_ACCOUNT_ADDRESS before starting.', + ); } -⋮---- -export interface FastAssetCtxsEvent { - [coin: string]: { - markPx?: string; - midPx?: string | null; - }; + +const client = new WebsocketAPIClient({ + privateKey, + accountAddress, + testnet: true, +}); + +function trimDecimal(value) { + if (!value.includes('.')) { + return value; + } + return value.replace(/0+$/, '').replace(/\.$/, ''); } -⋮---- -export type SpotAssetCtxsEvent = SpotAssetContext[]; -⋮---- -export interface OutcomeMetaUpdatesEvent { - updates: Array< - | { - outcomeCreated: { - description: string; - name: string; - outcome: number; - quoteToken: string; - sideSpecs: Array<{ name: string; token?: number }>; - }; - } - | { outcomeSettled: { outcome: number } } - | { - questionUpdated: { - description: string; - fallbackOutcome: number; - name: string; - namedOutcomes: number[]; - question: number; - settledNamedOutcomes: number[]; - }; - } - | { questionSettled: { question: number } } - >; + +function sizeForNotional(price, szDecimals, targetNotional) { + const factor = 10 ** szDecimals; + const size = Math.ceil((targetNotional / Number(price)) * factor) / factor; + return trimDecimal(size.toFixed(szDecimals)); } -⋮---- -export interface UserFillsEvent { - fills: UserFill[]; - isSnapshot?: boolean; - user: string; + +async function main() { + client + .on('open', ({ wsKey }) => { + console.log('WebSocket API opened:', wsKey); + }) + .on('reconnected', ({ wsKey }) => { + console.log('WebSocket API reconnected:', wsKey); + }) + .on('exception', ({ wsKey, event, data }) => { + console.error('WebSocket API exception:', wsKey, event ?? data); + }); + + let btc; + let orderBook; + + try { + const metadata = await client.getPerpMetadata(); + btc = metadata.universe.find((asset) => asset.name === 'BTC'); + console.log('BTC metadata:', btc); + } catch (error) { + console.error('WebSocket metadata request failed:', error); + client.closeAll(); + return; + } + + if (!btc) { + console.error('BTC was not returned by the Testnet perpetual metadata.'); + client.closeAll(); + return; + } + + try { + orderBook = await client.getOrderBook({ coin: 'BTC' }); + console.log('BTC order book received at:', orderBook?.time); + } catch (error) { + console.error('WebSocket order-book request failed:', error); + client.closeAll(); + return; + } + + const bid = orderBook?.levels[0][4] ?? orderBook?.levels[0][0]; + + if (!bid) { + console.error('The BTC order book did not contain a bid.'); + client.closeAll(); + return; + } + + const clientOrderId = `0x${randomBytes(16).toString('hex')}`; + const size = sizeForNotional(bid.px, btc.szDecimals, 12); + let orderId; + + try { + const result = await client.submitLimitOrder({ + symbol: 'BTC', + side: 'buy', + price: bid.px, + size, + postOnly: true, + clientOrderId, + }); + console.log('WebSocket order response:', result); + + if (result.status !== 'ok') { + console.error('Hyperliquid rejected the order request:', result.response); + client.closeAll(); + return; + } + + const orderStatus = result.response.data?.statuses[0]; + + if (!orderStatus) { + console.error('The order response did not contain an order status.'); + client.closeAll(); + return; + } + + if ('error' in orderStatus) { + console.error('The order was rejected:', orderStatus.error); + client.closeAll(); + return; + } + + if ('filled' in orderStatus) { + console.log('The order filled immediately:', orderStatus.filled); + client.closeAll(); + return; + } + + orderId = orderStatus.resting.oid; + console.log('Resting order ID:', orderId); + } catch (error) { + console.error('WebSocket order submission failed:', error); + client.closeAll(); + return; + } + + try { + const currentOrder = await client.getOrderStatus({ oid: orderId }); + console.log('Order before cancellation:', currentOrder); + } catch (error) { + console.error('WebSocket order-status request failed:', error); + } + + try { + const cancellation = await client.cancelOrdersByClientOrderId({ + cancels: [{ asset: 'BTC', cloid: clientOrderId }], + }); + console.log('WebSocket cancellation response:', cancellation); + } catch (error) { + console.error('WebSocket cancellation failed:', error); + } + + try { + const finalOrder = await client.getOrderStatus({ oid: clientOrderId }); + console.log('Final WebSocket order state:', finalOrder); + } catch (error) { + console.error('Final WebSocket order-status request failed:', error); + } + + client.closeAll(); } -⋮---- -export interface OpenOrdersEvent { - dex: string; - orders: OpenOrder[]; - user: string; + +main(); +``` + +WebSocket POST supports Info requests and signed Exchange actions. Explorer requests are not supported. Use `WebsocketClient` for subscriptions and `WebsocketAPIClient` for awaitable request-response calls. + +Successful POST replies also emit `response` events with `channel: 'post'` and `wsKey: 'api'`. Protocol errors and top-level Exchange rejections emit `exception` and reject the matching promise. An accepted action can still contain individual order errors, so inspect its status entries. Use `try`/`catch` for each command; event listeners provide additional connection and protocol diagnostics. + +`requestTimeoutMs` defaults to 10,000 ms and includes connection setup. Requests interrupted by a disconnect are rejected, not replayed after reconnection. Query the order's status before retrying a command whose outcome is uncertain. + +An order-command response acknowledges the action and reports its immediate status. Confirm later changes through `orderUpdates` or `getOrderStatus()`. That distinction is important when an order fills or is cancelled immediately after its first response. + +## Explore more REST API methods + +`RestClient` covers Hyperliquid's wider Info and Exchange surfaces. The following groups are a starting map rather than a complete list. + +### Market and account information + +| Workflow | Representative methods | +| --- | --- | +| Perpetual markets | `getPerpMetadata()`, `getPerpAssetContexts()`, `getFundingHistory()`, `getPredictedFundingRates()` | +| Spot markets | `getSpotMetadata()`, `getSpotAssetContexts()`, `getTokenDetails()` | +| Books and trades | `getOrderBook()`, `getAllMids()`, `getCandles()`, `getRecentTrades()` | +| Orders and fills | `getOpenOrders()`, `getFrontendOpenOrders()`, `getOrderStatus()`, `getOrderHistory()`, `getUserFills()` | +| Account and risk | `getPerpAccountSummary()`, `getSpotBalances()`, `getPortfolio()`, `getUserFees()`, `getUserRateLimit()` | +| HIP-3 perpetual DEXs | `getPerpDexs()`, `getPerpDexStatus()`, `getPerpDexLimits()`, `getAllPerpMetadata()` | +| Outcome markets | `getOutcomeMetadata()`, `getSettledOutcome()` | +| Vaults and staking | `getVaultDetails()`, `getVaultSummaries()`, `getStakingSummary()`, `getStakingDelegations()` | +| Borrow and lend | `getAllBorrowLendReserveStates()`, `getBorrowLendReserveState()`, `getUserBorrowLendState()` | +| Subaccounts and signers | `getSubAccounts()`, `getExtraAgents()`, `getMultiSigSigners()`, `getUserRole()` | + +### Signed actions + +| Workflow | Representative methods | +| --- | --- | +| Orders | `submitOrder()`, `submitMarketOrder()`, `submitLimitOrder()`, `modifyOrder()`, `cancelOrders()` | +| Position management | `submitMarketClose()`, `updateLeverage()`, `updateIsolatedMargin()`, `setIsolatedMarginLeverage()` | +| TWAP and safety | `submitTwapOrder()`, `cancelTwapOrder()`, `setCancelAllAfter()` | +| Transfers | `sendUsd()`, `sendSpotAsset()`, `sendAsset()`, `transferUsdBetweenSpotAndPerp()`, `withdrawUsd()` | +| Subaccounts and vaults | `createSubAccount()`, `transferUsdToOrFromSubAccount()`, `createVault()`, `transferVaultFunds()` | +| Staking | `depositIntoStaking()`, `withdrawFromStaking()`, `delegateOrUndelegateStake()`, `claimRewards()` | +| Account configuration | `approveApiWallet()`, `setUserPortfolioMargin()`, `setUserAbstraction()`, `setUserDexAbstraction()` | +| Advanced deployment | `deployPerp()`, `deploySpot()`, `submitUserOutcome()`, validator and multisig methods | + +Use the focused [repository examples](../examples) and exported request types when moving beyond the workflows in this guide. Transfers, withdrawals, staking, account configuration, deployer operations, and multisig actions change funds or account settings, so confirm their current official requirements before using them. + +### Batch orders, TP/SL, and dead-man switches + +Use `submitOrder()` when sending native wire-shaped batches or TP/SL orders. A batch is one request for IP limits but each order still counts toward address-based limits. Inspect every returned status, because errors usually align with individual batch entries. + +`setCancelAllAfter()` schedules cancellation of open orders if the account stops refreshing the deadline. Hyperliquid currently requires the trigger to be at least five seconds in the future and limits each account to ten scheduled-cancel triggers per UTC day. Refresh it from a monitored process, and call the method without `time` to remove the schedule when the strategy stops normally. + +Set `expiresAfter: Date.now() + 10_000` in the second argument of a signed method when an action should expire after ten seconds. This adds an action deadline. It does not replace order time in force or a dead-man switch. + +### Vaults and subaccounts + +Set `vaultAddress` on the client when all signed actions belong to one vault or subaccount: + +```javascript +import { RestClient } from '@siebly/hyperliquid-api'; + +const privateKey = process.env.HYPERLIQUID_API_WALLET_KEY; +const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; +const vaultAddress = process.env.HYPERLIQUID_VAULT_ADDRESS; + +if (!privateKey || !accountAddress || !vaultAddress) { + throw new Error( + 'Set HYPERLIQUID_API_WALLET_KEY, HYPERLIQUID_ACCOUNT_ADDRESS, and HYPERLIQUID_VAULT_ADDRESS before starting.', + ); } -⋮---- -export interface UserFundingsEvent { - fundings: Array<{ - coin: string; - fundingRate: string; - nSamples: number | null; - szi: string; - time: number; - usdc: string; - }>; - isSnapshot?: boolean; - user: string; + +const client = new RestClient({ + privateKey, + accountAddress, + vaultAddress, +}); +``` + +The signer still owns the nonce. A single API wallet used across several vaults or subaccounts shares one nonce set. Separate signers are easier to operate safely when those accounts trade concurrently. + +### Custom signers + +`privateKey` is the simplest server-side option. Applications that keep keys in another wallet system can pass a signer with `signTypedData` instead: + +```javascript +import { RestClient } from '@siebly/hyperliquid-api'; + +export function createHyperliquidClient(walletClient, walletAddress) { + return new RestClient({ + accountAddress: walletAddress, + signer: { + address: walletAddress, + signTypedData: (domain, types, value) => + walletClient.signTypedData({ + account: walletAddress, + domain, + types, + primaryType: Object.keys(types)[0], + message: value, + }), + }, + }); } -⋮---- -export interface UserHistoricalOrdersEvent { - isSnapshot?: boolean; - orderHistory: HistoricalOrder[]; - user: string; +``` + +The signer may expose `address` or `getAddress()`. The SDK handles Hyperliquid's two signature schemes and required MessagePack field ordering. + +## Recover account state after a reconnect + +Subscriptions are restored after a reconnect, but updates that occurred during the gap still need to be reconciled. Reload the account state required by the process, build a replacement snapshot, then publish it only after every required read succeeds. + +This example refreshes balances, open orders, and recent fills when an account-stream connection returns. + + + +```javascript +import { RestClient, WebsocketClient } from '@siebly/hyperliquid-api'; + +const accountAddress = process.env.HYPERLIQUID_ACCOUNT_ADDRESS; + +if (!accountAddress) { + throw new Error('Set HYPERLIQUID_ACCOUNT_ADDRESS before starting.'); } -⋮---- -export interface UserNonFundingLedgerUpdatesEvent { - isSnapshot?: boolean; - nonFundingLedgerUpdates: UserNonFundingLedgerUpdate[]; - user: string; + +const restClient = new RestClient({ accountAddress }); +const websocketClient = new WebsocketClient(); + +let accountState = { + balances: undefined, + openOrders: [], + recentFills: [], +}; +let recoveryVersion = 0; + +async function reloadAccountState() { + const version = ++recoveryVersion; + let balances; + let openOrders; + let recentFills; + + try { + balances = await restClient.getBalances(); + } catch (error) { + console.error('Balance recovery failed:', error); + return; + } + + try { + openOrders = await restClient.getOpenOrders(); + } catch (error) { + console.error('Open-order recovery failed:', error); + return; + } + + try { + recentFills = await restClient.getUserFills({ + aggregateByTime: true, + }); + } catch (error) { + console.error('Fill recovery failed:', error); + return; + } + + // A newer recovery must not be overwritten by an older request sequence. + if (version !== recoveryVersion) { + return; + } + + accountState = { + balances, + openOrders, + recentFills, + }; + + console.log('Recovered account state:', accountState); } -⋮---- -export interface UserTwapHistoryEvent { - history: TwapHistoryEntry[]; - isSnapshot?: boolean; - user: string; + +async function main() { + websocketClient + .on('reconnected', async ({ wsKey }) => { + console.log('WebSocket reconnected:', wsKey); + await reloadAccountState(); + }) + .on('exception', ({ wsKey, event, data }) => { + console.error('WebSocket exception:', wsKey, event ?? data); + }); + + websocketClient.on('message', ({ channel, data, wsKey }) => { + if (channel === 'orderUpdates') { + console.log('Order updates:', wsKey, data); + } else if (channel === 'userFills') { + console.log('Fill update:', data); + } + }); + + try { + await websocketClient.subscribe([ + { type: 'orderUpdates', user: accountAddress }, + { type: 'userFills', user: accountAddress, aggregateByTime: true }, + ]); + } catch (error) { + console.error('Account subscription failed:', error); + } + + await reloadAccountState(); } -⋮---- -export interface UserTwapSliceFillsEvent { - isSnapshot?: boolean; - twapSliceFills: TwapSliceFill[]; - user: string; + +main(); +``` + +This is [REST API hydration](https://siebly.io/reference/glossary#rest-hydration): rebuilding trusted [account state](https://siebly.io/reference/glossary#accountstate) from current REST API reads after a stream gap. Use [Scoped Recovery](https://siebly.io/reference/glossary#scoped-recovery) to reload only the state owned by the affected connection, and see [Exchange State](https://siebly.io/reference/exchange-state) and [Runtime Workflows](https://siebly.io/reference/runtime-workflows) for wider state-management patterns. + +If the process stores a durable fill cursor, reload by time and deduplicate by trade ID before replacing state. The initial `userFills` snapshot after resubscription may overlap with the REST API result. + +Here, `orderUpdates` uses `user:
` while `userFills` uses `public`, so either connection can trigger recovery. The version counter prevents overlapping recoveries from publishing out of order. This example logs stream events separately; a live state cache also needs to reconcile events received while its REST API reads are in flight. + + + +## Choose Mainnet, Testnet, or custom API hosts + +| Network | REST API host | WebSocket host | +| --- | --- | --- | +| Mainnet | `https://api.hyperliquid.xyz` | `wss://api.hyperliquid.xyz/ws` | +| Testnet | `https://api.hyperliquid-testnet.xyz` | `wss://api.hyperliquid-testnet.xyz/ws` | + +Mainnet is the default. Set `testnet: true` on every client that belongs to a Testnet workflow. + + + +```javascript +import { + RestClient, + WebsocketAPIClient, + WebsocketClient, +} from '@siebly/hyperliquid-api'; + +const mainnetRest = new RestClient(); +const mainnetStreams = new WebsocketClient(); +const mainnetWebsocketApi = new WebsocketAPIClient(); + +const testnetRest = new RestClient({ testnet: true }); +const testnetStreams = new WebsocketClient({ testnet: true }); +const testnetWebsocketApi = new WebsocketAPIClient({ testnet: true }); + +const restApiUrl = process.env.HYPERLIQUID_REST_API_URL; +const websocketUrl = process.env.HYPERLIQUID_WEBSOCKET_URL; + +if (!restApiUrl || !websocketUrl) { + throw new Error( + 'Set HYPERLIQUID_REST_API_URL and HYPERLIQUID_WEBSOCKET_URL before using custom hosts.', + ); } -⋮---- -export interface SpotStateEvent { - spotState: SpotBalancesResponse; - user: string; + +const customRest = new RestClient({ + baseUrl: restApiUrl, +}); + +const customStreams = new WebsocketClient({ + baseUrl: restApiUrl, + wsUrl: websocketUrl, +}); + +console.log({ + mainnetRest, + mainnetStreams, + mainnetWebsocketApi, + testnetRest, + testnetStreams, + testnetWebsocketApi, + customRest, + customStreams, +}); +``` + +`baseUrl` changes REST API routing. `wsUrl` changes WebSocket routing. The explicit `WebsocketClient.nameToCoin()` and `resolveAsset()` helpers use REST API metadata, so set both values if those helpers also need custom routing. Native streaming subscriptions use only `wsUrl`. + +Keep Mainnet and Testnet private keys, account addresses, client order IDs, and persistent state in separate configuration. Asset IDs and available markets can differ between the two networks. + +## Use a proxy with the REST API and WebSockets + +Install the proxy agent needed by your network: + +```bash +npm install https-proxy-agent socks-proxy-agent ws +``` + +The REST API client accepts Axios network options as its second constructor argument. WebSocket clients accept a custom WebSocket constructor. These are separate routes, so configure and test each one. + +See [Using a Proxy with Siebly SDKs](https://siebly.io/blog/using-proxy-with-siebly-sdks) for deployment and troubleshooting guidance. + +### HTTP or HTTPS proxy + +`HttpsProxyAgent` supports both HTTP and HTTPS proxy URLs. + + + +```javascript +import { HttpsProxyAgent } from 'https-proxy-agent'; +import WebSocket from 'ws'; +import { + RestClient, + WebsocketAPIClient, + WebsocketClient, +} from '@siebly/hyperliquid-api'; + +const proxyUrl = process.env.HYPERLIQUID_PROXY_URL; + +if (!proxyUrl) { + throw new Error('Set HYPERLIQUID_PROXY_URL before starting.'); } -⋮---- -export interface TwapStatesEvent { - dex: string; - states: Array<[twapId: number, state: TwapState]>; - user: string; + +const agent = new HttpsProxyAgent(proxyUrl); +const parsedProxy = new URL(proxyUrl); +const axiosProxy = { + protocol: parsedProxy.protocol.slice(0, -1), + host: parsedProxy.hostname, + port: Number( + parsedProxy.port || (parsedProxy.protocol === 'https:' ? 443 : 80), + ), + ...(parsedProxy.username + ? { + auth: { + username: decodeURIComponent(parsedProxy.username), + password: decodeURIComponent(parsedProxy.password), + }, + } + : {}), +}; + +class ProxyWebSocket { + constructor(url) { + this.socket = Reflect.construct(WebSocket, [url, { agent }]); + this.onopen = null; + this.onmessage = null; + this.onerror = null; + this.onclose = null; + + this.socket.onopen = () => this.onopen?.(); + this.socket.onmessage = (event) => this.onmessage?.({ data: event.data }); + this.socket.onerror = (event) => this.onerror?.(event); + this.socket.onclose = (event) => + this.onclose?.({ code: event.code, reason: String(event.reason) }); + } + + get readyState() { + return this.socket.readyState; + } + + send(data) { + this.socket.send(data); + } + + close(code, reason) { + this.socket.close(code, reason); + } } -⋮---- -export type UserEvent = - | { fills: UserFill[] } - | { - funding: { - coin: string; - fundingRate: string; - nSamples: number | null; - szi: string; - time: number; - usdc: string; - }; - } - | { - liquidation: { - lid: number; - liquidated_account_value: string; - liquidated_ntl_pos: string; - liquidated_user: string; - liquidator: string; - }; + +const restClient = new RestClient( + { keepAlive: false }, + { + httpsAgent: agent, + proxy: false, + }, +); + +const websocketClient = new WebsocketClient({ + requestOptions: { + proxy: axiosProxy, + }, + webSocketConstructor: ProxyWebSocket, +}); + +const websocketApiClient = new WebsocketAPIClient({ + webSocketConstructor: ProxyWebSocket, +}); + +async function main() { + try { + const status = await restClient.getExchangeStatus(); + console.log('REST API through proxy:', status); + } catch (error) { + console.error('Proxied REST API request failed:', error); + } + + websocketClient.on('message', ({ channel, data }) => { + if (channel === 'allMids') { + console.log('Stream through proxy:', data.mids.BTC); } - | { nonUserCancel: Array<{ coin: string; oid: number }> } - | { twapHistory: TwapHistoryEntry[] } - | { twapSliceFills: TwapSliceFill[] }; -⋮---- -export interface WebData3PerpDexState { - leadingVaults?: LeadingVault[]; - perpsAtOpenInterestCap?: string[]; - totalVaultEquity: string; + }); + + try { + await websocketClient.subscribe({ type: 'allMids' }); + } catch (error) { + console.error('Proxied stream subscription failed:', error); + } + + try { + const mids = await websocketApiClient.getAllMids(); + console.log('WebSocket API through proxy:', mids.BTC); + } catch (error) { + console.error('Proxied WebSocket API request failed:', error); + } +} + +main(); +``` + +Set `keepAlive: false` so the SDK preserves the custom agent, and set `proxy: false` so Axios does not apply separate environment-proxy handling on top of it. + +`webSocketConstructor` routes the socket itself. Streaming subscriptions send native topics without REST API metadata requests. `requestOptions.proxy` routes REST API metadata requests if you explicitly call `nameToCoin()` or `resolveAsset()` on the streaming client. + +### SOCKS5 proxy + + + +```javascript +import { SocksProxyAgent } from 'socks-proxy-agent'; +import WebSocket from 'ws'; +import { + RestClient, + WebsocketAPIClient, + WebsocketClient, +} from '@siebly/hyperliquid-api'; + +const proxyUrl = process.env.HYPERLIQUID_SOCKS_PROXY_URL; + +if (!proxyUrl) { + throw new Error('Set HYPERLIQUID_SOCKS_PROXY_URL before starting.'); } -⋮---- -export interface WebData3Event { - perpDexStates: WebData3PerpDexState[]; - userState: { - abstraction?: 'disabled' | 'portfolioMargin' | 'unifiedAccount'; - agentAddress: string | null; - agentValidUntil: number | null; - cumLedger: string; - dexAbstractionEnabled?: boolean; - isVault: boolean; - optOutOfSpotDusting?: true; - serverTime: number; - user: string; - }; + +const agent = new SocksProxyAgent(proxyUrl); + +class ProxyWebSocket { + constructor(url) { + this.socket = Reflect.construct(WebSocket, [url, { agent }]); + this.onopen = null; + this.onmessage = null; + this.onerror = null; + this.onclose = null; + + this.socket.onopen = () => this.onopen?.(); + this.socket.onmessage = (event) => this.onmessage?.({ data: event.data }); + this.socket.onerror = (event) => this.onerror?.(event); + this.socket.onclose = (event) => + this.onclose?.({ code: event.code, reason: String(event.reason) }); + } + + get readyState() { + return this.socket.readyState; + } + + send(data) { + this.socket.send(data); + } + + close(code, reason) { + this.socket.close(code, reason); + } } -⋮---- -export type GenericWebsocketEvent = - Record | unknown[] | string | number | boolean | null; -⋮---- -/** Payload types keyed by subscription type. Wire envelopes are defined in ws-events. */ -export interface WebsocketSubscriptionData { - activeAssetCtx: AssetContextEvent; - activeAssetData: ActiveAssetDataEvent; - allDexsAssetCtxs: AllDexsAssetCtxsEvent; - allDexsClearinghouseState: AllDexsClearinghouseStateEvent; - allMids: AllMidsEvent; - assetCtxs: AssetCtxsEvent; - bbo: BboEvent; - candle: Candle; - clearinghouseState: ClearinghouseStateEvent; - fastAssetCtxs: FastAssetCtxsEvent; - l2Book: OrderBookResponse; - notification: NotificationEvent; - openOrders: OpenOrdersEvent; - orderUpdates: OrderUpdateEvent[]; - outcomeMetaUpdates: OutcomeMetaUpdatesEvent; - spotAssetCtxs: SpotAssetCtxsEvent; - spotState: SpotStateEvent; - trades: TradeEvent[]; - twapStates: TwapStatesEvent; - userEvents: UserEvent; - userFills: UserFillsEvent; - userFundings: UserFundingsEvent; - userHistoricalOrders: UserHistoricalOrdersEvent; - userNonFundingLedgerUpdates: UserNonFundingLedgerUpdatesEvent; - userTwapHistory: UserTwapHistoryEvent; - userTwapSliceFills: UserTwapSliceFillsEvent; - webData3: WebData3Event; + +const restClient = new RestClient( + { keepAlive: false }, + { + httpsAgent: agent, + proxy: false, + }, +); + +const websocketClient = new WebsocketClient({ + webSocketConstructor: ProxyWebSocket, +}); + +const websocketApiClient = new WebsocketAPIClient({ + webSocketConstructor: ProxyWebSocket, +}); + +async function main() { + try { + const status = await restClient.getExchangeStatus(); + console.log('REST API through SOCKS5:', status); + } catch (error) { + console.error('SOCKS5 REST API request failed:', error); + } + + websocketClient.on('message', ({ channel, data }) => { + if (channel === 'allMids') { + console.log('WebSocket through SOCKS5:', data.mids.BTC); + } + }); + + try { + await websocketClient.subscribe({ type: 'allMids' }); + } catch (error) { + console.error('SOCKS5 stream subscription failed:', error); + } + + try { + const mids = await websocketApiClient.getAllMids(); + console.log('WebSocket API through SOCKS5:', mids.BTC); + } catch (error) { + console.error('SOCKS5 WebSocket API request failed:', error); + } } -⋮---- -export type WebsocketEventFor = - WebsocketSubscriptionData[S['type']]; -================ -File: src/index.ts -================ +main(); +``` +Set `keepAlive: false` so the REST API client preserves the SOCKS agent. Streaming subscriptions do not trigger REST API metadata lookups. Resolve readable aliases with the configured `restClient.nameToCoin()` first, then pass the result to `websocketClient.subscribe()`, so alias resolution also follows the SOCKS route. -================ -File: src/WebsocketAPIClient.ts -================ -import { HyperliquidWebSocketError } from './lib/websocket/errors.js'; -import { - DefaultLogger, - type DefaultLogger as Logger, -} from './lib/websocket/logger.js'; -import { RestClient } from './RestClient.js'; -import type { - WebsocketAPIClientOptions, - WebsocketClientEventMap, -} from './types/websockets/ws-general.js'; -import { WebsocketClient } from './WebsocketClient.js'; -⋮---- -export class WebsocketAPIClient extends RestClient -⋮---- -constructor( - options: WebsocketAPIClientOptions = {}, - logger: Logger = DefaultLogger, -) -⋮---- -getWSClient(): WebsocketClient -⋮---- -connect(): Promise -⋮---- -on( - event: K, - listener: (...args: WebsocketClientEventMap[K]) => void, -): this -⋮---- -once( - event: K, - listener: (...args: WebsocketClientEventMap[K]) => void, -): this -⋮---- -off( - event: K, - listener: (...args: WebsocketClientEventMap[K]) => void, -): this -⋮---- -isConnected(): boolean -⋮---- -close(): void -⋮---- -closeAll(): void -⋮---- -protected override post(path: string, body: unknown): Promise +A proxy changes the network route, not the account, network, or available products. Monitor latency, disconnects, and the proxy's egress IP. + +## Understand Siebly referral and builder settings + +The SDK includes two separate Hyperliquid features: + +- Referral code: around the first supported trading action from a client instance, the SDK makes one passive attempt to apply `SIEBLY`. Hyperliquid leaves an existing referrer unchanged. Trading continues if the referral request fails. +- Builder code: after the account has approved Siebly's builder fee, the SDK can include the builder code on eligible orders. The configured maximum is 0.1%. Approval must be signed by the main wallet, not an API wallet. If a main-wallet private key is configured and approval is missing, the SDK may attempt that approval in the background. An API wallet cannot grant it. + +Builder approval is optional. Call `approveBuilderFee()` only from a client configured with the main wallet signer and only after reviewing Hyperliquid's current [builder-code documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/builder-codes). The approval sets a maximum and can be revoked through Hyperliquid. + +## Production checklist + +- **Keep signers isolated.** Use a separate approved API wallet per trading process and, where practical, per subaccount. +- **Query the correct address.** Account reads use the master account, subaccount, or vault address rather than the API wallet address. +- **Keep time synchronized.** Signed nonces are millisecond timestamps and must remain inside Hyperliquid's accepted time window. +- **Use exact decimal strings.** Avoid JavaScript floating-point arithmetic for stored financial values. Trim trailing decimal zeroes before native wire-shaped actions. +- **Read current metadata.** Resolve assets and use the latest `szDecimals`, universe names, margin tables, and market status. +- **Meet minimum notional.** Hyperliquid currently requires at least 10 units of the quote asset for an order. +- **Check every action status.** A top-level `ok` response may contain an order-specific `error` entry. +- **Reconcile uncertain writes.** Query by [custom order ID](https://siebly.io/reference/glossary#custom-order-id) before retrying a timed-out submission. +- **Confirm final order state.** Treat the first response as [pending confirmation](https://siebly.io/reference/glossary#pending-confirmation), then use `getOrderStatus()` or `orderUpdates`. +- **Use action deadlines where useful.** `expiresAfter` limits how long a signed action remains valid. A stale deadline rejection currently consumes extra address-based rate-limit allowance. +- **Use a dead-man switch.** Refresh `setCancelAllAfter()` from a monitored process when unattended resting orders need automatic cancellation. +- **Recover after gaps.** Rebuild the affected balances, positions, orders, and fills after a reconnect. +- **Respect current limits.** Hyperliquid applies IP, address, open-order, connection, subscription, and in-flight request limits. Check the current [rate-limit documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/rate-limits-and-user-limits). +- **Keep keys server-side.** Browser use should be limited to public REST API calls and public-address WebSocket subscriptions. +- **Monitor the route.** Track latency, reconnect frequency, and egress IP when using a proxy or custom endpoint. + +## Frequently asked questions + +### Can I use the SDK with plain JavaScript? + +Yes. All examples in this guide are JavaScript. The package also includes TypeScript declarations. + +### Which client should I start with? + +Start with `RestClient` for public market data and account state. Add `WebsocketClient` for live subscriptions. Use `WebsocketAPIClient` when you need awaitable Info or Exchange requests over WebSocket. + +### Why do account reads work without a private key? + +Hyperliquid Info queries use a public account address. A signer is needed for Exchange actions that change orders, funds, leverage, or account settings. + +### Why are my balances empty when I use an API wallet? + +Account queries need the master account, subaccount, or vault address. The API wallet address identifies the signer, not the account whose state is being queried. Set `accountAddress` on the client. + +### What is the difference between a wallet and an API wallet? + +The main wallet owns the account. An approved API wallet signs actions on its behalf. Use an API wallet for an automated process and keep the main wallet key outside that process. + +### Why are REST API response shapes different? + +Hyperliquid's Info endpoint selects an operation through the request body, and each operation has its own response schema. The SDK returns that response directly. + +### Why did an order fail when the response status was `ok`? + +The top-level status describes the action request. Each order has its own entry under `response.data.statuses`, which may contain `resting`, `filled`, or `error`. + +### Does `submitMarketOrder()` send a native market order? + +It sends an immediate-or-cancel limit order with a protection price. The SDK derives and rounds that price from the midpoint when one is not supplied. + +### How do I tell perpetual and Spot assets apart? + +Perpetuals commonly use names such as `BTC`. Spot markets use native universe names such as `PURR/USDC` or `@107`. REST API helpers can resolve readable metadata-derived aliases such as `HYPE/USDC`. For streaming subscriptions, call `nameToCoin()` explicitly to resolve an alias, then subscribe with the returned native coin name. + +### Do account WebSocket streams require authentication? + +They use the public account address. The SDK isolates user-scoped channels when needed and resubscribes after reconnecting. + +### How do subscription acknowledgements appear? + +Listen for `response` events with `channel === 'subscriptionResponse'`. Snapshot and update envelopes arrive through `message`. Both events include `wsKey`, and the promise returned by `subscribe()` only confirms that the subscription request was sent. + +### What is the difference between `WebsocketClient` and `WebsocketAPIClient`? + +`WebsocketClient` consumes ongoing subscriptions. `WebsocketAPIClient` sends one Info or Exchange request and resolves the matching response as a promise. + +### Should I retry a timed-out order submission? + +Not immediately. Query `getOrderStatus()` with the client order ID first. A request can reach Hyperliquid even when the response does not reach your process. + +### How do I use a vault or subaccount? + +Set its address as `vaultAddress` for signed actions and use the same address for its account-state queries. The API wallet must be approved to act for the relevant account. + +### Can I use Mainnet credentials on Testnet? + +Keep the networks separate. Approval state, account balances, assets, and API-wallet registration differ between Mainnet and Testnet. + +### Does a proxy change which network I use? + +No. `testnet`, `baseUrl`, and `wsUrl` select the destination. A proxy only changes the network path used to reach it. + +## Next steps + +1. Run the public REST API example and inspect the current BTC metadata and order book. +2. Set a public account address and load balances, positions, open orders, and fills. +3. Add public and account WebSocket subscriptions, including reconnect handling. +4. Approve a Testnet API wallet and run the perpetual order lifecycle. +5. Add client order IDs, action deadlines, dead-man-switch refresh, and scoped recovery before moving a strategy to Mainnet. + +Continue with the [Hyperliquid SDK examples](https://siebly.io/examples/Hyperliquid), browse the [source repository](https://github.com/sieblyio/hyperliquid-api), and check the [official Hyperliquid API documentation](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api) for current exchange behavior. ================ File: .gitignore @@ -3698,119 +3738,7 @@ README_BEFORE_RELEASE.md examples/tstests /tmp/ localtest.sh - -================ -File: package.json -================ -{ - "name": "@siebly/hyperliquid-api", - "version": "0.0.2", - "description": "Node.js and TypeScript SDK for Hyperliquid REST APIs and WebSockets.", - "scripts": { - "clean": "node scripts/clean.mjs", - "build": "npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/postbuild.mjs", - "lint": "eslint src", - "test": "jest --passWithNoTests", - "test:extensions": "tsc -p tsconfig.extensions.json", - "test:packed": "npm run build && node test/packaging/packed-consumer-smoke.mjs", - "test:types": "npm run test:extensions && tsc -p test/tsconfig.test.json --noEmit && tsc -p test/types/tsconfig.json", - "test:unit": "jest --passWithNoTests --runInBand test/unit" - }, - "main": "dist/cjs/index.js", - "module": "dist/mjs/index.js", - "types": "dist/mjs/index.d.ts", - "exports": { - ".": { - "types": "./dist/mjs/index.d.ts", - "browser": "./dist/mjs/index.js", - "import": "./dist/mjs/index.js", - "require": "./dist/cjs/index.js", - "default": "./dist/mjs/index.js" - } - }, - "browser": { - "./dist/mjs/lib/https-agent.js": "./dist/mjs/lib/https-agent.browser.js", - "./dist/cjs/lib/https-agent.js": "./dist/cjs/lib/https-agent.browser.js", - "./dist/cjs/lib/event-emitter.js": "./dist/cjs/lib/event-emitter.browser.js", - "./dist/mjs/lib/event-emitter.js": "./dist/mjs/lib/event-emitter.browser.js" - }, - "type": "module", - "sideEffects": false, - "packageManager": "npm@11.12.0", - "files": [ - "dist/*", - "llms.txt" - ], - "author": "Siebly.io (https://github.com/sieblyio)", - "contributors": [ - "Tiago Siebler (https://github.com/tiagosiebler)" - ], - "dependencies": { - "@msgpack/msgpack": "^3.1.2", - "axios": "1.20.0", - "ethers": "^6.16.0", - "events": "3.3.0", - "fflate": "^0.8.2", - "isomorphic-ws": "^5.0.0", - "ws": "8.21.0" - }, - "devDependencies": { - "@eslint/compat": "2.1.0", - "@types/jest": "^30.0.0", - "@types/node": "^24.10.14", - "@types/ws": "^8.18.1", - "@typescript-eslint/eslint-plugin": "8.70.0", - "@typescript-eslint/parser": "8.70.0", - "esbuild": "0.27.2", - "eslint": "^10.3.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.5", - "eslint-plugin-require-extensions": "0.1.3", - "eslint-plugin-simple-import-sort": "^13.0.0", - "jest": "^30.4.2", - "prettier": "^3.8.3", - "typescript": "^6.0.3" - }, - "overrides": { - "@babel/core": "7.29.6", - "form-data": "4.0.6", - "js-yaml": "3.15.2" - }, - "keywords": [ - "hyperliquid", - "hyperliquid api", - "hyperliquid nodejs", - "hyperliquid javascript", - "hyperliquid typescript", - "hyperliquid websocket", - "hip-3", - "algo trading", - "api", - "sdk", - "websocket", - "rest", - "trading bots", - "cryptocurrency", - "siebly", - "siebly.io" - ], - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/tiagosiebler" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/sieblyio/hyperliquid-api.git" - }, - "bugs": { - "url": "https://github.com/sieblyio/hyperliquid-api/issues" - }, - "homepage": "https://github.com/sieblyio/hyperliquid-api#readme", - "engines": { - "node": ">=22.0.0" - } -} +rest-ts.ts ================ File: src/types/response/info.types.ts @@ -5001,7 +4929,7 @@ export class WebsocketClient extends BaseWebsocketClient< constructor( options: WebsocketClientOptions = {}, logger: Logger = DefaultLogger, -) + ) ⋮---- connect(wsKey: WsKey = 'public'): Promise ⋮---- @@ -5021,18 +4949,18 @@ close(wsKey?: WsKey): void */ async subscribe( subscriptions: WebsocketSubscription | readonly WebsocketSubscription[], -): Promise + ): Promise ⋮---- /** Remove cached topics independently of event listeners, including while disconnected. */ async unsubscribe( subscriptions: WebsocketSubscription | readonly WebsocketSubscription[], -): Promise + ): Promise ⋮---- async sendWSAPIRequest( endpoint: 'action' | 'info', body: unknown, options: RequestOptions = {}, -): Promise + ): Promise ⋮---- resolveAsset(identifier: number | string): Promise ⋮---- @@ -5042,7 +4970,7 @@ refreshAssetMetadata(): Promise ⋮---- private groupSubscriptions( subscriptions: WebsocketSubscription | readonly WebsocketSubscription[], -): Map + ): Map ⋮---- protected override getWsUrl(): string ⋮---- @@ -5052,26 +4980,26 @@ protected override getWsRequestEvents( _wsKey: WsKey, operation: WSOperation, topics: WebsocketSubscription[], -): object[] + ): object[] ⋮---- protected override sendPingEvent(wsKey: WsKey): boolean ⋮---- protected override resolveEmittableEvents( wsKey: WsKey, message: string, -): EmittableEvent[] + ): EmittableEvent[] ⋮---- // Emit each wire message once. Subscription ownership never controls listeners. // Keep upstream channel names (including user and activeSpotAssetCtx) unchanged. ⋮---- private handlePostMessage( record: Record, -): 'exception' | 'response' + ): 'exception' | 'response' ⋮---- private logUnmatchedResponse( requestId: unknown, parsed: Record, -): void + ): void ⋮---- // IDs are monotonic for this client. A known ID without a pending promise has // already settled (for example by timeout or abort); its late reply is expected. @@ -5119,6 +5047,7 @@ import type { SubmitMarketCloseRequest, SubmitMarketOrderRequest, SubmitOrderRequest, + SubmitTrailingStopRequest, SubmitTwapOrderRequest, SubmitUserOutcomeRequest, TransferSpotToOrFromSubAccountRequest, @@ -5153,6 +5082,7 @@ import type { CancelResponseData, ExchangeResponse, OrderResponseData, + TrailingStopResponseData, TwapCancelResponseData, TwapOrderResponseData, } from './types/response/exchange.types.js'; @@ -5242,7 +5172,7 @@ export class RestClient extends BaseRestClient constructor( options: RestClientOptions = {}, networkOptions: RestClientNetworkOptions = {}, -) + ) ⋮---- resolveAsset(identifier: AssetIdentifier): Promise ⋮---- @@ -5258,7 +5188,7 @@ getAccountAddress(): Promise * An empty book falls back to the last trade price. Spot mids are included * only when `dex` is omitted or empty. */ -getAllMids(params: +getAllMids(params: { dex?: string } = {}): Promise ⋮---- /** * User's active asset data (`activeAssetData`): leverage, max trade size, @@ -5266,7 +5196,7 @@ getAllMids(params: */ getActiveAssetData( params: AccountRequest & { coin: string }, -): Promise + ): Promise ⋮---- /** * Borrow and lend reserve state for every token (`allBorrowLendReserveStates`). @@ -5286,14 +5216,14 @@ getAllPerpMetadata(): Promise /** * Builder addresses this user has approved (`approvedBuilders`). */ -getApprovedBuilders(params: AccountRequest = +getApprovedBuilders(params: AccountRequest = {}): Promise ⋮---- /** * Borrow and lend reserve state for one token (`borrowLendReserveState`). */ getBorrowLendReserveState(params: { token: number; -}): Promise + }): Promise ⋮---- /** * User borrow and lend balances (`borrowLendUserState`). @@ -5302,7 +5232,7 @@ getBorrowLendReserveState(params: { */ getUserBorrowLendState( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- /** * Candle snapshot (`candleSnapshot`). @@ -5321,14 +5251,14 @@ async getCandles(params: GetCandlesRequest): Promise */ getPerpAccountSummary( params: AccountDexRequest = {}, -): Promise + ): Promise ⋮---- /** * Stake delegated to validators (`delegations`). */ getStakingDelegations( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- /** * Staking history (`delegatorHistory`). @@ -5337,23 +5267,23 @@ getStakingDelegations( */ getStakingHistory( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- /** * Staking rewards (`delegatorRewards`). * * `source` is `"delegation"` or `"commission"` in the documented samples. */ -getStakingRewards(params: AccountRequest = +getStakingRewards(params: AccountRequest = {}): Promise ⋮---- /** * Staking summary (`delegatorSummary`): delegated, undelegated, and pending withdrawals. */ -getStakingSummary(params: AccountRequest = +getStakingSummary(params: AccountRequest = {}): Promise ⋮---- getExchangeStatus(): Promise ⋮---- -getExtraAgents(params: AccountRequest = +getExtraAgents(params: AccountRequest = {}): Promise ⋮---- /** * Open orders with trigger and reduce-only fields (`frontendOpenOrders`). @@ -5362,14 +5292,14 @@ getExtraAgents(params: AccountRequest = */ getFrontendOpenOrders( params: AccountDexRequest = {}, -): Promise + ): Promise ⋮---- /** * Historical funding rates for one coin (`fundingHistory`). */ async getFundingHistory( params: GetFundingHistoryRequest, -): Promise + ): Promise ⋮---- getGossipPriorityAuctionStatus(): Promise ⋮---- @@ -5380,9 +5310,9 @@ getGossipRootIps(): Promise * * Returns at most 2000 orders. `status` uses the same values as `getOrderStatus()`. */ -getOrderHistory(params: AccountRequest = +getOrderHistory(params: AccountRequest = {}): Promise ⋮---- -getVipStatus(params: AccountRequest = +getVipStatus(params: AccountRequest = {}): Promise ⋮---- /** * L2 order book (`l2Book`). @@ -5392,15 +5322,15 @@ getVipStatus(params: AccountRequest = */ async getOrderBook( params: GetOrderBookRequest, -): Promise + ): Promise ⋮---- -getLeadingVaults(params: AccountRequest = +getLeadingVaults(params: AccountRequest = {}): Promise ⋮---- -getLegalStatus(params: AccountRequest = +getLegalStatus(params: AccountRequest = {}): Promise ⋮---- getLiquidatablePositions(): Promise ⋮---- -getMarginTable(params: +getMarginTable(params: { id: number }): Promise ⋮---- /** * Maximum approved builder fee (`maxBuilderFee`). @@ -5416,7 +5346,7 @@ getMaxMarketOrderNotionals(): Promise * * `dex` defaults to the empty string, which is the first perp DEX. */ -getPerpMetadata(params: +getPerpMetadata(params: { dex?: string } = {}): Promise ⋮---- /** * Perp metadata plus asset contexts (`metaAndAssetCtxs`). @@ -5427,7 +5357,7 @@ getPerpMetadata(params: */ getPerpAssetContexts( params: { dex?: string } = {}, -): Promise + ): Promise ⋮---- /** * Open orders (`openOrders`). @@ -5435,7 +5365,7 @@ getPerpAssetContexts( * Spot orders are included only for the first perp DEX. `side` `"B"` is bid * and `"A"` is ask. */ -getOpenOrders(params: AccountDexRequest = +getOpenOrders(params: AccountDexRequest = {}): Promise ⋮---- /** * Order status by order id or client order id (`orderStatus`). @@ -5457,14 +5387,14 @@ getOutcomeMetadata(): Promise */ getOutcomeDeployerLimits(params: { venue: string; -}): Promise + }): Promise ⋮---- /** * Perp annotation for one coin (`perpAnnotation`). * * Returns `null` when the coin has no annotation. */ -getPerpAnnotation(params: +getPerpAnnotation(params: { coin: string }): Promise ⋮---- /** * Perp category for each listed coin (`perpCategories`). @@ -5490,14 +5420,14 @@ getPerpDeployAuctionStatus(): Promise * * `dex` is required. The empty string is not allowed. */ -getPerpDexLimits(params: +getPerpDexLimits(params: { dex: string }): Promise ⋮---- /** * Perp DEX deposit status (`perpDexStatus`). * * The empty string is the first perp DEX. */ -getPerpDexStatus(params: +getPerpDexStatus(params: { dex: string }): Promise ⋮---- /** * All perpetual DEXs (`perpDexs`). @@ -5512,7 +5442,7 @@ getPerpDexs(): Promise> * * `dex` selects a builder-deployed market. Omitted means the first perp DEX. */ -getPerpsAtOpenInterestCap(params: +getPerpsAtOpenInterestCap(params: { dex?: string } = {}): Promise ⋮---- /** * Account value, PnL, and volume history (`portfolio`). @@ -5520,11 +5450,11 @@ getPerpsAtOpenInterestCap(params: * Each entry is `[period, snapshot]`. Periods include `day`, `week`, * `month`, `allTime`, and the `perp*` variants. */ -getPortfolio(params: AccountRequest = +getPortfolio(params: AccountRequest = {}): Promise ⋮---- getPreTransferCheck( params: GetPreTransferCheckRequest, -): Promise + ): Promise ⋮---- /** * Predicted funding rates by venue (`predictedFundings`). @@ -5535,7 +5465,7 @@ getPreTransferCheck( */ getPredictedFundingRates(): Promise ⋮---- -getRecentTrades(params: +getRecentTrades(params: { coin: string }): Promise ⋮---- /** * Referral state (`referral`). @@ -5544,7 +5474,7 @@ getRecentTrades(params: * is `"ready"` or `"needToTrade"`. Claimed rewards now also appear in the * non-funding ledger. `rewardHistory` is legacy. */ -getReferralInfo(params: AccountRequest = +getReferralInfo(params: AccountRequest = {}): Promise ⋮---- /** * Settled outcome (`settledOutcome`). @@ -5553,11 +5483,11 @@ getReferralInfo(params: AccountRequest = */ getSettledOutcome(params: { outcome: number; -}): Promise + }): Promise ⋮---- async getBalances( params: AccountDexRequest = {}, -): Promise + ): Promise ⋮---- /** * Spot token balances (`spotClearinghouseState`). @@ -5565,14 +5495,14 @@ async getBalances( * Under unified account or portfolio margin, this is the trading-account * balance across spot and perps. */ -getSpotBalances(params: AccountRequest = +getSpotBalances(params: AccountRequest = {}): Promise ⋮---- /** * Spot deploy state for a user (`spotDeployState`), including the gas auction. */ getSpotDeployAuctionStatus( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- /** * Spot metadata (`spotMeta`): tokens and trading pairs. @@ -5597,11 +5527,11 @@ getSpotPairDeployAuctionStatus(): Promise * * Returns `null` when the address has no subaccounts. */ -getSubAccounts(params: AccountRequest = +getSubAccounts(params: AccountRequest = {}): Promise ⋮---- getSubAccountsWithAllDexStates( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- /** * Token details (`tokenDetails`): supply, prices, and genesis balances. @@ -5609,28 +5539,28 @@ getSubAccountsWithAllDexStates( * `tokenId` is the 34-character onchain id, for example * `0x00000000000000000000000000000000`. */ -getTokenDetails(params: +getTokenDetails(params: { tokenId: string }): Promise ⋮---- -getTwapHistory(params: AccountRequest = +getTwapHistory(params: AccountRequest = {}): Promise ⋮---- /** * Account abstraction mode (`userAbstraction`). */ -getUserAbstraction(params: AccountRequest = +getUserAbstraction(params: AccountRequest = {}): Promise ⋮---- getUserBorrowLendInterest( params: GetUserBorrowLendInterestRequest, -): Promise + ): Promise ⋮---- /** * Whether HIP-3 DEX abstraction is enabled (`userDexAbstraction`). */ -getUserDexAbstraction(params: AccountRequest = +getUserDexAbstraction(params: AccountRequest = {}): Promise ⋮---- /** * User fee rates and the current fee schedule (`userFees`). */ -getUserFees(params: AccountRequest = +getUserFees(params: AccountRequest = {}): Promise ⋮---- /** * Recent fills (`userFills`). @@ -5638,7 +5568,7 @@ getUserFees(params: AccountRequest = * Returns at most 2000 fills. `builderFee` is omitted when it is zero. * `side` `"B"` is buy and `"A"` is sell. */ -getUserFills(params: GetUserFillsRequest = +getUserFills(params: GetUserFillsRequest = {}): Promise ⋮---- /** * Fills in a time range (`userFillsByTime`). @@ -5655,7 +5585,7 @@ getUserFillsByTime(params: GetUserFillsByTimeRequest): Promise */ getUserFundingHistory( params: GetUserFundingHistoryRequest = {}, -): Promise + ): Promise ⋮---- /** * User non-funding ledger updates (`userNonFundingLedgerUpdates`): @@ -5666,7 +5596,7 @@ getUserFundingHistory( */ getUserNonFundingLedgerUpdates( params: GetUserNonFundingLedgerUpdatesRequest = {}, -): Promise + ): Promise ⋮---- /** * Address request usage and cap (`userRateLimit`). @@ -5674,7 +5604,7 @@ getUserNonFundingLedgerUpdates( * `nRequestsUsed` is usage above the reserved amount. * `nRequestsSurplus` is reserved amount not yet used. */ -getUserRateLimit(params: AccountRequest = +getUserRateLimit(params: AccountRequest = {}): Promise ⋮---- /** * Account role (`userRole`): user, agent, vault, subaccount, or missing. @@ -5686,25 +5616,25 @@ getUserRole(params: UserRequest): Promise ⋮---- getMultiSigSigners( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- /** * Recent TWAP slice fills (`userTwapSliceFills`). * * Returns at most 2000 fills. TWAP fill hashes are zero. */ -getUserTwapSliceFills(params: AccountRequest = +getUserTwapSliceFills(params: AccountRequest = {}): Promise ⋮---- getUserTwapSliceFillsByTime( params: GetUserTwapSliceFillsByTimeRequest, -): Promise + ): Promise ⋮---- /** * Vault equity for this user (`userVaultEquities`). */ getUserVaultDeposits( params: AccountRequest = {}, -): Promise + ): Promise ⋮---- getValidatorL1Votes(): Promise ⋮---- @@ -5718,16 +5648,16 @@ getValidatorSummaries(): Promise */ getVaultDetails( params: GetVaultDetailsRequest, -): Promise + ): Promise ⋮---- getVaultSummaries(): Promise ⋮---- -getWebData2(params: AccountRequest = +getWebData2(params: AccountRequest = {}): Promise ⋮---- /** Published response schema; the public API currently rejects this request type. */ getAlignedQuoteTokenStatus(params: { token: number; -}): Promise + }): Promise ⋮---- /** * Place one or more orders (`order`). @@ -5738,7 +5668,7 @@ getAlignedQuoteTokenStatus(params: { async submitOrder( params: SubmitOrderRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Place an IOC order around the current mid (`order`). @@ -5748,7 +5678,7 @@ async submitOrder( async submitMarketOrder( params: SubmitMarketOrderRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Close a perp position with a reduce-only IOC order (`order`). @@ -5759,7 +5689,7 @@ async submitMarketOrder( async submitMarketClose( params: SubmitMarketCloseRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Place a limit order (`order`). @@ -5769,7 +5699,7 @@ async submitMarketClose( async submitLimitOrder( params: SubmitLimitOrderRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Modify one resting order (`modify`). @@ -5784,7 +5714,7 @@ async submitLimitOrder( async modifyOrder( params: ModifyOrderRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Modify multiple resting orders (`batchModify`). @@ -5795,7 +5725,7 @@ async modifyOrder( async modifyOrders( params: ModifyOrdersRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Cancel orders by order id (`cancel`). @@ -5806,7 +5736,7 @@ async modifyOrders( async cancelOrders( params: CancelOrdersRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Cancel orders by client order id (`cancelByCloid`). @@ -5816,7 +5746,7 @@ async cancelOrders( async cancelOrdersByClientOrderId( params: CancelOrdersByClientOrderIdRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Set cross or isolated leverage for one asset (`updateLeverage`). @@ -5824,7 +5754,7 @@ async cancelOrdersByClientOrderId( updateLeverage( params: UpdateLeverageRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Add or remove isolated margin (`updateIsolatedMargin`). @@ -5834,7 +5764,7 @@ updateLeverage( updateIsolatedMargin( params: UpdateIsolatedMarginRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Set isolated margin to a target leverage (`topUpIsolatedOnlyMargin`). @@ -5844,7 +5774,7 @@ updateIsolatedMargin( setIsolatedMarginLeverage( params: SetIsolatedMarginLeverageRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Place a TWAP order (`twapOrder`). @@ -5854,7 +5784,7 @@ setIsolatedMarginLeverage( submitTwapOrder( params: SubmitTwapOrderRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- /** * Cancel a TWAP order (`twapCancel`). @@ -5864,7 +5794,19 @@ submitTwapOrder( cancelTwapOrder( params: CancelTwapOrderRequest, options: L1RequestOptions = {}, -): Promise> + ): Promise> +⋮---- +/** + * Place a trailing stop order for perpetuals only (`trailingStop`). + * + * `asset` is a numeric asset ID. `retracement` is a percent string such as + * `{ pct: '1.234%' }` or a price distance such as `{ px: '100' }`. + * A successful request returns `{ oid }`. + */ +submitTrailingStop( + params: SubmitTrailingStopRequest, + options: L1RequestOptions = {}, + ): Promise> ⋮---- /** * Schedule a cancel-all, or clear one (`scheduleCancel`). @@ -5875,14 +5817,14 @@ cancelTwapOrder( setCancelAllAfter( params: { time?: number } = {}, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- -setReferrer(options: L1RequestOptions = +setReferrer(options: L1RequestOptions = {}): Promise ⋮---- registerReferrer( params: { code: string }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Approve the configured builder fee (`approveBuilderFee`). @@ -5891,7 +5833,7 @@ registerReferrer( */ async approveBuilderFee( options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Approve an API wallet (`approveAgent`). @@ -5903,7 +5845,7 @@ async approveBuilderFee( approveApiWallet( params: ApproveApiWalletRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Send USDC to another address on HyperCore (`usdSend`). @@ -5913,7 +5855,7 @@ approveApiWallet( sendUsd( params: SendUsdRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Send a spot token to another address on HyperCore (`spotSend`). @@ -5923,7 +5865,7 @@ sendUsd( sendSpotAsset( params: SendSpotAssetRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Start a USDC withdrawal to the bridge (`withdraw3`). @@ -5931,7 +5873,7 @@ sendSpotAsset( withdrawUsd( params: WithdrawUsdRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Move USDC between spot and perp (`usdClassTransfer`). @@ -5941,7 +5883,7 @@ withdrawUsd( transferUsdBetweenSpotAndPerp( params: TransferUsdBetweenSpotAndPerpRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Transfer collateral between perp DEXs, spot, users, or subaccounts (`sendAsset`). @@ -5952,7 +5894,7 @@ transferUsdBetweenSpotAndPerp( sendAsset( params: SendAssetRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Send tokens from Core to EVM with calldata (`sendToEvmWithData`). @@ -5962,7 +5904,7 @@ sendAsset( sendToEvmWithData( params: SendToEvmWithDataRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Transfer collateral with an API wallet (`agentSendAsset`). @@ -5972,7 +5914,7 @@ sendToEvmWithData( sendAssetFromApiWallet( params: SendAssetFromApiWalletRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Move native token from spot into staking (`cDeposit`). @@ -5982,7 +5924,7 @@ sendAssetFromApiWallet( depositIntoStaking( params: { wei: number }, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Move native token from staking back to spot (`cWithdraw`). @@ -5992,7 +5934,7 @@ depositIntoStaking( withdrawFromStaking( params: { wei: number }, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Delegate or undelegate stake to a validator (`tokenDelegate`). @@ -6002,27 +5944,27 @@ withdrawFromStaking( delegateOrUndelegateStake( params: DelegateOrUndelegateStakeRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- convertToMultiSigUser( params: ConvertToMultiSigUserRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- linkStakingAccount( params: LinkStakingAccountRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- disableStakingTradingLink( params: { tradingUser: string }, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- setUserPortfolioMargin( params: UserToggleRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Set account abstraction (`userSetAbstraction`). @@ -6032,7 +5974,7 @@ setUserPortfolioMargin( setUserAbstraction( params: SetUserAbstractionRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Enable or disable HIP-3 DEX abstraction (`userDexAbstraction`). @@ -6042,7 +5984,7 @@ setUserAbstraction( setUserDexAbstraction( params: UserToggleRequest, options: UserSignedRequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Enable HIP-3 DEX abstraction from an API wallet (`agentEnableDexAbstraction`). @@ -6051,7 +5993,7 @@ setUserDexAbstraction( */ enableApiWalletDexAbstraction( options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Set account abstraction from an API wallet (`agentSetAbstraction`). @@ -6061,7 +6003,7 @@ enableApiWalletDexAbstraction( setApiWalletAbstraction( params: { abstraction: 'i' | 'p' | 'u' }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- executeBorrowLendOperation( params: { @@ -6070,27 +6012,27 @@ executeBorrowLendOperation( token: number; }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Claim rewards (`claimRewards`). */ -claimRewards(options: L1RequestOptions = +claimRewards(options: L1RequestOptions = {}): Promise ⋮---- createSubAccount( params: { name: string }, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- createVault( params: { description: string; initialUsd: number; name: string }, options: L1RequestOptions = {}, -): Promise> + ): Promise> ⋮---- setEvmBigBlocks( params: { usingBigBlocks: boolean }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- finalizeEvmContract( params: { @@ -6101,12 +6043,12 @@ finalizeEvmContract( token: number; }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- submitGossipPriorityBid( params: { ip: string; maxGas: number; slotId: 0 | 1 }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Deposit or withdraw from an HIP-3 backstop liquidator (`hip3LiquidatorTransfer`). @@ -6117,22 +6059,22 @@ submitGossipPriorityBid( transferHip3LiquidatorFunds( params: { dex: string; isDeposit: boolean; ntl: number }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Mark a nonce as used without another effect (`noop`). */ -invalidateNonce(options: L1RequestOptions = +invalidateNonce(options: L1RequestOptions = {}): Promise ⋮---- deployPerp( params: DeployPerpRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- deploySpot( params: DeploySpotRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Buy extra address request weight (`reserveRequestWeight`). @@ -6142,32 +6084,32 @@ deploySpot( reserveAdditionalActions( params: { weight: number }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- setDisplayName( params: { displayName: string }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- setSpotDusting( params: { toggleSpotDusting: { optOut: boolean } }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- renameSubAccount( params: { name: string; subAccountUser: string }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- transferSpotToOrFromSubAccount( params: TransferSpotToOrFromSubAccountRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- transferUsdToOrFromSubAccount( params: TransferUsdToOrFromSubAccountRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Deposit or withdraw vault funds (`vaultTransfer`). @@ -6175,12 +6117,12 @@ transferUsdToOrFromSubAccount( transferVaultFunds( params: TransferVaultFundsRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- distributeVaultFunds( params: { usd: number; vaultAddress: string }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- modifyVault( params: { @@ -6189,7 +6131,7 @@ modifyVault( vaultAddress: string; }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Vote the risk-free rate for an aligned quote asset (`validatorL1Stream`). @@ -6199,17 +6141,17 @@ modifyVault( updateValidatorL1Stream( params: { riskFreeRate: string }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- submitSignerAction( params: { jailSelf: null } | { unjailSelf: null }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- submitValidatorAction( params: Omit, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Authorize an AQAv2 role (`authorizeAqav2Role`). @@ -6220,7 +6162,7 @@ submitValidatorAction( authorizeAqav2Role( params: { role: 'technical' | 'treasury'; token: number }, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- /** * Split, merge, or negate outcome shares (`userOutcome`). @@ -6233,12 +6175,121 @@ authorizeAqav2Role( submitUserOutcome( params: SubmitUserOutcomeRequest, options: L1RequestOptions = {}, -): Promise + ): Promise ⋮---- private async accountInfo(type: string, params: InfoParams): Promise ⋮---- private async resolveAccountAddress(requestedUser?: string): Promise +================ +File: package.json +================ +{ + "name": "@siebly/hyperliquid-api", + "version": "1.0.3", + "description": "Professional JavaScript & Node.js SDK for Hyperliquid's REST APIs and WebSockets, built in TypeScript with strong end to end tests.", + "scripts": { + "clean": "node scripts/clean.mjs", + "build": "npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && node scripts/postbuild.mjs", + "lint": "eslint src", + "test": "jest --passWithNoTests", + "test:extensions": "tsc -p tsconfig.extensions.json", + "test:packed": "npm run build && node test/packaging/packed-consumer-smoke.mjs", + "test:types": "npm run test:extensions && tsc -p test/tsconfig.test.json --noEmit && tsc -p test/types/tsconfig.json", + "test:unit": "jest --passWithNoTests --runInBand test/unit" + }, + "main": "dist/cjs/index.js", + "module": "dist/mjs/index.js", + "types": "dist/mjs/index.d.ts", + "exports": { + ".": { + "types": "./dist/mjs/index.d.ts", + "browser": "./dist/mjs/index.js", + "import": "./dist/mjs/index.js", + "require": "./dist/cjs/index.js", + "default": "./dist/mjs/index.js" + } + }, + "browser": { + "./dist/mjs/lib/https-agent.js": "./dist/mjs/lib/https-agent.browser.js", + "./dist/cjs/lib/https-agent.js": "./dist/cjs/lib/https-agent.browser.js", + "./dist/cjs/lib/event-emitter.js": "./dist/cjs/lib/event-emitter.browser.js", + "./dist/mjs/lib/event-emitter.js": "./dist/mjs/lib/event-emitter.browser.js" + }, + "type": "module", + "sideEffects": false, + "files": [ + "dist/*", + "llms.txt" + ], + "author": "Siebly.io (https://github.com/sieblyio)", + "contributors": [ + "Tiago Siebler (https://github.com/tiagosiebler)" + ], + "dependencies": { + "@msgpack/msgpack": "^3.1.2", + "axios": "1.20.0", + "ethers": "^6.16.0", + "events": "3.3.0", + "fflate": "^0.8.2", + "isomorphic-ws": "^5.0.0", + "ws": "8.21.0" + }, + "devDependencies": { + "@eslint/compat": "2.1.0", + "@types/jest": "^30.0.0", + "@types/node": "^24.10.14", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "8.70.0", + "@typescript-eslint/parser": "8.70.0", + "esbuild": "0.27.2", + "eslint": "^10.3.0", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-require-extensions": "0.1.3", + "eslint-plugin-simple-import-sort": "^13.0.0", + "jest": "^30.4.2", + "prettier": "^3.8.3", + "typescript": "^6.0.3" + }, + "overrides": { + "@babel/core": "7.29.6", + "form-data": "4.0.6", + "js-yaml": "3.15.2" + }, + "keywords": [ + "hyperliquid", + "hyperliquid api", + "hyperliquid nodejs", + "hyperliquid javascript", + "hyperliquid typescript", + "hyperliquid websocket", + "hip-3", + "algo trading", + "api", + "sdk", + "websocket", + "rest", + "trading bots", + "cryptocurrency", + "siebly", + "siebly.io" + ], + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/tiagosiebler" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/sieblyio/hyperliquid-api.git" + }, + "bugs": { + "url": "https://github.com/sieblyio/hyperliquid-api/issues" + }, + "homepage": "https://siebly.io/sdk/hyperliquid/javascript/tutorial" +} + ================ File: README.md ================ @@ -6347,7 +6398,7 @@ Node.js 22 or newer is supported. Request and response types are exported from the package root. Every documented Info and Exchange operation has a corresponding `RestClient` method. Public lookup methods use `get...` names based on the information they return instead of exposing Hyperliquid's internal request type names. See the [examples](./examples) directory for REST API, WebSocket, and WebSocket API samples. -Exchange methods share one response wrapper: `ExchangeResponse` for acknowledgements, or `ExchangeResponse` when the response contains data, such as `ExchangeResponse` for orders and `ExchangeResponse` for a new vault or subaccount address. Check `status === 'ok'` before reading `response.data`. Raw Exchange methods use `ExchangeResponse` because the response depends on the submitted action. The same types apply to `RestClient` and `WebsocketAPIClient`. +Exchange methods share one response wrapper: `ExchangeResponse` for acknowledgements, or `ExchangeResponse` when the response contains data, such as `ExchangeResponse` for orders and `ExchangeResponse` for a new vault or subaccount address. For an order result, check `order.status === 'ok'` before reading `order.response.data`. Raw Exchange methods use `ExchangeResponse` because the response depends on the submitted action. The same types apply to `RestClient` and `WebsocketAPIClient`. ## Issues & Discussion @@ -6393,7 +6444,6 @@ const mids = await client.getAllMids(); const book = await client.getOrderBook({ coin: 'BTC', nSigFigs: 5, - mantissa: 1, }); const perpSummary = await client.getPerpAccountSummary({ user: '0x1234567890123456789012345678901234567890', @@ -6432,7 +6482,7 @@ console.log('Account:', balances.user); console.log('Spot:', balances.spot.balances); console.log('Perpetuals:', balances.perp.assetPositions); -const result = await client.submitOrder({ +const order = await client.submitOrder({ orders: [ { a: 'BTC', @@ -6444,6 +6494,8 @@ const result = await client.submitOrder({ }, ], }); + +if (order.status === 'ok') console.log(order.response.data); ``` We've added some convenient wrapper methods for common workflows: @@ -6620,9 +6672,9 @@ const book = { type: 'l2Book', coin: 'BTC', nSigFigs: 5, - mantissa: 1, fast: true, } as const; + const fills = { type: 'userFills', user: '0x1234567890123456789012345678901234567890', @@ -6816,6 +6868,9 @@ Contributions are encouraged, I will review any incoming pull requests. See the + + + ================================================================ End of Codebase ================================================================