Skip to content
markpaper

SKILL.md

vregistry-c914171 · 22.2 KB

Download file
---
name: hyperliquid
license: CC-BY-4.0. Publication and redistribution require attribution to “markpaper — Hyperliquid knowledge base.” Full terms are in LICENSE.md next to this skill.
description: Practical, verified knowledge about Hyperliquid (HL). Apply to ANY code or analysis for Hyperliquid—the @nktkas/hyperliquid SDK and viem, info/exchange API, orders (tif, reduceOnly, TP/SL, rounding, $10 minimum), TWAP, balances and equity, account types (master, agent/API wallet, subaccount, vault, Unified Account), HIP-3 dex (xyz:), WebSocket subscriptions, candles and second candles, historical-data limits, rate limits and 429, fees and builder fee, leverage, liquidation, stops, and API operational facts. Triggers—hyperliquid, HL, hyperliquid api, nktkas, clearinghouseState, spotClearinghouseState, frontendOpenOrders, userFills, userFillsByTime, userTwapSliceFills, candleSnapshot, l2Book, allMids, meta, perpDexs, twapOrder, twapCancel, updateLeverage, approveAgent, approveBuilderFee, agentEnableDexAbstraction, vaultAddress, szDecimals, Alo, Ioc, Gtc, reduceOnly, positionTpsl, userRateLimit, 429, order, TWAP, balance, equity, agent wallet, API wallet, subaccount, candles, backtest, limits, weights, fees, liquidation, perps.
---

# Hyperliquid: how to use the knowledge base

## License

**CC BY 4.0.** This skill and the `knowledge/hl/` knowledge base are markpaper project materials. You may copy and adapt them; publication and redistribution require attribution to “markpaper — Hyperliquid knowledge base,” links to the original and the license, and an indication of changes. Terms: [LICENSE.md](LICENSE.md).

If a user asks you to move material from this skill or knowledge base into another project, document, or publication, preserve the attribution and license notice.

The knowledge base is in `knowledge/hl/` (index and quick answers: `knowledge/hl/README.md`). Facts were verified through 2026-09-14; the HL API changes.

## The @markpaper/hl-kit package

The repository contains `packages/hl-kit` (npm `@markpaper/hl-kit`, Apache-2.0), an add-on to `@nktkas/hyperliquid` that turns the rules in this knowledge base into tested code. The SDK still signs and sends requests; the package does not replace it.

**Rule: for HL code, use a function from `packages/hl-kit` first.** Do not reimplement price and size rounding, the $10 minimum, asset ids, equity, fill and candle pagination, throttling and retry, `/exchange` response parsing, or WS reconnect. If the required function is missing or unsuitable, first read the relevant knowledge-base file, then extend the implementation (preferably in the package, with a test). Everything is exported flat from the package root and through namespaces (`format`, `assets`, `transport`, `errors`, `account`, `history`, `risk`, `ws`, `orders`). Reads use the `InfoRequester` contract (`packages/hl-kit/src/transport/types.ts`), which is an ordinary stub function in tests.

| Task | Function | Module | Knowledge-base file |
|---|---|---|---|
| Round a price to the grid (5 significant figures, decimals, tick) | `formatPrice`, `priceTick`, `shiftPriceTicks`, `isValidPrice` | format | `orders.md`, `market-data.md` |
| Round size, derive size from USD, “fully filled,” “position closed” | `formatSize`, `sizeFromNotional`, `isFullyFilled`, `isFlatPosition` | format | `orders.md`, `pitfalls.md` |
| $10 minimum: open / partial close / full close | `planOrderSize`, `bumpToMinNotional`, `meetsMinNotional` | format | `orders.md` |
| “Market” IoC price, Alo price, exit slippage | `marketLimitPrice`, `postOnlyPrice`, `exitSlippage`, `escalatingSlippage` | format | `orders.md` |
| Validate an order before sending | `validateOrder`, `triggerPxMatches` | format | `orders.md` |
| Perp, spot, and HIP-3 asset ids; cached meta | `createAssetRegistry` (`resolve`, `lookup`), `perpAssetId`, `spotAssetId`, `hip3AssetId` | assets | `market-data.md` |
| `xyz:` coin names, spot `@N` | `parseCoin`, `qualifyCoin`, `stripDexPrefix`, `isSpotCoin` | assets | `market-data.md`, `pitfalls.md` |
| `updateLeverage` parameters | `leverageParams` / `leverageUpdateParams` | assets / risk | `risk-and-margin.md` |
| Trading hours for `xyz` equities | `getMarketSession`, `isMarketOpen` | assets | `market-data.md` |
| `/info` with weights, limiter, and retries | `createInfoClient`, `getSharedWeightLimiter`, `weightOf` | transport | `rate-limits.md` |
| Exchange call through the shared budget, without duplicate sending | `limitExchange`, `withRetry`, `isTransientError`, `isRateLimitError` | transport | `rate-limits.md`, `orders.md` |
| Address request limit | `getAddressBudget`, `computeAddressBudget` | transport | `rate-limits.md` |
| TTL, single-flight, SWR cache | `createCachedLoader` | transport | `rate-limits.md`, `pitfalls.md` |
| Parse `/exchange` response, partial batch, retry advice | `parseOrderResponse`, `extractPartialBatch`, `interpretExchangeError`, `recommendRetry`, `classifyExchangeError` | errors | `orders.md`, `sdk-and-api.md` |
| Balance and equity across every dex and spot | `fetchAccountSnapshot`, `computeEquity` | account | `balance-and-equity.md` |
| Positions, ROE, “empty” HIP-3 rows | `normalizePositions`, `aggregateRoe`, `listZeroSizePositions` | account | `balance-and-equity.md`, `risk-and-margin.md` |
| Phantom equity reads | `isEquityImplausible`, `createEquitySmoother`, `createLastGoodCache`, `ledgerExplainsDrop` | account | `balance-and-equity.md`, `pitfalls.md` |
| Agent wallet, `userRole`, trading account | `checkAgentApproval`, `fetchUserRole`, `resolveTradingAccount`, `assertAgentActive` | account / orders | `accounts.md` |
| Paginated and deduplicated fills | `fetchFillsByTime`, `fetchRecentFills`, `dedupFills` | history | `fills-and-history.md` |
| TWAP slices | `fetchTwapSliceFills`, `mergeFillsWithTwap` | history | `twap.md` |
| Group partials, round trips, PnL | `groupFillsByOrder`, `reconstructRoundTrips`, `realizedPnlSince`, `summarizePnl` | history | `fills-and-history.md` |
| Candles (~5000-bar limit, closure status, gaps) | `fetchCandles`, `fetchServerTimeMs`, `findCandleGaps`, `sliceCandlesByTime` | history | `market-data.md`, `backtest-and-data.md` |
| Time-window cache keys | `candleCacheKey`, `fillsCacheKey`, `utcDayWindow` | history | `backtest-and-data.md` |
| Fees: tiers, `userFees`, calculation from fills | `feeRatesForVolume`, `readUserFees` (flat export: `parseUserFeeRates`), `summarizeFills`, `roundTripFeeUsd` | risk | `fees.md` |
| Builder fee: units and when to omit it | `resolveBuilderFee`, `builderFeeToBps`, `parseMaxFeeRate` | risk | `fees.md` |
| Liquidation and stop buffer | `liquidationRoe`, `stopLiquidationBuffer` | risk | `risk-and-margin.md` |
| ROE stops and TP/SL prices | `roeToTriggerPrice`, `tpslLegPrices` | risk | `risk-and-margin.md` |
| WS client: ack, watchdog, reconnect, IP limits | `createWsClient`, `createWsIpBudget`, `reconnectDelay` | ws | `websocket.md` |
| WS self-heal and userFills deduplication | `createStalenessTracker`, `createTidDeduper`, `splitUserFills` | ws | `websocket.md` |
| Intent to wire order, fail-closed batch outcomes | `buildOrder`, `placeOrders`, `mayBeLive` | orders | `orders.md` |
| Reconcile by cloid, close, cancel | `reconcileByCloid`, `closePosition`, `marketClose`, `cancelOrders`, `createCloid` | orders | `orders.md`, `pitfalls.md` |

**@experimental.** Functions and fields tagged `@experimental` in JSDoc are implemented from HL documentation, SDK types, or a single observation and have not been confirmed live (the knowledge base marks these “according to the documentation, not verified”). They include TWAP (`placeTwap`, `cancelTwap`, `fetchTwapSliceFillsByTime`), cancellation by cloid and `scheduleCancel`, `reconcileByCloid`, `hip3AssetId` for dexes other than `xyz`, the spot registry and spot price decimals, some `classifyExchangeError` kinds, response-size weight surcharge, funding pagination and `fundingPayment`, discount stacking and `parseFeeScheduleTiers`, `estimateLiquidationPrice`, `fetchSubAccounts` and `fetchUserAbstraction`, and WS limits on new connections and messages. When using them, tell the user and propose a testnet check or smoke test.

Package checks (from `packages/hl-kit`): `npm run typecheck`, `npm test` (no network), `npm run build`, `npm run smoke` (read-only live smoke test against public mainnet data, without addresses or keys). Renames for export-name conflicts are documented in `packages/hl-kit/README.md`.

## A. Rule: file first, then code

**Before writing or changing HL code, open the relevant topic file under `knowledge/hl`** and read at least its TL;DR, the required section, “Pitfalls,” and “Open questions.” If the task spans several topics (for example, order + balance + limits), open all of them. Do not write HL logic from memory: every rule below has a verified reason, and memories of response shapes become stale.

If a fact is marked “according to the documentation, not verified” or appears under “Open questions,” tell the user and propose a check (smoke test, live query) instead of presenting it as confirmed.

| Task topic | File |
|---|---|
| Endpoints, SDK, viem, SDK errors, signing, egress IP | `knowledge/hl/sdk-and-api.md` |
| Master, agent/API wallet, subaccount, vault, `userRole`, Unified Account, portfolio margin | `knowledge/hl/accounts.md` |
| Balance, equity, spot, margin ratio, sizing, phantom reads | `knowledge/hl/balance-and-equity.md` |
| Placement, tif, reduceOnly, TP/SL, rounding, minimum, cancellation, retries, open orders | `knowledge/hl/orders.md` |
| TWAP: placement, cancellation, slices, and verification | `knowledge/hl/twap.md` |
| `meta`, precision, HIP-3 dex and asset id, `allMids`, `l2Book`, candles, spot, equity hours | `knowledge/hl/market-data.md` |
| Fill history, pagination, PnL, `historicalOrders`, ledger, leaderboard | `knowledge/hl/fills-and-history.md` |
| WebSocket: subscriptions, limits, ping, reconnect | `knowledge/hl/websocket.md` |
| Weights, IP and address limits, 429, limiter, retry, scans | `knowledge/hl/rate-limits.md` |
| Fees, `userFees`, builder fee | `knowledge/hl/fees.md` |
| Leverage, ROE, liquidation, utilization, stops, reliable closing, preflight | `knowledge/hl/risk-and-margin.md` |
| Historical data, funding, cache, why HL backtesting is difficult and where it lies, recording second candles | `knowledge/hl/backtest-and-data.md` |
| API latency, per-IP limits, safe client shutdown, telemetry | `knowledge/hl/ops-and-deploy.md` |
| Code or analysis for Nado / Lighter (robinhoodchain) | use the dedicated `nado` or `lighter` skill instead |
| Number precision, races, caches, “not read” ≠ “empty,” closing | `knowledge/hl/pitfalls.md` |

## B. Hard facts and invariants (always remember)

**API and SDK**
1. Two endpoints: `POST https://api.hyperliquid.xyz/info` (reads) and `POST https://api.hyperliquid.xyz/exchange` (signed actions); WS `wss://api.hyperliquid.xyz/ws`. Response numbers are strings. Use an 8–10 s timeout on every info fetch. → `sdk-and-api.md` TL;DR, §11
2. SDK 0.27.1 throws `ApiRequestError` when **even one** batch item is an error. Adjacent orders may still have entered the book; their oids are in `err.response.response.data.statuses`. The class is not exported: check `err.name === 'ApiRequestError'`. → `sdk-and-api.md` §7, `orders.md` §7.2
3. The SDK runs one wallet's exchange requests strictly in sequence. Parallel `order()` calls do not speed placement; batching does. → `sdk-and-api.md` §6.3

**Accounts**
4. The agent (API wallet) signs; the traded and read account is the `user` in info—the master or subaccount address. The agent address has no positions. An agent cannot withdraw funds or sign `approveBuilderFee`. → `accounts.md` TL;DR
5. `User or API Wallet 0x… does not exist` means the agent is unauthorized, expired, or revoked. It is not transient: classify it as “dead key” and do not retry. `extraAgents` omits unnamed agents; the definitive check is a signed `updateLeverage`. → `accounts.md` §2.3–2.4
6. A subaccount is traded by the master account's agent using `vaultAddress` (`defaultVaultAddress` in the SDK). A subaccount has separate funds and its own address limit. `reserveRequestWeight` does not credit a subaccount. → `accounts.md` §3
7. Collateral mode is reliably visible only in WS `webData3` → `userState.abstraction`. REST `userDexAbstraction` returns `false` for Unified Account. On Unified Account, `usdSend`/`usdClassTransfer` are disabled; use `sendAsset` instead. → `accounts.md` §5
8. One bot per account or subaccount. Revoking an agent does not close positions. → `accounts.md` §9

**HIP-3 dex and market data**
9. `clearinghouseState`, `frontendOpenOrders`, `openOrders`, `meta`, and `allMids` operate **one dex at a time**: without `dex`, only the main dex is returned. Request HIP-3 (`xyz` and others) separately; obtain the dex list from `perpDexs`. Exception: `userFills`/`userFillsByTime` ignore `dex`; a second request with `dex` duplicates records. → `market-data.md` TL;DR, `fills-and-history.md` §2
10. Asset ids: main—index in `meta.universe`; spot—`10000 + index`; HIP-3—`100000 + perpDexIndex × 10000 + index`. Derive `perpDexIndex` by name from `perpDexs`; do not hardcode it. Normalize HIP-3 names idempotently (`xyz:TSLA`, not `xyz:xyz:TSLA`). → `market-data.md` §4–5
11. Before the first HIP-3 order, the agent calls `agentEnableDexAbstraction()`. Response `Abstraction transition not allowed` is success (the account is already unified). → `accounts.md` §5.3

**Balance**
12. Capital = Σ `marginSummary.accountValue` across **all** dexes + Σ for stablecoins `max(0, total − reserve)`, where `reserve = spotHold` when present, otherwise `hold`. Do not add all spot to perp (that doubles it on Unified Account). On portfolio margin, `hold` is negative. Do not use `crossMarginSummary` for equity. → `balance-and-equity.md` §3
13. One equity read is not a fact. HTTP 200 without `marginSummary` is degradation, not $0. Read perp and spot in one `Promise.all` and smooth the sum with a window median. Non-finite `szi` is a corrupt read, not flat. → `balance-and-equity.md` §6, §9.2
14. Perp `accountValue` is reflexive: on Unified Account it is occupied margin and changes because of your own orders and spot↔perp transfers without trading. It is unsuitable as a capital or drawdown proxy. → `balance-and-equity.md` §5, `risk-and-margin.md` §6.4

**Orders**
15. Perp price: at most 5 significant figures **and** at most `6 − szDecimals` decimal places. Size: floor to `10^-szDecimals` with epsilon. Pass `p` and `s` as strings. Planner and executor quantize through **one** function. → `orders.md` §5
16. The $10 notional minimum uses the order price **after** rounding. OPEN—never raise to a lot or minimum; partial reduceOnly—floor only, otherwise skip; full reduceOnly close—ceil and raise to the minimum (the exchange clamps to the position). → `orders.md` §5.4, §6
17. There is no market order: it is an IoC limit order at mid ± slippage (exactly at mid will not fill). An `Alo` crossing the book is rejected (`badAloPxRejected`). Use wider slippage for exits than entries. → `orders.md` §2
18. HL clamps reduceOnly to the live position and does not reverse it, but this does **not** prevent an excessive partial close. → `orders.md` §3.1
19. Retries: 429 means not executed; retry is safe. For 5xx, timeout, or disconnect, outcome is unknown: do not retry OPEN/INCREASE, partial reduceOnly, or transfers before reconciling the book. Cancel, `updateLeverage`, `agentEnableDexAbstraction`, and full reduceOnly close are idempotent. → `orders.md` §10
20. Cancellation is confirmed only by status `'success'`. `never placed / already canceled / filled` means “the order is absent.” Everything else is unconfirmed: do not place a replacement before reconciliation. Complete all cancellations before placements. → `orders.md` §9
21. Native TP/SL: `grouping: 'positionTpsl'`, `s: '0'`, `r: true`, triggered on mark. The response contains strings without oid; find the oid in `frontendOpenOrders` (with `dex` for HIP-3). → `orders.md` §4, `risk-and-margin.md` §9

**Limits and WS**
22. IP limit ≈1200 weight/min across `/info` + `/exchange`, with a window materially shorter than a minute. Every subsystem on an IP must share one limiter; configure its sustained rate, burst, and concurrency explicitly for the actual workload. Weights: 2—`l2Book`, `allMids`, `clearinghouseState`, `spotClearinghouseState`; 60—`userRole`; 20—almost everything else. → `rate-limits.md` TL;DR, §2–4
23. Address limit: 10,000 + 1 request per $1 of volume. A batch of N orders = N. Once exhausted: 1 request per 10 s. Read it through `userRateLimit`. → `rate-limits.md` §9
24. WS: `{"method":"ping"}` every 30 s (the server closes after 60 s of silence); `terminate()` after 65 s without frames. User tracking is documented as 10 addresses per IP (measured ~20) across all connections. Rejection arrives in `channel:"error"` and must be logged. Ghost slots persist ~60 s. The SDK WS transport dies after 3 reconnects. The first `userFills` frame (`isSnapshot:true`) is history, not new events. WS provides speed; REST is authoritative: spot balances are absent from WS, and positions and orders need periodic REST reconciliation. → `websocket.md` TL;DR, §2, §4

**History, candles, fees, risk**
25. `userFills` returns up to the latest 2000. `userFillsByTime` returns up to the 2000 **oldest** from inclusive `startTime`: paginate forward with `startTime = max(time)` for the page, **without `+ 1`**, and deduplicate by `tid`; for a page entirely within one ms, continue at `ms + 1` and mark it. `+ 1` loses the tail of a millisecond where the page ended (58–99% of fills share a millisecond on HLP child vaults, measured on 2026-09-23; the former `max(time) + 1` guidance was wrong). Group partials by `oid`. TWAP slices exist only in `userTwapSliceFills`; calculate PnL and turnover from both feeds. → `fills-and-history.md` TL;DR, `twap.md` TL;DR
26. `candleSnapshot`: no more than ~5000 candles; wide windows return the newest; 5m ≈ 18 days, 15m ≈ 52 days; minimum `1m`, no seconds; the latest bar is unclosed; HIP-3 uses `coin: 'xyz:SP500'` without `dex` (with `dex`, HTTP 500). → `market-data.md` §8
27. Base perp fees: maker 1.5 bps, taker 4.5 bps. `closedPnl` includes neither fee nor funding. Builder fee: `builder: { b: lowercase, f }`, with `f` in tenths of a bp and a perp ceiling of 100; `f` above the approval read from `maxBuilderFee` rejects the entire order; send close retries without `builder`. → `fees.md` TL;DR, §3
28. `updateLeverage`: `leverage = max(1, min(floor(x), maxLeverage))`, `isCross = !onlyIsolated`, set before entry. Liquidation is approximately at `ROE = leverage / (2 × maxLeverage) − 1` (at maximum leverage, −50%). Keep margin-utilization ceilings below the limit with headroom: open orders reserve margin themselves, and at the ceiling new orders are endlessly rejected with `Insufficient margin`; check insufficiency using aggregate `withdrawable` across all dexes. → `risk-and-margin.md` §2, §5, §6
29. No gate blocks exit; exit orders are always reduceOnly. “Not read” ≠ “empty”: an incomplete read of any dex skips the tick rather than meaning “no positions.” Store oid and other identifiers as strings. → `pitfalls.md` TL;DR, §2, §7
30. `twapOrder`/`twapCancel` were not verified live. Before using them in a bot, run a smoke test on a subaccount. → `twap.md` §7.1

## C. Checklist for historical-data analysis

Backtesting on HL is difficult: small intervals retain only the latest ~5000 bars, the public API provides neither second candles nor order-book history, and intrabar price order and the order-book queue are invisible. Therefore a backtest is at best a **relative** comparison of alternatives; absolute return figures are almost always optimistic. Tell the user before drawing conclusions from a run. → `backtest-and-data.md` (opening paragraph, §5, §7)

**Data**
- [ ] Candles: the window is no wider than ~5000 bars per call; during pagination, an empty chunk does not mean `break`; the cache key is quantized (not `Date.now()`); the final unclosed bar is discarded. `t` is open time and `c` belongs to `t + interval`. The run report records the actual data range, not the requested window. → `backtest-and-data.md` §2, §6.3, `market-data.md` §8
- [ ] HIP-3 candles: prefixed `coin`, without `dex`. An empty response means `NO_DATA`, not zeroes. → `market-data.md` §8
- [ ] Fills: paginate `userFillsByTime` forward in time, deduplicate by `tid`, group by `oid`, include TWAP slices from `userTwapSliceFills`, derive the position path from `startPosition`. → `fills-and-history.md` §3–6
- [ ] Hourly bars are insufficient for stops: an intrabar overshoot past the stop is invisible. Minute or second data is required. Second data exists only through your own WS `trades` recording (deduplicate `tid`, mark gaps, do not invent missing seconds). Record mark in parallel for native TP/SL. → `backtest-and-data.md` §7.7, §8

**Execution**
- [ ] Intrabar order is unknown: SL and TP in the same candle → SL. Entry and exit in the same candle do not count. A limit order fills only when price **passes through** the level; a touch ≠ a fill. → `backtest-and-data.md` TL;DR, §7.1–7.2
- [ ] Entry is not at the decision-time price: include latency calibrated from your own live fills. IoC is always taker. → `backtest-and-data.md` §7.5
- [ ] Charge fees on both sides according to actual execution type and account rates. Treat unknown execution costs as assumptions and report how conclusions change when those assumptions vary. → `fees.md` §6.2, `backtest-and-data.md` §7.3, §7.9
- [ ] Funding is separate: once per hour, `payment = −szi × oraclePx × fundingRate`. Total = `Σ closedPnl − Σ fee + Σ funding`. → `backtest-and-data.md` §4

**Honesty**
- [ ] No look-ahead: compare bars by close time; at time `t`, use only events with time `≤ t`; build the equity curve from realized trades at exit time. → `backtest-and-data.md` TL;DR, §7.6
- [ ] The conclusion keeps its sign on a held-out window (out-of-sample); stops and liquidations were also run across a separate extreme window. → `backtest-and-data.md` §7.7
- [ ] Reconcile the simulator with reality: simulated PnL against `Σ closedPnl − Σ fee + Σ funding` from your own real trades over the same period. → `backtest-and-data.md` §7.10
- [ ] Do not synthesize events: no invented closes or history backfill.
All files