Hyperliquid
Hyperliquid is a perpetual futures DEX running on its own L1 blockchain (HyperBFT consensus). It provides fully on-chain order book matching with sub-second finality, up to 50x leverage on perpetual contracts, and a spot DEX. The API uses EIP-712 wallet signing for authentication — there are no API keys. All trading actions are cryptographically signed by your wallet or an approved agent wallet.
Base URLs:
- Mainnet:
https://api.hyperliquid.xyz - Testnet:
https://api.hyperliquid-testnet.xyz - WebSocket Mainnet:
wss://api.hyperliquid.xyz/ws - WebSocket Testnet:
wss://api.hyperliquid-testnet.xyz/ws
What You Probably Got Wrong
LLMs have stale training data. These are the most common mistakes.
- "Hyperliquid uses API keys" → There are no API keys. Every exchange action is an EIP-712 signed message from your wallet. You can approve "agent wallets" for automated trading, but the auth mechanism is always wallet signatures — never bearer tokens or API secrets.
- "Use chain ID 1 or the Arbitrum chain ID for signing" → L1 trading actions (orders, cancels, leverage changes) use chain ID
1337with a "phantom agent" signing scheme. User-signed actions (agent approval, withdrawals) use chain ID421614. Getting this wrong producesUser or API Wallet does not existerrors with no further explanation. - "Prices and sizes are numbers" → All prices and sizes in the Exchange API are strings, not numbers. Sending
"p": 50000instead of"p": "50000"will fail silently or produce incorrect signatures. - "Asset IDs are coin symbols" → The Exchange API uses integer asset indices, not symbols. BTC might be
0, ETH might be1. Query themetaendpoint to get the current universe mapping. Spot assets use10000 + index. - "I can place a $1 order" → Minimum order notional is $10 for perpetuals. Orders below this are rejected with
Order must have minimum value of $10. - "Rate limit is per-endpoint" → Rate limiting is per-address across all endpoints: 1200 requests/minute base. Trading volume increases your cap. Stale
expiresAftercancellations consume 5x the normal weight. - "The Python SDK uses async" → The official
hyperliquid-python-sdkis synchronous. It usesrequestsunder the hood. For async, use a community SDK or build your own withaiohttp. - "WebSocket subscriptions auto-reconnect" → They do not. You must implement reconnection logic. Missed data during disconnection is not replayed — use the Info REST API to backfill.
L1 Architecture
Hyperliquid runs its own L1 with HyperBFT consensus (modified HotStuff). Key properties:
- Block time: ~400ms
- Finality: Single-slot (sub-second)
- Throughput: 100,000+ orders/second
- Settlement: Fully on-chain order matching (not off-chain with on-chain settlement)
- Bridge: Native bridge to Arbitrum for USDC deposits/withdrawals (~5 min, $1 fee)
- HyperEVM: EVM-compatible execution layer for smart contracts alongside the native DEX
All positions, orders, and liquidations execute on the L1. The API is a gateway to this L1 — not a centralized matching engine.
Authentication: EIP-712 Signing
Hyperliquid uses two signing schemes:
L1 Actions (Trading)
Used for: orders, cancels, leverage changes, margin transfers.
- Chain ID:
1337 - Signing scheme: Phantom Agent — the action is hashed, then a phantom agent struct is signed
- The wallet never signs the raw order; it signs a derived message
User-Signed Actions (Account)
Used for: agent wallet approval, withdrawals, USD transfers.
- Chain ID:
421614(Arbitrum Sepolia) - Signing scheme: Direct EIP-712 with the action payload
Agent Wallets
You can approve up to 4 agent wallets per account (1 unnamed + 3 named) for automated trading. Agent wallets sign L1 actions on behalf of the master account.
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants
from eth_account import Account
wallet = Account.from_key("0x...")
exchange = Exchange(wallet, constants.MAINNET_API_URL)
# Approve an agent wallet for automated trading
approve_result = exchange.approve_agent(agent_address="0xAGENT_ADDRESS", agent_name="my-bot")
Info API (Public Data — No Auth Required)
All Info requests are POST https://api.hyperliquid.xyz/info with a JSON body containing type.
Market Metadata
import requests
url = "https://api.hyperliquid.xyz/info"
# Universe: list of all perpetual markets with asset indices
meta = requests.post(url, json={"type": "meta"}).json()
for i, asset in enumerate(meta["universe"]):
print(f"Asset {i}: {asset['name']} — maxLeverage: {asset['maxLeverage']}")
Mid Prices
mids = requests.post(url, json={"type": "allMids"}).json()
btc_mid = mids["BTC"]
eth_mid = mids["ETH"]
L2 Order Book
book = requests.post(url, json={
"type": "l2Book",
"coin": "BTC",
"nSigFigs": 5
}).json()
for level in book["levels"][0][:5]: # top 5 bids
print(f"Bid: {level['px']} x {level['sz']}")
for level in book["levels"][1][:5]: # top 5 asks
print(f"Ask: {level['px']} x {level['sz']}")
Candle Data
import time
candles = requests.post(url, json={
"type": "candleSnapshot",
"req": {
"coin": "ETH",
"interval": "1h",
"startTime": int(time.time() * 1000) - 86400000,
"endTime": int(time.time() * 1000)
}
}).json()
# Returns up to 5000 candles: T, o, h, l, c, v, n
User State (Positions + Margin)
state = requests.post(url, json={
"type": "clearinghouseState",
"user": "0xYOUR_ADDRESS"
}).json()
margin_summary = state["marginSummary"]
print(f"Account value: {margin_summary['accountValue']}")
print(f"Total margin used: {margin_summary['totalMarginUsed']}")
for pos in state["assetPositions"]:
p = pos["position"]
print(f"{p['coin']}: size={p['szi']} entry={p['entryPx']} unrealizedPnl={p['unrealizedPnl']}")
Open Orders
orders = requests.post(url, json={
"type": "frontendOpenOrders",
"user": "0xYOUR_ADDRESS"
}).json()
for o in orders:
print(f"{o['coin']} {o['side']} {o['sz']}@{o['limitPx']} oid={o['oid']}")
Funding Rates
predicted = requests.post(url, json={"type": "predictedFundings"}).json()
history = requests.post(url, json={
"type": "fundingHistory",
"coin": "BTC",
"startTime": int(time.time() * 1000) - 86400000
}).json()
User Rate Limit
rate = requests.post(url, json={
"type": "userRateLimit",
"user": "0xYOUR_ADDRESS"
}).json()
# nRequestsUsed, nRequestsCap, nRequestsSurplus, cumVlm
Exchange API (Orders — Requires Signing)
All Exchange requests go to POST https://api.hyperliquid.xyz/exchange with a signed payload.
Python SDK Setup
pip install hyperliquid-python-sdk
from hyperliquid.info import Info
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants
from eth_account import Account
wallet = Account.from_key("0xYOUR_PRIVATE_KEY")
info = Info(constants.MAINNET_API_URL, skip_ws=True)
exchange = Exchange(wallet, constants.MAINNET_API_URL)
Place a Limit Order
result = exchange.order(
name="ETH",
is_buy=True,
sz=0.1,
limit_px=3000.0,
order_type={"limit": {"tif": "Gtc"}},
reduce_only=False
)
print(result)
# {"status": "ok", "response": {"type": "order", "data": {"statuses": [{"resting": {"oid": 123456}}]}}}
Place a Market Order
# market_open uses IOC with slippage tolerance (default 5%)
result = exchange.market_open(
name="BTC",
is_buy=True,
sz=0.01,
slippage=0.03 # 3% slippage tolerance
)
Place a Trigger Order (Stop-Loss / Take-Profit)
# Stop-loss: sell if price drops to 2800
result = exchange.order(
name="ETH",
is_buy=False,
sz=0.1,
limit_px=2790.0, # limit price after trigger
order_type={"trigger": {
"triggerPx": "2800",
"isMarket": True, # execute as market when triggered
"tpsl": "sl" # "sl" for stop-loss, "tp" for take-profit
}},
reduce_only=True
)
Batch Orders
orders = [
{
"name": "BTC",
"is_buy": True,
"sz": 0.01,
"limit_px": 95000.0,
"order_type": {"limit": {"tif": "Gtc"}},
"reduce_only": False
},
{
"name": "ETH",
"is_buy": True,
"sz": 0.1,
"limit_px": 3000.0,
"order_type": {"limit": {"tif": "Gtc"}},
"reduce_only": False
}
]
result = exchange.bulk_orders(orders)
TWAP Orders
# TWAP: execute large order over time to minimize impact
result = exchange.twap_order(
name="BTC",
is_buy=True,
sz=1.0,
reduce_only=False,
minutes=30, # execute over 30 minutes
randomize=True # randomize slice timing
)
# Cancel a running TWAP
exchange.twap_cancel(twap_id=12345)
Cancel Orders
# Cancel by order ID
exchange.cancel(name="ETH", oid=123456)
# Cancel by client order ID
exchange.cancel_by_cloid(name="ETH", cloid="0x00000000000000000000000000000001")
Schedule Cancel (Dead Man's Switch)
Cancels all open orders if no heartbeat received within the timeout. Minimum 5-second delay, maximum 10 triggers per day.
import time
result = exchange.schedule_cancel(time=int(time.time() * 1000) + 30000) # 30s from now
Position Management
Set Leverage
# Cross leverage
exchange.update_leverage(name="BTC", leverage=10, is_cross=True)
# Isolated leverage
exchange.update_leverage(name="ETH", leverage=20, is_cross=False)
Adjust Isolated Margin
# Add margin to isolated position (positive = add, negative = remove)
exchange.update_isolated_margin(name="ETH", amount=100.0)
Close Position
# Market close entire position
exchange.market_close(name="BTC")
# Or close with a limit order
state = info.user_state("0xYOUR_ADDRESS")
for pos in state["assetPositions"]:
p = pos["position"]
if p["coin"] == "ETH":
size = abs(float(p["szi"]))
is_long = float(p["szi"]) > 0
exchange.order(
name="ETH",
is_buy=not is_long,
sz=size,
limit_px=float(p["entryPx"]) * (1.01 if is_long else 0.99),
order_type={"limit": {"tif": "Gtc"}},
reduce_only=True
)
WebSocket Streaming
Connect to wss://api.hyperliquid.xyz/ws and send JSON subscription messages.
Available Channels
| Channel | Params | Description |
|---|---|---|
allMids |
dex (optional) |
All mid prices, real-time |
l2Book |
coin |
L2 orderbook updates |
trades |
coin |
Trade prints |
candle |
coin, interval |
Candle updates |
bbo |
coin |
Best bid/offer |
orderUpdates |
user |
Order status changes |
userEvents |
user |
All user events |
userFills |
user |
Fill notifications |
userFundings |
user |
Funding payments |
clearinghouseState |
user, dex |
Position updates |
openOrders |
user, dex |
Open order changes |
activeAssetCtx |
coin |
Market context (funding, OI) |
twapStates |
user, dex |
TWAP order progress |
Subscribe/Unsubscribe Pattern
{"method": "subscribe", "subscription": {"type": "trades", "coin": "BTC"}}
{"method": "subscribe", "subscription": {"type": "l2Book", "coin": "ETH"}}
{"method": "subscribe", "subscription": {"type": "userFills", "user": "0xYOUR_ADDRESS"}}
{"method": "unsubscribe", "subscription": {"type": "trades", "coin": "BTC"}}
Python WebSocket Example
import json
import websocket
import threading
def on_message(ws, message):
data = json.loads(message)
channel = data.get("channel")
if channel == "trades":
for trade in data["data"]:
print(f"{trade['coin']} {trade['side']} {trade['sz']}@{trade['px']}")
elif channel == "l2Book":
book = data["data"]
best_bid = book["levels"][0][0] if book["levels"][0] else None
best_ask = book["levels"][1][0] if book["levels"][1] else None
if best_bid and best_ask:
print(f"BBO: {best_bid['px']} / {best_ask['px']}")
def on_open(ws):
ws.send(json.dumps({
"method": "subscribe",
"subscription": {"type": "trades", "coin": "BTC"}
}))
ws.send(json.dumps({
"method": "subscribe",
"subscription": {"type": "l2Book", "coin": "BTC"}
}))
def on_error(ws, error):
print(f"WebSocket error: {error}")
def on_close(ws, close_status_code, close_msg):
print("WebSocket closed, reconnecting...")
threading.Timer(5.0, connect).start()
def connect():
ws = websocket.WebSocketApp(
"wss://api.hyperliquid.xyz/ws",
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws.run_forever()
connect()
Rate Limits
| Metric | Limit |
|---|---|
| Base request cap | 1200/minute per address |
| Weight per request | 1 (standard) |
Stale expiresAfter cancel |
5x weight |
| Volume bonus | Higher trading volume increases cap |
| Burst | No documented burst limit — sustained rate |
Check your current usage:
rate = requests.post("https://api.hyperliquid.xyz/info", json={
"type": "userRateLimit",
"user": "0xYOUR_ADDRESS"
}).json()
print(f"Used: {rate['nRequestsUsed']}/{rate['nRequestsCap']}")
You can reserve additional rate limit capacity at 0.0005 USDC per request via the requestWeightReservation exchange action.
Vault Strategies
Vaults are on-chain managed accounts. A vault leader trades with depositors' funds and takes a profit share.
# Deposit into a vault
exchange.vault_transfer(
vault_address="0xVAULT_ADDRESS",
is_deposit=True,
usd=1000.0
)
# Query vault details
vault = requests.post("https://api.hyperliquid.xyz/info", json={
"type": "vaultDetails",
"vaultAddress": "0xVAULT_ADDRESS"
}).json()
print(f"TVL: {vault['summary']['tvl']}")
print(f"APR: {vault['summary']['apr']}")
Vault leaders trade by passing vault_address to exchange methods — the master account signs on behalf of the vault.
Subaccounts
Subaccounts are separate trading accounts under a master wallet. They have their own positions and margin but no private key — the master account signs for them.
# Create a subaccount
exchange.create_sub_account(name="arb-bot")
# Transfer USDC to subaccount
exchange.sub_account_transfer(
sub_account_user="0xSUB_ADDRESS",
is_deposit=True,
usd=5000.0
)
# Query subaccounts
subs = requests.post("https://api.hyperliquid.xyz/info", json={
"type": "subAccounts",
"user": "0xMASTER_ADDRESS"
}).json()
Liquidation Mechanics
- Maintenance margin: Varies by asset tier. Positions are liquidated when account margin drops below maintenance requirement.
- Margin tiers: Larger positions require more margin. A 10x BTC position has lower maintenance margin than a 50x one.
- Liquidation price: Visible via
clearinghouseState— checkliquidationPxon each position. - Insurance fund: Backstops liquidations that cannot be filled at the bankruptcy price.
- ADL (Auto-Deleveraging): Last resort when insurance fund is depleted. Profitable positions are reduced proportionally.
state = info.user_state("0xYOUR_ADDRESS")
for pos in state["assetPositions"]:
p = pos["position"]
if p.get("liquidationPx"):
print(f"{p['coin']}: liquidation at {p['liquidationPx']}")
TypeScript Signing Pattern
For TypeScript integrations, use ethers or viem for EIP-712 signing. The SDK @nktkas/hyperliquid provides a typed client.
import { privateKeyToAccount } from "viem/accounts";
import { type Hex, hashTypedData, signTypedData } from "viem";
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const EXCHANGE_URL = "https://api.hyperliquid.xyz/exchange";
const INFO_URL = "https://api.hyperliquid.xyz/info";
async function queryInfo(body: Record<string, unknown>): Promise<unknown> {
const res = await fetch(INFO_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
return res.json();
}
// Info endpoints require no authentication
const meta = await queryInfo({ type: "meta" }) as { universe: Array<{ name: string }> };
const assetIndex = meta.universe.findIndex((a) => a.name === "ETH");
Key Differences from CEX APIs
| Feature | CEX (Binance/Bybit) | Hyperliquid |
|---|---|---|
| Auth | API key + secret | EIP-712 wallet signature |
| Order ID | Server-assigned | OID + optional client order ID (CLOID) |
| Asset reference | Symbol string | Integer index from meta |
| Price/size format | Number | String |
| Rate limit | Per-endpoint | Per-address, all endpoints |
| Settlement | Database updates | On-chain L1 transactions |
| Withdrawals | Centralized | Bridge to Arbitrum (~5 min) |
Official Resources
- Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/
- Python SDK: https://github.com/hyperliquid-dex/hyperliquid-python-sdk
- TypeScript SDK: https://github.com/nktkas/hyperliquid
- App: https://app.hyperliquid.xyz
- Testnet: https://app.hyperliquid-testnet.xyz
- Stats: https://stats.hyperliquid.xyz