api/SKILL.md
Complete Hyperliquid API reference — /info reads, /exchange signed actions, WebSocket subscriptions. The definitive guide for AI agents building on Hyperliquid.
npx skillsauth add cloudzombie/liquidskills apiInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
POST /info — Reads (no auth, no signing required)
POST /exchange — Writes (signed L1 actions required)
WS /ws — Subscriptions (real-time data)
Mainnet:
https://api.hyperliquid.xyzwss://api.hyperliquid.xyz/wsTestnet:
https://api.hyperliquid-testnet.xyzwss://api.hyperliquid-testnet.xyz/wsAsset IDs are required for all order actions. Get them wrong and orders fail silently or go to the wrong market.
Perps: asset = index in meta.universe array
Spot: asset = 10000 + index in spotMeta.universe array
HIP-3: asset = 100000 + (perp_dex_index * 10000) + index_in_perp_meta
Always fetch meta and spotMeta at startup and cache them. Never hardcode asset IDs.
from hyperliquid.info import Info
from hyperliquid.utils import constants
info = Info(constants.MAINNET_API_URL)
meta = info.meta()
# Build asset ID lookup
ASSET_IDS = {m['name']: i for i, m in enumerate(meta['universe'])}
print(ASSET_IDS['BTC']) # e.g., 0
print(ASSET_IDS['ETH']) # e.g., 1
# All perp markets
info.meta()
# Returns: { universe: [{ name, szDecimals, maxLeverage, onlyIsolated, ... }] }
# All spot assets
info.spot_meta()
# Returns: { universe: [{ name, tokens: [...] }], tokens: [...] }
# All current mid prices
info.all_mids()
# Returns: { "BTC": "95000.0", "ETH": "3500.0", ... }
# Order book (L2 snapshot)
info.l2_snapshot("BTC")
# Returns: { coin: "BTC", levels: [[{px, sz, n}...], [{px, sz, n}...]], time: 1234 }
# levels[0] = bids (descending), levels[1] = asks (ascending)
# OHLCV candles
info.candles_snapshot("BTC", "1h", start_time_ms, end_time_ms)
# Returns: [{ t, o, h, l, c, v }]
# Recent trades
info.recent_trades("BTC")
# Returns: [{ coin, side, px, sz, time, hash }]
# Funding rate history
info.funding_history("BTC", start_time_ms)
# Returns: [{ coin, fundingRate, premium, time }]
# Meta and asset contexts (prices, OI, funding)
info.meta_and_asset_ctxs()
# Returns: [meta, [assetCtx per market]]
# Perp account state
info.user_state(address)
# Returns: { assetPositions, marginSummary, withdrawable, time }
# Spot account state
info.spot_user_state(address)
# Returns: { balances: [{ coin, hold, total, entryNtl }] }
# Open orders
info.open_orders(address)
# Returns: [{ coin, side, limitPx, sz, oid, timestamp, ... }]
# Fill history
info.user_fills(address)
# Returns: [{ coin, px, sz, side, time, startPosition, closedPnl, fee, hash, ... }]
# Fill history since a timestamp
info.user_fills_by_time(address, start_time_ms)
# Order status by order ID
info.order_status(address, order_id)
# Returns: { order: {...}, status: "open"|"filled"|"canceled"|"rejected" }
# Referral state
info.referral(address)
# User funding history
info.user_funding(address, start_time_ms)
# Portfolio (cross-margin and isolated positions)
info.portfolio(address)
All exchange actions require:
/exchange with { action, nonce, signature }Use the official SDK. Signing correctly from scratch is error-prone.
Hyperliquid uses a rolling nonce window, not linear nonces like Ethereum.
# Best practice: use millisecond timestamps
import time
nonce = int(time.time() * 1000) # milliseconds
Requirements:
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants
import eth_account
# Setup
private_key = "0x..."
account = eth_account.Account.from_key(private_key)
exchange = Exchange(account, constants.MAINNET_API_URL)
# Market buy BTC (coin = asset name, not ID — SDK handles ID lookup)
result = exchange.market_open(
coin="BTC",
is_buy=True,
sz=0.001, # size in BTC
slippage=0.01, # 1% max slippage
)
print(result)
# { status: "ok", response: { type: "order", data: { statuses: [...] } } }
# Limit order
result = exchange.order(
coin="BTC",
is_buy=True,
sz=0.001,
limit_px=90000.0, # price
order_type={"limit": {"tif": "Gtc"}}, # Good Till Cancel
)
# TIF options: "Gtc", "Alo" (add liquidity only), "Ioc" (immediate or cancel)
# Close position (market)
result = exchange.market_close(coin="BTC")
# Cancel order
result = exchange.cancel(coin="BTC", oid=order_id)
# Cancel all orders for a coin
result = exchange.cancel_by_cloid(coin="BTC", cloid="your-cloid")
# Limit GTC
order_type = {"limit": {"tif": "Gtc"}}
# Limit ALO (maker only — rejects if would take)
order_type = {"limit": {"tif": "Alo"}}
# Limit IOC (immediate or cancel)
order_type = {"limit": {"tif": "Ioc"}}
# Trigger (stop loss / take profit)
order_type = {
"trigger": {
"isMarket": True,
"triggerPx": "90000.0",
"tpsl": "sl" # "sl" = stop loss, "tp" = take profit
}
}
# Place multiple orders in one request (efficient — one IP hit, multiple actions)
orders = [
{
"coin": "BTC",
"is_buy": True,
"sz": 0.001,
"limit_px": 90000.0,
"order_type": {"limit": {"tif": "Gtc"}},
"reduce_only": False,
},
{
"coin": "ETH",
"is_buy": False,
"sz": 0.1,
"limit_px": 3600.0,
"order_type": {"limit": {"tif": "Gtc"}},
"reduce_only": False,
}
]
result = exchange.bulk_orders(orders)
# Set leverage for a coin
exchange.update_leverage(leverage=10, coin="BTC", is_cross=True)
# Change margin type
exchange.update_isolated_margin(amount=100.0, coin="BTC") # add $100 isolated margin
# Withdraw from perp to spot
exchange.withdraw_from_bridge(amount=100.0, destination=address)
# Transfer between subaccounts
exchange.usd_transfer(amount=100.0, destination=address)
API wallets sign on behalf of a master account. Use for automation to avoid using your main key.
from hyperliquid.exchange import Exchange
# Register an API wallet
# (do this once, from your main account)
result = master_exchange.approve_agent(
agent_address=api_wallet_address,
agent_name="my-bot"
)
# Use API wallet for trading
api_account = eth_account.Account.from_key(api_private_key)
api_exchange = Exchange(
api_account,
constants.MAINNET_API_URL,
vault_address=master_address # trades credited to master
)
# Now trade as the master account
result = api_exchange.market_open("BTC", True, 0.001, 0.01)
import WebSocket from 'ws';
class HyperliquidWS {
constructor(url = 'wss://api.hyperliquid.xyz/ws') {
this.url = url;
this.ws = null;
this.subscriptions = new Map();
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.on('open', () => {
console.log('Connected');
// Re-subscribe after reconnect
for (const [id, sub] of this.subscriptions) {
this.ws.send(JSON.stringify({ method: 'subscribe', subscription: sub }));
}
// Heartbeat
this.heartbeat = setInterval(() => {
this.ws.send(JSON.stringify({ method: 'ping' }));
}, 30000);
});
this.ws.on('close', () => {
clearInterval(this.heartbeat);
setTimeout(() => this.connect(), 1000); // reconnect
});
this.ws.on('message', (raw) => {
const msg = JSON.parse(raw);
this.handleMessage(msg);
});
}
subscribe(subscription, handler) {
const id = JSON.stringify(subscription);
this.subscriptions.set(id, { subscription, handler });
this.ws.send(JSON.stringify({ method: 'subscribe', subscription }));
}
handleMessage(msg) {
if (msg.channel === 'pong') return;
for (const [, { subscription, handler }] of this.subscriptions) {
if (msg.channel === subscription.type) {
handler(msg.data);
}
}
}
}
// Usage
const hl = new HyperliquidWS();
// Order book updates
hl.subscribe({ type: 'l2Book', coin: 'BTC' }, (data) => {
const [bids, asks] = data.levels;
console.log(`BTC: ${bids[0]?.px} / ${asks[0]?.px}`);
});
// All mid prices (every second)
hl.subscribe({ type: 'allMids' }, (data) => {
console.log('BTC mid:', data.mids?.BTC);
});
// User fills
hl.subscribe({ type: 'userFills', user: '0xYourAddress' }, (data) => {
for (const fill of data.fills) {
console.log(`Fill: ${fill.coin} ${fill.side} ${fill.sz} @ ${fill.px}`);
}
});
// User events (order updates, position changes)
hl.subscribe({ type: 'userEvents', user: '0xYourAddress' }, (data) => {
console.log('User event:', data);
});
// Trades feed for a market
hl.subscribe({ type: 'trades', coin: 'BTC' }, (data) => {
for (const trade of data) {
console.log(`Trade: ${trade.side} ${trade.sz} @ ${trade.px}`);
}
});
| Type | Params | Data |
|------|--------|------|
| allMids | none | All mid prices, every second |
| l2Book | coin | Order book updates |
| trades | coin | Recent trades stream |
| candle | coin, interval | OHLCV candle updates |
| userEvents | user | Order updates, fills, liquidations |
| userFills | user | Fill notifications |
| userFundings | user | Funding payment notifications |
| userNonFundingLedgerUpdates | user | Deposits, withdrawals, transfers |
| notification | user | Liquidation warnings |
| webData2 | user | Full user state snapshot |
IP-level:
Address-level:
Design around both limits, not just one.
result = exchange.market_open("BTC", True, 0.001, 0.01)
if result['status'] == 'ok':
statuses = result['response']['data']['statuses']
for status in statuses:
if 'filled' in status:
print(f"Filled at {status['filled']['avgPx']}")
elif 'resting' in status:
print(f"Resting order ID: {status['resting']['oid']}")
elif 'error' in status:
print(f"Order error: {status['error']}")
elif result['status'] == 'err':
print(f"Exchange error: {result['response']}")
| Error | Cause | Fix |
|-------|-------|-----|
| Nonce too low | Reused nonce, clock skew | Use int(time.time() * 1000), ensure monotonic |
| User or API wallet does not exist | Wrong signing key or vault address | Check account registration, API wallet approval |
| Insufficient margin | Not enough collateral | Deposit more or reduce size |
| Invalid sz | Size violates szDecimals | Round to correct decimal places from meta |
| Invalid px | Price violates tick constraints | Use SDK's price normalization |
| Post-only rejected | ALO order would have taken | Market has moved; retry or switch to GTC |
[ ] Fetched meta and built asset ID lookup
[ ] Using millisecond timestamps for nonces
[ ] Nonce allocator is atomic (no concurrent reuse)
[ ] API wallet registered (not using main key for automation)
[ ] Tested full order lifecycle on testnet
[ ] Error handling covers: fill, resting, error statuses
[ ] WebSocket reconnect logic implemented
[ ] Rate limit awareness (batch where possible)
[ ] Kill switch for repeated rejections
development
Why build on Hyperliquid. HyperBFT consensus, native orderbook, speed, AI agent angle, honest tradeoffs. Use when someone asks "should I build on Hyperliquid?", "why not Ethereum?", or when an agent needs to understand what makes Hyperliquid unique.
development
Wallets on Hyperliquid — MetaMask + chain ID 999 setup, HyperCore API wallets, agent wallet patterns, EIP-712 signing for exchange actions. Essential for any agent that needs to interact with Hyperliquid.
tools
Development tools for Hyperliquid — Foundry, Hardhat, viem, wagmi for HyperEVM; Python SDK and TypeScript SDK for HyperCore API. What works, what to use, how to set up.
testing
Smart contract testing for HyperEVM with Foundry/Hardhat — unit tests, fuzz testing, testnet fork testing. What to test, what not to test, and what LLMs get wrong on Hyperliquid.