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.

Multi-sig actions

Expert

A multi-sig action carries enough authorised users' signatures over one nonce and one named leader, in an envelope only the leader signs and submits.

HyperCore multi-sig is built in, not a smart contract. The concept is on multi-sig accounts; this wire format nests both schemes from signing.

Say an account has 2 authorised users and a threshold of 2. Both sign the same scheduleCancel with the same nonce and leader. The leader wraps both signatures, signs the wrapper and submits.

Every signer must sign exactly the same bytes; common mismatches surface as one error naming the leader, at fault or not.

Each authorised signer signs the inner payload [multiSigUser, outerSigner, action] same nonce, same leader Leader (outerSigner) collects the signatures strips leading zeros from r, s wraps them in multiSig signs SendMultiSig over the wrapper's hash Exchange checks the inner signatures and the envelope; uses only the leader's nonce set
Inner signatures attest to the action. The leader's signature attests to the collected set.

convertToMultiSigUser is user-signed by the account’s own key:

{
"type": "convertToMultiSigUser",
"signatureChainId": "0xa4b1",
"hyperliquidChain": "Mainnet",
"signers": "{\"authorizedUsers\":[\"0x0000000000000000000000000000000000000001\",\"0x0000000000000000000000000000000000000002\"],\"threshold\":2}",
"nonce": 1717000000000
}
  • signers is a JSON string, signed as a string: HyperliquidTransaction:ConvertToMultiSigUser(string hyperliquidChain,string signers,uint64 nonce).
  • Authorised users must already exist on Hyperliquid. After conversion, every action must be a multi-sig action.
  • To change signers or threshold, wrap a new convertToMultiSigUser in a multi-sig action. Empty authorised users converts back to a normal user (one SDK sends the string "null").
  • The HyperEVM side is not converted. The HyperEVM address stays under the original key and CoreWriter does not work for multi-sig users. The docs advise not touching the HyperEVM before or after conversion.

The leader (outerSigner) is the authorised user who submits, never the multi-sig account itself. Take the nonce once and give it, with the leader’s address, to every signer. The leader collects all signatures before submitting.

A mismatched nonce or leader can’t be repaired: collect the whole set again.

3. Each authorised user signs the inner action

Section titled “3. Each authorised user signs the inner action”

The inner signature depends on the wrapped action’s scheme:

Wrapped action What each authorised user signs
L1 action, such as order or outcomeDeploy An ordinary L1 signature over the three-element array [multiSigUser, outerSigner, action], both addresses lowercase, with the shared nonce and no vault address
User-signed action, such as usdSend or approveAgent The action’s own typed data with two fields inserted directly after hyperliquidChain: address payloadMultiSigUser and address outerSigner, values lowercase

For a usdSend, the inner type becomes:

HyperliquidTransaction:UsdSend(string hyperliquidChain,address payloadMultiSigUser,address outerSigner,string destination,string amount,uint64 time)

Position is part of the struct hash: anywhere else signs differently.

Browser wallets can’t sign L1 inner actions, which use the chain-1337 Exchange domain. User-signed inner actions are typed data on a real chain id, so a quorum of browser wallets or custodians can sign account-level actions; L1 actions need raw keys.

{
"type": "multiSig",
"signatureChainId": "0xa4b1",
"signatures": [
{ "r": "0x<r, leading zeros stripped>", "s": "0x<s, leading zeros stripped>", "v": 27 }
],
"payload": {
"multiSigUser": "0x<multi-sig account, lowercase>",
"outerSigner": "0x<leader, lowercase>",
"action": { "type": "scheduleCancel", "time": 1717000010000 }
}
}

Keep the key order shown, inside payload too. Write r and s as minimal hex, with no leading zeros.

multiSigActionHash hashes the wrapper minus type exactly like an L1 action (msgpack of {signatureChainId, signatures, payload}, then nonce, vault marker and any expiry). The leader signs it as user-signed typed data under HyperliquidSignTransaction, with chainId equal to signatureChainId:

HyperliquidTransaction:SendMultiSig(string hyperliquidChain,bytes32 multiSigActionHash,uint64 nonce)

The body is {action: <wrapper>, nonce, signature: <leader's signature>}. Only the leader’s nonce set is checked and updated. A leader that is an authorised user’s API wallet uses that API wallet’s nonce.

Parameter Value
Authorised users per multi-sig account at most 10
Threshold set in signers; the minimum number of authorised signatures
Nonces per action one, shared by every signer and the leader
Nonce set consumed the leader’s only
Inner v 27 or 28
Submissions failing with zero-padded r, s and two inner signers about 1 in 4
Accepted after stripping leading zeros, measured on testnet 24 of 24

A sketch for an L1 inner action, using l1ActionHash, signL1Action and signUserAction from signing. It follows the SDKs; test on testnet first.

// Illustrative. Hex comes from viem. Mainnet values; on testnet use "Testnet", source "b" and 0x66eee.
const lower = (a: Hex) => a.toLowerCase() as Hex;
const minimalHex = (h: Hex): Hex => `0x${h.slice(2).replace(/^0+/, "") || "0"}`;
export async function multiSigL1(
signerKeys: Hex[], // at least `threshold` authorised users
leaderKey: Hex, // the outerSigner, an authorised user
multiSigUser: Hex,
outerSigner: Hex,
action: object, // an L1 action in canonical key order
nonce: number, // one nonce for everyone
) {
const inner = [lower(multiSigUser), lower(outerSigner), action];
const sigs = await Promise.all(signerKeys.map((k) => signL1Action(k, inner, { nonce, mainnet: true })));
const wrapper = {
type: "multiSig",
signatureChainId: "0xa4b1" as Hex,
signatures: sigs.map(({ r, s, v }) => ({ r: minimalHex(r), s: minimalHex(s), v })),
payload: { multiSigUser: lower(multiSigUser), outerSigner: lower(outerSigner), action },
};
const { type: _type, ...withoutType } = wrapper; // the envelope hash excludes `type`
const signature = await signUserAction(
leaderKey,
{
signatureChainId: wrapper.signatureChainId,
hyperliquidChain: "Mainnet",
multiSigActionHash: l1ActionHash(withoutType, { nonce }),
nonce,
},
"HyperliquidTransaction:SendMultiSig",
[
{ name: "hyperliquidChain", type: "string" },
{ name: "multiSigActionHash", type: "bytes32" },
{ name: "nonce", type: "uint64" },
],
);
return { action: wrapper, nonce, signature }; // POST this to /exchange
}

For a user-signed inner action, payload.action carries the bare action, without the two extra fields. The SDKs verify by rebuilding the typed data from the payload’s addresses, so they must match what was signed, lowercase included:

// Illustrative. One authorised user's inner signature over a usdSend.
const innerSig = await signUserAction(
signerKey,
{ ...usdSend, payloadMultiSigUser: lower(multiSigUser), outerSigner: lower(outerSigner) },
"HyperliquidTransaction:UsdSend",
[
{ name: "hyperliquidChain", type: "string" },
{ name: "payloadMultiSigUser", type: "address" },
{ name: "outerSigner", type: "address" },
{ name: "destination", type: "string" },
{ name: "amount", type: "string" },
{ name: "time", type: "uint64" },
],
);
// The wrapper then carries payload: { multiSigUser, outerSigner, action: usdSend }.

Checksummed addresses anywhere in the payload recover the wrong signer: same error. Lowercase everything before signing.

For the neighbouring signer errors, see Debugging signatures. For API wallets as leaders, see Nonces and API wallets.