hyperliquid
Hyperliquid: perps + spot trading, account state, orders, TWAP, leverage, withdrawals, account abstraction, and websocket data. Single-tool dispatch on action. Pass only the params listed below for each action; everything else is ignored.
CHAIN MODEL — read this before describing deposits/withdrawals to a user: Hyperliquid is its OWN L1 trading venue. It is NOT "Hyperliquid on Arbitrum" and there is no "USDC on Hyperliquid (Arbitrum)" ticker. The bridge is the only link to Arbitrum: - FUND: send USDC on Arbitrum → your Hyperliquid deposit address (the address returned by get_deposit_address). That address lives on Arbitrum; Hyperliquid's L1 validators co-sign and credit the funds to your Hyperliquid account. - EXIT: action='withdraw_to_arbitrum' burns Hyperliquid USDC and the validators co-sign a payout to the SAME EOA on Arbitrum (flat $1 fee, ~5 min). Hyperliquid only accepts USDC, not USDT or USDC.e. Funding-from-INR routing is therefore: onramp INR → USDC on Polygon/BSC (Onramp.money supports those for USDC), bridge USDC → Arbitrum via Relay, then deposit USDC on Arbitrum to the Hyperliquid deposit address. Never tell the user to onramp to "USDC on Hyperliquid" or "USDC on Arbitrum (Hyperliquid)" — those phrases are wrong.
PUBLIC DATA (no auth): get_all_mids — {}: map of asset symbol → mid price. get_market_metas — { kind?: 'all'|'perp'|'spot' (default 'all'), dex? }: 'all' returns asset contexts (funding rate, OI, mark/mid/oracle px, 24h vol, prevDayPx); 'perp' returns the perp universe (asset indices, name, szDecimals, maxLeverage, marginTableId, isDelisted) — call this before placing orders to look up indices and szDecimals; 'spot' returns spot universes/pairs and contexts (midPx, markPx, supplies, day volumes). get_perp_categories — {}: returns [[symbol, category], ...] for every HIP-3 (dex-prefixed) perp. Categories: 'crypto', 'stocks', 'commodities', 'indices', 'fx', 'preipo'. Symbols are dex-prefixed (e.g. 'xyz:NVDA', 'vntl:SPACEX', 'km:GOLD', 'flx:BTC', 'para:OTHERS'). Use for discovery — "list HIP-3 stock perps", "what FX HIP-3 perps exist", "show commodities", "what pre-IPO perps are listed" — without walking the whole metas universe. To place an order on one of these symbols, look it up in get_market_metas kind='perp' to get the asset index and szDecimals. get_l2_book — { coin }: L2 orderbook (bids/asks with px, sz, n). get_token_details — { tokenId }: spot token details (full hex hash from spot universe — e.g. "0xc1fb593aeffbeb02f85e0308e9956a90", not a simple number). get_candle_snapshot — { coin, interval, startTime, endTime? }: OHLCV candles. Intervals: 1m,3m,5m,15m,30m,1h,2h,4h,8h,12h,1d,3d,1w,1M. For HIP-3 spot prefix coin like "xyz:XYZ100".
ACCOUNT (auth). Every user-scoped read below — get_account_summary, get_open_orders, get_historical_orders, get_order_status, get_user_fills, get_user_funding_history, get_twap_fills, get_user_abstraction, get_rate_limit — accepts an OPTIONAL address (0x-prefixed). When set, the call returns that address's data instead of the caller's. Omit it for the caller's own data. Use for watchlists / copy-trading / "show me what wallet X is doing" queries. Note: get_deposit_address is caller-only by design (depositing on someone else's behalf doesn't make sense).
get_deposit_address — {}: returns the user's Hyperliquid deposit address. That address LIVES ON ARBITRUM — to fund Hyperliquid the user sends USDC on Arbitrum (not Polygon, not Ethereum, not USDT) to this address, and Hyperliquid validators credit their L1 account within ~minutes.
get_account_summary — { view?: 'perp'|'all' (default 'perp') }: 'perp' returns the unified snapshot — clearinghouseState across the native dex AND every HIP-3 dex (xyz, flx, vntl, hyna, km, abcd, cash, para, …) with each dex's marginSummary/withdrawable/assetPositions, plus spotClearinghouseState (token balances) and userAbstraction in one response. So one call gets perp positions on every dex + spot — do NOT loop per-dex, and there is no separate 'spot' view (read spotClearinghouseState from the same response). 'all' is different: it returns the time-series portfolio (equity curve over windows), not a current snapshot.
get_batch_portfolio_states — { users: string[] }: portfolio snapshot for ANY list of Hyperliquid addresses — NOT the caller's. Always against ALL_DEXES (native + every HIP-3 dex + spot per address). Use when the user wants to track/compare other traders ("watch wallet X", "compare these 3 addresses", leaderboard, copy-trading dashboard). One round-trip; fans out across dexes server-side. Do NOT loop get_account_summary; do NOT pass the caller's own address (use get_account_summary for that). users must be a non-empty array. Response shape: { successful_states: [[address, { clearinghouseState: { native, xyz, flx, … per dex }, spotClearinghouseState: { balances: [...] }, userAbstraction }], …], failed_wallets: string[] } — successful_states is an array of [address, portfolio] tuples (NOT a dict), so iterate with entry[0] = address, entry[1] = portfolio. Check failed_wallets for any address that couldn't be fetched.
get_rate_limit — {}: current API rate-limit window.
ORDERS (auth): get_open_orders — { verbose? }: open orders (verbose=true adds origSz, triggerPx, triggerCondition, cloid, reduceOnly). get_historical_orders — {}: filled/cancelled/rejected orders with statusMsg, filledSz, avgFillPx. get_order_status — { oid }: status of a specific order.
⚠ REQUEST-SHAPE WARNING (most common MCP failure mode) — the request body uses Hyperliquid's SINGLE-LETTER field names. The human-readable shape (coin / is_buy / sz / limit_px / order_type / reduce_only) only appears in GET responses; the REQUEST side REJECTS those names. Sending them produces a confusing 500 ("Cannot read properties of undefined (reading 'limit')") because the validator reads order.t.limit.tif.
❌ WRONG (crashes): { "orders": [{ "coin": "xyz:NVDA", "is_buy": false, "sz": 0.052, "limit_px": 384.96, "order_type": { "limit": { "tif": "FrontendMarket" } }, "reduce_only": false }], "grouping": "na" }
✅ RIGHT: { "orders": [{ "a": 110012, "b": false, "s": "0.052", "p": "384.96", "t": { "limit": { "tif": "FrontendMarket" } }, "r": false, "szDecimals": 4 }], "grouping": "na" }
ASSET INDEX (a) — compute, do NOT pass a coin symbol:
• Main-DEX perps: a = index_in_meta (BTC=0, ETH=1, …; get from get_market_metas kind='perp')
• HIP-3 perps: a = 100000 + perp_dex_index * 10000 + index_in_meta (xyz:NVDA: perp_dex_index=1, index_in_meta=12 → a=110012)
• Spot pairs: a = 10000 + spotInfo.index (PURR/USDC: spotInfo.index=0 → a=10000; from get_market_metas kind='spot')
• HIP-4 outcomes: a = 100_000_000 + (10 * outcome + side) (outcome=35 / side=Yes(1) → encoding 351 → a=100000351 — EIGHT zeros after the 1, NOT five — easy to miscount)
Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/asset-ids
place_order — { orders: [{ a, b, p, s, r, t, c?, szDecimals }], grouping?: 'na'|'normalTpsl'|'positionTpsl' }: a = numeric asset index (see formula above). b = true(buy)/false(sell). p = price STRING ("65000"). s = size STRING in base-asset units. r = reduceOnly bool. c = optional cloid. szDecimals = size precision from get_market_metas — STRONGLY RECOMMENDED: when provided, the backend lot-rounds price/size to HL's rules before signing (matches the canonical form HL recovers the signer from). When omitted, the backend strips trailing zeros from the raw string; for whole-number sizes that's fine, but fractional sizes that don't already match HL's lot rules will sign at the rounded value, not what you intended. Pass szDecimals whenever you've read it from market metas. t = order type, EXACTLY ONE branch: • { "limit": { "tif": "Gtc" | "Ioc" | "Alo" | "FrontendMarket" } } — Gtc=resting, Ioc=cross-book (buy-now), Alo=post-only, FrontendMarket=stock-perp default for HIP-3 • { "trigger": { "isMarket": bool, "triggerPx": "string-or-number", "tpsl": "tp" | "sl" } } grouping = "na" (default) | "normalTpsl" (group TP/SL with primary) | "positionTpsl" (attach TP/SL to existing position).
On "insufficient" errors the response carries `insufficientCollateral: [{ requiredAsset, dex, context, message }]` — the collateral asset is NOT always the same: native perps = USDC, HIP-4 outcomes = USDC, HIP-3 perps use the dex's `collateralToken` (per dex `meta`, usually USDC, occasionally USDH for a few dexes). Relay requiredAsset + message verbatim — if a HIP-3 dex asks for USDH, swap USDC → USDH on Hyperliquid spot (see "USDH SWAP" below) before retrying. HIP-4 outcome trades just need USDC.cancel_order — { cancels: [{ a, o }] }: a=numeric asset index (same formula), o=order id. modify_order — { oid, order: { a, b, p, s, r, t, c?, szDecimals } }: same shape as place_order order.
USDH SWAP (USDC → USDH) — only for HIP-3 dexes whose collateralToken is USDH (NOT needed for HIP-4 outcomes, which use USDC):
1. get_market_metas kind='spot' → find the universe entry whose tokens reference USDH (commonly name 'USDH/USDC') → note its index AND the pair's szDecimals.
2. Compute a = 10000 + spotInfo.index.
3. place_order with { a, b: true, p: <USDH price ≈ "1.00">, s: <USDC amount as a decimal STRING, rounded to szDecimals>, r: false, t: { "limit": { "tif": "Ioc" } }, szDecimals }. Ioc fills immediately at or better than the limit.
4. Confirm: get_account_summary → look for coin='USDH' in spotClearinghouseState.balances. Same flow for any spot swap (USDC→HYPE, USDC→PURR, …) — only the pair differs.
PRE-ORDER CHECKLIST (run BEFORE every place_order — especially HIP-3 / HIP-4):
1. get_account_summary to read unifiedFreeUSDC = clearinghouseState.withdrawable + spotClearinghouseState.balances.USDC.total and userAbstraction.
2. If userAbstraction ∈ {"default", "disabled"} AND clearinghouseState.withdrawable > 0, call set_user_abstraction { abstraction: "u" } first (one-shot, idempotent — surface as a notice, not a confirmation prompt).
3. HIP-3 perp: check the dex's collateralToken. If USDH and user has no USDH balance → run USDH SWAP first.
4. HIP-4 outcome: settles in USDC — same unifiedFreeUSDC check as native perps. No USDH, no swap.
5. Pull szDecimals from get_market_metas — required in the order payload for signature recovery.
TWAP (auth): get_twap_fills — {}: per-slice executions. place_twap_order — { a, b, s, m, r?, t?, szDecimals? }: a=asset index, b=true/false, s=total size, m=duration minutes, r=reduceOnly, t=randomize-timing boolean (NOT the order-type object), szDecimals=optional size precision from get_market_metas (when passed the backend lot-rounds s). terminate_twap_order — { twapId, asset }.
FILLS / FUNDING (auth): get_user_fills — { startTime?, endTime?, aggregateByTime? }: pass startTime to filter by time range; otherwise returns latest. get_user_funding_history — {}: funding payments (fundingRate, usdc amount, timestamp, position size).
LEVERAGE / MARGIN (auth): update_leverage — { asset, leverage, isCross? }. update_isolated_margin — { asset, amount, isBuy }: positive amount adds, negative removes.
WITHDRAWAL (auth): withdraw_to_arbitrum — { amount }: USDC to caller's own EVM address on Arbitrum (flat $1 fee, ~5 min finality).
ACCOUNT ABSTRACTION (auth): get_user_abstraction — {}: returns 'unifiedAccount' | 'portfolioMargin' | 'disabled' | 'default' | 'dexAbstraction'. set_user_abstraction — { abstraction: 'u' }: switches to unifiedAccount (only supported mode; one-shot per wallet).
WEBSOCKET: set_all_dexs_asset_ctxs_subscription — { subscribe }: toggle live allDexsAssetCtxs stream. get_all_dexs_asset_ctxs — {}: latest cached snapshot. Returns array indexed by asset position from get_market_metas — call that to map indices to names. get_ws_status — {}: { connected: boolean }.
OUTCOMES (HIP-4 — Hyperliquid's prediction-market-style binary outcomes):
Each outcome has two sides (No=0, Yes=1). Outcomes are grouped under questions; a question carries a fallbackOutcome plus a list of namedOutcomes.
Collateral: HIP-4 outcomes settle in USDC — same as native perps. split/merge/negate burn or mint USDC, and buying/selling outcome share tokens via place_order debits/credits USDC. No USDH and no spot swap is needed (this changed — HIP-4 used to require USDH; it no longer does). On "insufficient" errors the response carries insufficientCollateral: { requiredAsset: 'USDC', ... } — relay that to the user.
Asset-ID encoding (used by place_order, modify_order, cancel_order for outcome tokens):
encoding = 10 * outcome + side (side ∈ {0, 1})
spot coin: "#" token name: "+" asset ID: 100_000_000 + encoding
Example: outcome=1, side=1 (Yes) → encoding 11 → spot "#11" → asset ID 100000011.
Actions:
get_outcome_meta — {} (no auth): returns BOTH the raw upstream payload AND a partitioned, metadata-enriched presentation view —
• outcomes / questions — raw arrays from HL (backward-compat).
• binaryMarkets — standalone Yes/No outcomes NOT grouped under any question. Simple "Will X happen?" bets. Each carries { outcome, name, description, sideSpecs, metadata }. Trade via place_order with the outcome's asset ID; resolve via split / merge.
• questionMarkets — questions that group N named outcomes sharing one quote pool, resolving mutually exclusively. Each entry expands { question, name, description, metadata, fallbackOutcome, fallbackOutcomeDetail, namedOutcomes, namedOutcomeDetails (each with its own metadata), settledNamedOutcomes, settledNamedOutcomeDetails } so you can render the question's children without re-joining. Resolve via merge_question / negate_outcome.
• counts — { binary, question, totalOutcomes, totalQuestions }.
⚠ DISPLAY NAMES ARE RECYCLED — every BTC daily market is named "Recurring", every named outcome inside one is "Recurring Named Outcome", the fallback is "Recurring Fallback". DO NOT use `name` alone to render — it tells the user nothing. The real semantics live in `metadata` (parsed from the description's "key:value|key:value" form):
• Binary: `metadata.class === 'priceBinary'` with { underlying, expiry: "YYYYMMDD-HHmm" UTC, targetPrice, period } → render as "Will <underlying> hit $<targetPrice> by <expiry formatted>? (<period>)".
• Question: `metadata.class === 'priceBucket'` with { underlying, expiry, priceThresholds: "T1,T2,…", period }. The named outcomes have `metadata.index = "0"|"1"|…` which maps to bucket ranges defined by the parent's thresholds:
index=0 → <underlying> < T1
index=k → T_k ≤ <underlying> < T_{k+1}
index=N (last) → <underlying> ≥ T_N
fallback (metadata.tag = "other") → settlement source unavailable / other
• Other classes may exist (sports, elections, etc.) — surface metadata.class verbatim and any keys you recognize; fall back to raw `description` if nothing parses.
Always present binaryMarkets and questionMarkets as TWO DISTINCT SECTIONS so a Yes/No bet isn't confused with a one-of-N question. Don't dump the flat outcomes array; route through the partitioned view.
split_outcome — { outcome, outcomeAmount }: burn outcomeAmount quote tokens, mint outcomeAmount Yes + outcomeAmount No shares. outcomeAmount is a decimal string (e.g. "123.0").
merge_outcome — { outcome, outcomeAmount? }: burn outcomeAmount Yes + outcomeAmount No → mint outcomeAmount quote. Omit outcomeAmount (or pass null) for max.
merge_question — { question, outcomeAmount? }: burn outcomeAmount Yes shares of every named outcome of the question → mint outcomeAmount quote. Omit/null for max.
negate_outcome — { question, outcome, outcomeAmount }: burn outcomeAmount No shares of one outcome → mint outcomeAmount Yes shares of every OTHER outcome of the same question.Tool call template
Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {
"action": "<action>"
},
"name": "hyperliquid"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| a | No | ||
| b | No | ||
| m | No | ||
| r | No | ||
| s | No | ||
| t | No | TWAP randomize-timing flag (NOT the order-type object — that lives inside orders[].t) | |
| dex | No | ||
| oid | No | ||
| coin | No | ||
| kind | No | ||
| view | No | ||
| asset | No | ||
| isBuy | No | ||
| order | No | Single order object for modify_order | |
| users | No | Hyperliquid addresses for get_batch_portfolio_states | |
| action | Yes | Which Hyperliquid action to perform — see tool description | |
| amount | No | ||
| orders | No | Array of orders for place_order — see description for per-order shape | |
| twapId | No | ||
| address | No | Optional 0x-prefixed Hyperliquid address. When set, every user-scoped read (account, orders, fills, funding, abstraction, twap-fills, …) targets THAT address instead of the caller. Omit for the caller's own data. | |
| cancels | No | Array of {a, o} for cancel_order | |
| endTime | No | ||
| isCross | No | ||
| outcome | No | Outcome id (HIP-4) | |
| tokenId | No | ||
| verbose | No | ||
| grouping | No | ||
| interval | No | ||
| leverage | No | ||
| question | No | Question id (HIP-4) | |
| startTime | No | ||
| subscribe | No | ||
| szDecimals | No | Optional TWAP size precision (from get_market_metas) — when present the backend lot-rounds `s`. | |
| abstraction | No | ||
| outcomeAmount | No | HIP-4 split/merge/negate amount as a decimal string ("123.0"). Pass null for "max" on merge_outcome / merge_question. | |
| aggregateByTime | No |