> About this guide: I'm Lawrence, the writer behind supa.is. Between February and May 2026 I've published 150+ articles on supa.is across crypto and brokerage tooling โ including 30+ Hyperliquid-specific guides (recent examples: Hyperliquid getting started, Cross vs isolated margin on Hyperliquid, Hyperliquid zero-fee assets). The most-repeated reader question across that Hyperliquid archive is exactly how to wire the official Python SDK to a working perp bot without losing keys or getting rate-limited, which is why I'm publishing this standardized guide instead of answering one-off.
> Note: Code patterns below are reconstructed from the official Hyperliquid Python SDK (github.com/hyperliquid-dex/hyperliquid-python-sdk) and the Hyperliquid developer docs (hyperliquid.gitbook.io/hyperliquid-docs) as of 2026-04. SDK method names and request shapes change between releases โ pin your dependency, read the upstream CHANGELOG.md, and verify every snippet against the current SDK before pointing it at real funds.
Why use the Python SDK at all
You can talk to Hyperliquid in three ways: the official Python SDK, raw HTTPS POSTs against the documented /info and /exchange endpoints, or the WebSocket feed. The SDK is a thin wrapper that handles three things you genuinely don't want to write yourself:
1. EIP-712 message signing. Every order, cancel, and transfer on Hyperliquid is a signed action โ not just an authenticated REST call. Getting the typed-data structure, chain ID, and signing primitive wrong silently rejects orders with cryptic "User or API Wallet ... does not exist" errors.
pxDecimals and szDecimals constraint enforced by the matching engine. Submitting 0.1234567 when the symbol allows three decimals returns a generic error. The SDK's meta() payload tells you the precision per symbol โ verify against the current Hyperliquid developer docs (hyperliquid.gitbook.io/hyperliquid-docs) before relying on cached values.
3. Nonce and signature replay protection. The SDK auto-increments the nonce per signing wallet so you don't accidentally reuse one and get an order silently dropped.
If you're building a discretionary alert bot, raw HTTP works. If you're placing thousands of orders a day or running a market-maker, the SDK saves a week of debugging. The rest of this article assumes the SDK route.
Installation and project layout
The SDK is on PyPI as hyperliquid-python-sdk (PyPI page):
pip install hyperliquid-python-sdk
Pin the version in requirements.txt or pyproject.toml rather than tracking latest โ the SDK occasionally renames symbols between minor versions, and a silent upgrade in CI can break a live bot at 3 a.m.
Top-level modules you'll touch most:
| Module | Purpose |
|---|---|
hyperliquid.info | Read-only market data, user state, fills history |
hyperliquid.exchange | Place/cancel orders, transfer USDC, update leverage |
hyperliquid.utils.constants | MAINNET_API_URL, TESTNET_API_URL |
hyperliquid.utils.signing | Lower-level signing helpers (rarely needed directly) |
hyperliquid.websocket_manager | Streaming subscriptions to L2 books, trades, user events |
Info client (for polling), one Exchange client (for sending orders), and one WebsocketManager (for live state).
The API wallet model โ read this before generating any keys
This section is where most first-time bot developers lose money. Hyperliquid uses an API wallet (also called an agent wallet) abstraction โ search for "API wallets" or "agents" in the Hyperliquid docs to find the canonical reference. The mechanism is also encoded directly in the SDK's examples/ folder in the SDK repo.
Mental model:
- Your main wallet holds the USDC, the open positions, and the staked HYPE. Its private key should never touch a server.
- An API wallet is a fresh Ethereum keypair that you authorize *on behalf of* your main wallet through the API portal in the Hyperliquid app. It can place and cancel orders for the main wallet, but it cannot withdraw USDC and it cannot transfer collateral to other addresses.
In code, the pattern looks like this:
import eth_account
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils import constants
main_wallet_address = "0xYourMainWalletAddress"
api_wallet_secret = "0xPrivateKeyOfTheAGENTWalletYouAuthorized"
agent = eth_account.Account.from_key(api_wallet_secret)
info = Info(constants.MAINNET_API_URL, skip_ws=True)
exchange = Exchange(
wallet=agent,
base_url=constants.MAINNET_API_URL,
account_address=main_wallet_address,
)
Three things to verify before you run this:
1. The address derived from api_wallet_secret is not the same as main_wallet_address. If you accidentally put your main key into api_wallet_secret, the SDK will happily sign with it and you've negated the entire safety story.
"User or API Wallet ... does not exist".
3. Your account_address (the main wallet) matches the wallet you authorized the agent for. The signing wallet and the funded wallet are different โ the SDK separates them via account_address.
For a refresher on the underlying account model and how isolated margin behaves under the same main wallet, the margin-mode comparison explains why you might want one agent per sub-strategy.
Reading market data with Info
The Info client wraps the public read endpoints described in the Hyperliquid developer docs. Common calls during a bot's hot path:
meta = info.meta() # all perp metadata: szDecimals, maxLeverage, etc.
all_mids = info.all_mids() # latest mid prices keyed by symbol
user_state = info.user_state(main_wallet_address)
open_orders = info.open_orders(main_wallet_address)
fills = info.user_fills(main_wallet_address)
meta() is the one you care about at startup. Each entry has szDecimals (size precision) and maxLeverage, and you should cache it. Submitting an order whose size doesn't conform to szDecimals is a silent rejection in many SDK versions โ the matching engine returns an error string the SDK passes through, and rookie bots log it as "weird API blip" and retry forever.
user_state returns the canonical view of your account: margin summary, asset positions, withdrawable USDC. Use this โ not your bot's internal accounting โ as the source of truth after every fill.
For historical data, info.candles_snapshot(coin, interval, start_ms, end_ms) returns OHLCV bars. The interval set is fixed (1m, 3m, 5m, 15m, 1h, 4h, 1d, 1w as documented in the SDK source) โ you can't request 7-minute bars; resample yourself.
Placing orders with Exchange
The Exchange client wraps the signed write endpoints. The single most common call:
result = exchange.order(
name="ETH",
is_buy=True,
sz=0.1,
limit_px=2500.0,
order_type={"limit": {"tif": "Gtc"}},
reduce_only=False,
)
Like what you're reading? Try it yourself โ this link supports ChartedTrader at no cost to you.
Open a Hyperliquid account (4% fee discount on first $25M of volume) โA few things that aren't obvious from the signature:
nameis the base asset symbol, not the perp ticker โ the SDK looks up the asset index frommeta()for you.order_typecarries both the order kind (limit,trigger) and time-in-force (Gtc,Ioc,Alo).Alois "add-liquidity-only" โ the order is rejected if it would cross the book and pay taker fees. If your strategy depends on maker rebates, default every limit order toAloand treat any rejection as a signal that your price was too aggressive, not as an error.limit_pxmust respect bothpxDecimals(price tick) and the maximum percentage deviation from the oracle price that Hyperliquid enforces on perps. Trying to place a limit far off market for testing returns an error โ verify the current cap in the Hyperliquid docs before relying on a hardcoded number.
result dict needs to be parsed carefully. A successful submission returns:
{"status": "ok", "response": {"type": "order", "data": {"statuses": [{"resting": {"oid": 12345678}}]}}}
But a partially filled order looks like:
{"status": "ok", "response": {"type": "order", "data": {"statuses": [{"filled": {"oid": ..., "totalSz": "0.05", "avgPx": "2500.1"}}]}}}
And a rejection looks like:
{"status": "ok", "response": {"type": "order", "data": {"statuses": [{"error": "Insufficient margin"}]}}}
Note that the outer status is still "ok" even when the inner status is error. This is the single most common silent-bug source in beginner Hyperliquid bots: code checks result["status"] == "ok" and assumes the order made it. Always inspect result["response"]["data"]["statuses"][i].
Managing positions, leverage, and transfers
Setting leverage before a trade:
exchange.update_leverage(leverage=3, name="ETH", is_cross=True)
Closing a position is just an opposite-side reduce_only=True order. There is no dedicated "close" method โ reduce_only is the contract.
USDC transfers between perp and spot wallets, and between cross/isolated margin sub-accounts, also live on Exchange:
exchange.usd_transfer(amount=100.0, destination=main_wallet_address)
Note that the agent wallet cannot withdraw to a different address โ that operation requires a signature from the main wallet. This is the safety guarantee from the previous section made concrete.
If you plan to scale up volume, the Hyperliquid 4% fee discount math covers how the per-account $25M referral cap interacts with HYPE-staking discounts โ both apply automatically as long as your main wallet was registered through a referral link, no SDK setting required.
WebSocket subscriptions for live state
For anything beyond a slow polling loop, use the WebSocket feed:
from hyperliquid.websocket_manager import WebsocketManager
ws = WebsocketManager(constants.MAINNET_API_URL)
ws.start()
def on_l2(msg):
print(msg["data"]["levels"])
ws.subscribe({"type": "l2Book", "coin": "ETH"}, on_l2)
ws.subscribe({"type": "userEvents", "user": main_wallet_address}, lambda m: print(m))
Useful subscription types:
| Channel | What you get |
|---|---|
l2Book | Order book snapshots and updates per coin |
trades | Public trade prints |
userEvents | Your fills, funding payments, liquidations |
userFills | Just fills (lighter than userEvents) |
candle | OHLC bars, useful for low-latency strategy ticks |
WebsocketManager in a supervisor that reconnects and re-subscribes on close. And reconcile your in-memory state against info.user_state() periodically; missed messages do happen, especially during spikes.
Rate limits, error handling, and idempotency
Search the Hyperliquid developer docs for the current rate-limit numbers and verify before sizing your strategy โ they have changed across releases. Three patterns from production hold across versions:
1. Batch where possible. exchange.bulk_orders([...]) submits multiple orders in one signed action and counts as a single rate-limit unit. A market-maker placing 20 quotes per second should never call order() 20 times.
cloid for idempotency. Pass a client order ID (cloid) on every order() call. If your bot crashes mid-send and you retry, the matching engine will reject the duplicate by cloid rather than placing two orders. Generate a UUID per intent and log it before sending.
3. Don't busy-poll user_state from the hot path. Many beginner bots call info.user_state() after every order to "confirm." This eats your read-budget and lags behind WebSocket userFills anyway. Confirm via WebSocket; treat user_state as a slow reconciliation tool every 30โ60 seconds.
Ten pitfalls that cost beginner bots money
1. Confusing main wallet and agent wallet โ covered above. The single highest-stakes mistake.
2. Not pinning the SDK version. Apip install --upgrade in a deploy script can silently change a method signature.
3. Treating result["status"] == "ok" as success. The inner statuses list is the real signal.
4. Submitting non-tick-aligned prices. Always round limit_px to the symbol's pxDecimals.
5. Using Gtc when you meant Alo. Gtc will cross the book and pay taker fees if your price is aggressive โ silently turning a maker strategy into a taker strategy.
6. Ignoring funding rate accruals. Funding is paid hourly on perps; if you carry positions overnight without modeling it, your post-fee P&L will diverge from your simulated P&L.
7. No cloid on retries. Network-flaky deploys end up with duplicate orders.
8. Hardcoding asset indexes. Indexes shift when new perps list โ always look up by symbol via meta().
9. WebSocket without a watchdog. Disconnections that don't auto-reconnect leave a bot trading on stale state.
10. Testing on mainnet "just to be sure." Testnet (constants.TESTNET_API_URL) is a near-clone of mainnet for SDK behaviour. Use it.
SDK vs raw HTTP: when to drop down
| Concern | SDK | Raw HTTP |
|---|---|---|
| EIP-712 signing | Handled | You implement |
| Symbol โ asset index lookup | Auto via meta() | You cache and refresh |
| Tick rounding helpers | Built in | You write them |
Custom transports (e.g. aiohttp connection pooling) | Limited | Full control |
| Footprint in a Lambda cold-start | Heavier | Minimal |
| Multi-language stack (e.g. Rust signing, Python orchestration) | Forces Python signing path | Cleaner separation |
Production checklist before pointing it at real USDC
- [ ] Agent wallet authorized in the API tab, main wallet key never on the server
- [ ] SDK version pinned,
requirements.txtcommitted,pip-auditclean - [ ]
meta()cached at startup, refreshed on a schedule - [ ] All orders use
cloidfor idempotency - [ ] Outer + inner status both inspected on every order response
- [ ] WebSocket supervisor with auto-reconnect and re-subscribe
- [ ] Periodic reconciliation against
info.user_state() - [ ] Fee assumptions match the live fee schedule in the Hyperliquid docs โ verify the current taker/maker numbers before sizing positions, since the schedule and tier discounts change
- [ ] Funding rate accruals modeled in your P&L calculation
- [ ] Tested end-to-end on testnet first
Where the affiliate fits
If you don't yet have a funded Hyperliquid main wallet, sign up via this Hyperliquid referral link. The referral fee discount is set at registration โ you can't backfill it onto an account that signed up without a referral. For most retail bot operators that's a real, multi-month saving on taker fees, and it stacks with the HYPE-staking fee tier when you eventually qualify. For the exact discount math against the per-account volume cap (as of 2026-04), see the Hyperliquid 4% fee discount cap explainer โ and always verify the current numbers in the Hyperliquid docs before sizing a strategy around them.
Build it on testnet first, keep your main key cold, and let the agent wallet do the talking.
`