Skip to content
BuildBuild on HyperCore16 pages
Architecture: info, exchange, WebSocketFoundationHyperliquid's API has three surfaces: an unsigned read endpoint, a signed write endpoint, and a WebSocket that streams data and can carry both.Your first requestFoundationReading Hyperliquid needs no key, no account and no risk: three unsigned POST requests return the list of markets, an order book and any address's positions.Asset ids and namingPractitionerOrders name a market by an integer asset id and info requests by a coin string, and perps, spot, HIP-3 and outcomes each derive both by a different rule.Precision: tick size, lot size, significant figuresPractitionerA HyperCore price has at most 5 significant figures and a decimal cap set by szDecimals, a size at most szDecimals decimals, and outcomes use a 0.00001 grid.Signing from first principlesExpertL1 actions sign a keccak hash of their msgpack bytes, user-signed actions sign readable EIP-712 data, and one wrong byte in either recovers a stranger's address.Nonces, expiry and API walletsPractitionerHyperCore keeps each signer's 100 highest nonces and accepts a new one only if unused, above the smallest, and within 2 days before and 1 day after block time.Orders: place, modify, cancel, batch, dead man's switchPractitionerAn order is a signed action carrying a batch of compact orders; modify, cancel, TWAP and the dead man's switch are separate actions, each with its own response.Transfers and withdrawalsPractitionerEvery HyperCore transfer is a signed action: sends and withdrawals need the account's own key, and an API wallet's transfer actions keep value inside the account.Multi-sig actionsExpertA multi-sig action carries enough authorised users' signatures over one nonce and one named leader, in an envelope only the leader signs and submits.WebSocket: subscriptions, post requests, heartbeatsPractitionerOne WebSocket streams book, trade and account updates as blocks execute and carries signed actions, but drops without warning and forgets every subscription.Rate limits and user limitsPractitionerHyperliquid meters each IP by request weight, 1200 per minute, and each address by actions: 10,000 plus one per USDC traded.Priority fees and the activation feeExpertHyperliquid sells latency in two burned auctions, read priority per IP and write priority per order, and charges 1 quote token to activate a new account.Error responsesPractitionerHyperliquid reports errors at four layers, from HTTP status to a string inside a successful response, and several name a cause other than the real one.Debugging signatures and wire shapesExpertA signature over wrong bytes still verifies, so HyperCore recovers a stranger's address and says it does not exist; the fix is almost always the encoding.Optimising latencyExpertMost of an order's round trip is consensus; code wins by streaming reads, cutting serial round trips, using the fast order class and never stalling orders.Data: historical, node and explorerPractitionerThe API keeps only recent history; a full record needs pagination, archived node data or your own node, and each trade counted once by its trade id.
BuildBuild on the HyperEVM9 pages
What the HyperEVM is, and is notFoundationThe HyperEVM is Hyperliquid's smart contract environment: it reads HyperCore state and queues HyperCore actions, but never executes them atomically.Blocks, gas and RPCPractitionerThe HyperEVM produces small blocks every second and big blocks every minute, prices gas with EIP-1559 in HYPE, and serves a JSON-RPC with no historical state.Moving assets between HyperCore and the HyperEVMPractitionerLinked tokens cross between HyperCore and the HyperEVM via per-token system addresses, with a decimal offset and a 200k-gas fee when leaving HyperCore.Linking a tokenExpertLinking binds a HyperCore token to one ERC20 through the spot deployer's request and the contract deployer's finalisation; the protocol checks almost nothing else.Reading HyperCore: precompilesExpertRead precompiles are 21 fixed addresses, 0x800 to 0x814, that return HyperCore state as of the EVM block's construction, and only at the latest block.Writing to HyperCore: CoreWriterExpertCoreWriter turns a contract call into a HyperCore action for the caller's own account, run after the EVM block, with no result returned and no revert on refusal.Execution order and timingExpertEach L1 block runs the EVM block, then EVM to HyperCore transfers, then CoreWriter actions; on mainnet most actions land within half a second, vault transfers in about 4 s.PatternsExpertA contract that touches HyperCore stays solvent by recording an intent, sending the action, and settling only on HyperCore state that visibly moved.Footguns, measuredExpertTwelve ways a HyperEVM contract loses money or silently does nothing when it touches HyperCore, each with the on-chain evidence and the fix.

Debugging signatures and wire shapes

Expert

A signature over wrong bytes still verifies, so HyperCore recovers a stranger's address and says it does not exist; the fix is almost always the encoding.

An Ethereum-style signature doesn’t carry the signer’s address. The verifier recovers it from the signature and the message hash. Sign the wrong bytes and recovery still succeeds, deterministically returning a stranger’s address, which HyperCore looks up and rejects:

L1 error: User or API Wallet 0x0123... does not exist.
Must deposit before performing actions. User: 0x123...

The docs list these two for an incorrect signature. They read like account problems but are usually serialisation bugs, and neither says where the bytes differ.

Say you sign an order whose decimal strings are "0.5000" and "10.00". The correct hash, the third vector below, comes only from "0.5" and "10". Hash the zeros and the signature recovers a stranger’s address.

In effect, the server parses the body, re-encodes and hashes it, recovers a signer, looks it up, then validates and executes. The first stage that fails decides the response:

Response Got as far as Means
HTTP 422, plain text Failed to deserialize ... Parsing The JSON matches no action shape: wrong type name, missing or extra field, wrong field type. The signature was never looked at
Unable to recover signer Parsed The shape is valid; the signature could not be turned into an address at all
User or API Wallet 0x... does not exist Parsed and recovered The recovered address is unknown. Either your bytes differ from the server’s, or the API wallet is not approved, or not approved on this network
Must deposit before performing actions Parsed and recovered The recovered address is not a funded account. Same causes, or a genuinely new, unfunded account
Invalid multi-sig outer signer Multi-sig envelope Zero-padded r or s in the inner signatures, or a checksummed address; see multi-sig
Price must be divisible by tick size. Parsed and validated A real tick violation, or a trailing-zero mismatch between the body and the signed bytes
  • Account or encoding? If the error names your signer’s address, the account really is unknown or unfunded, even with perfect encoding. If not, and it changes when the input changes (the docs’ diagnosis), the encoding is wrong.
  • Which encoding? If L1 actions from a key work but a user-signed action from the same key recovers a stranger, the EIP-712 type table is wrong, not the msgpack.
  • Load, not encoding. too many pending post requests on a WebSocket post is about load (WebSocket).
Check Right What wrong looks like
Scheme L1 path for L1 actions, typed data for user-signed ones Mixing them is the first common error the documentation lists
Key order The server’s order: type, orders, grouping, builder; a, b, p, s, r, t, c An object built from an unordered or sorted map
Trailing zeros "0.65", "10", "123" "0.650", "10.0", or the docs’ own userOutcome example "123.0"
Letter case Every address lowercase: builder b, destination, vault, multiSigUser, outerSigner A checksummed address; destination is typed string, so its case is hashed
Integer width Smallest msgpack width; 2^32 and above as uint64 An oid of 2^32 or more encoded as a float64
Omit or null f and a omitted when false, cloid omitted when unset, null merge amounts f: false or a: false in the hash, which the exchange rejects
Network source "a" with hyperliquidChain "Mainnet" on the mainnet host, "b" and "Testnet" on testnet A valid signature for the other network
Chain id 1337 for every L1 action; for user-signed ones, the domain chain id equal to signatureChainId An L1 action signed under 42161
Nonce One value in the body, in the hash, and in the action’s time or nonce field time differing from the outer nonce
Optional fields vaultAddress and expiresAfter in the hash exactly when they are in the body Hashing one and not sending it, or the reverse
Markers 0x01 before a vault address, 0x00 before expiresAfter, 0x00 alone for no vault 0x01 before expiresAfter, or a 0x00 expiry marker with no expiry
Type table Exact field names, types, order and primary type: HyperliquidTransaction:Withdraw for withdraw3 A field swapped, or Withdraw3
Tuple lists Sorted before signing in HIP-3 and HIP-4 deployer actions; keywordToValue as sorted pairs Sorted after signing, or sent as an object
Signature {r, s, v} with v 27 or 28; minimal-hex r and s inside multi-sig payloads A 65-byte hex string, or zero-padded inner values
API wallet Approved on this network and not expired An approval made on testnet, or one past its valid_until

Local recovery is not on the list: it only shows your code agrees with itself, while the server rebuilds the payload from the action it parsed, which is where the divergence lives.

Because parsing comes first, a dummy signature safely tests whether the exchange accepts a shape:

Terminal window
SIG='{"r":"0x1111111111111111111111111111111111111111111111111111111111111111","s":"0x2222222222222222222222222222222222222222222222222222222222222222","v":27}'
curl -sS -X POST https://api.hyperliquid-testnet.xyz/exchange \
-H 'Content-Type: application/json' \
-d "{\"action\":{\"type\":\"noop\"},\"nonce\":1,\"signature\":$SIG}"

Replace the action with the shape under test:

  • HTTP 422 Failed to deserialize: the shape is wrong, or no longer exists.
  • Unable to recover signer, does not exist, or Must deposit before performing actions: the shape parsed.

Nothing can take effect: a made-up signature recovers no account that holds anything. Probe both hosts, since mainnet and testnet have parsed different shapes for the same action. This is also how undocumented actions are found and documented ones confirmed dead.

Failed to deserialize is a catch-all that names no field: remove optional fields one at a time.

Pin signing code with known-good vectors in unit tests, so an encoder change that alters a byte fails a test instead of an order. Capture each new action’s vector from a reference implementation before shipping; when one fails, log the msgpack bytes as hex and the connectionId and compare byte by byte, as the docs advise.

Four public vectors, reproduced with the code on signing; the L1 vectors use nonce 1700000000000, no vault and no expiresAfter:

Order
action {"type":"order","orders":[{"a":100017580,"b":true,"p":"0.55","s":"10","r":false,"t":{"limit":{"tif":"Gtc"}}}],"grouping":"na"}
connectionId 0x4b5e3a6100e09595c2d6832bc12e92580c2fc43f9979f3496babd393acbef3c5
Cancel
action {"type":"cancel","cancels":[{"a":100017580,"o":12345}]}
connectionId 0x44fbf1da741dc60547399e5c8bb94a3fc7b3a46c600419590ee152efba9164a9
Order written with trailing zeros ("0.5000", "10.00"), stripped before hashing
action {"type":"order","orders":[{"a":100000090,"b":true,"p":"0.5","s":"10","r":false,"t":{"limit":{"tif":"Gtc"}}}],"grouping":"na"}
connectionId 0x7870e83874912e8953a1bc1ab37dff9bec4deeb971d49c46736937899130fb88
usdSend (user-signed), signatureChainId 0xa4b1
message {"hyperliquidChain":"Mainnet","destination":"0x0d1d9635d0640821d15e323ac8adadfa9c111414","amount":"1","time":1690393044548}
signature 0xeca6267bcaadc4c0ae1aed73f5a2c45fcdbb7271f2e9356992404e5d4bad75a3572e08fe93f17755abadb7f84be7d1e9c4ce48bb5633e339bc430c672d5a20ed1b
recovers 0xcd49bbac6e85fdeb167eb7ca41a945d2b8758f6f
  • The third matches only once "0.5000" and "10.00" are stripped to "0.5" and "10".
  • The fourth reproduces only with destination lowercase; the checksummed address signs differently.
  • A user-signed vector needs no private key: recover the address from the typed data and signature, and compare.

Passing the vectors is not enough to submit. Their nonces are far outside the live window, so an action signed with them is refused even when every byte is right; use a live nonce (nonces).

For the byte layout this page debugs, see Signing from first principles. For every error by cause, see Error responses.