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.

Precision: tick size, lot size, significant figures

Practitioner

A 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.

HyperCore rejects any price or size off its grid, so you round before you sign. There’s no tick size field to read: rules decide.

Say a perp has szDecimals 2 and trades at 99.98241. Two digits sit before the point, so 5 significant figures leave 3 decimals; the cap, 6 - 2 = 4, doesn’t bind. The tick is 0.001. From 100 it’s 0.01: 99.999 is valid, 100.001 is not.

A price is valid when both hold:

  1. It has at most 5 significant figures.
  2. It has at most MAX_DECIMALS - szDecimals decimal places, where MAX_DECIMALS is 6 for perps and 8 for spot.

Integers always pass: 123456 is valid, 12345.6 is not. szDecimals is in meta.universe for perps and on the base token in spotMeta.tokens for spot. A failure returns Price must be divisible by tick size.

The tick jumps tenfold at each power of ten

Section titled “The tick jumps tenfold at each power of ten”

With d digits before the point, the significant-figure rule allows 5 - d decimals and the cap then limits that:

d = floor(log10(price)) + 1 digits before the point (negative below 0.1)
decimals = clamp(5 - d, 0, MAX_DECIMALS - szDecimals)
tick = 10 ^ (-decimals)

For a perp (MAX_DECIMALS 6):

szDecimals Price d Decimals allowed Tick Valid example Invalid example
5 93231.23 5 0 1 93231 93231.2
2 137.23025 3 2 0.01 137.23 137.231
2 99.98241 2 3 0.001 99.982 99.9824
0 1234.56 4 1 0.1 1234.5 1234.56 (6 significant figures)
0 0.0012345 -2 6 0.000001 0.001234 0.0012345 (7 decimals)
1 0.012345 -1 5 0.00001 0.01234 0.012345 (more than 6 - 1 decimals)

For spot (MAX_DECIMALS 8), 0.0001234 (7 decimals) is valid for a base szDecimals of 0 or 1 and invalid from 2, where 8 - 2 allows 6.

A size has at most szDecimals decimals, in base-asset units: with szDecimals = 3, 1.001 passes and 1.0001 fails. Outcome prices skip the significant-figure rule and sit on the 0.00001 grid; sizes are whole contracts.

Parameter Perps Spot Outcomes
Significant figures in a price 5 (integers exempt) 5 (integers exempt) not applicable
MAX_DECIMALS 6 8 not applicable
Price decimals allowed min(5 - d, 6 - szDecimals), floor 0 min(5 - d, 8 - szDecimals), floor 0 5, from 0.00001 to 0.99999
Size decimals szDecimals base token szDecimals 0

Where the significant-figure rule binds, the tick is one part in 10,000 to 100,000: 0.0001 near 1 (if the cap allows four decimals) and 1 near 100,000. The smallest size is fixed per asset: 0.00001 BTC at the docs’ example szDecimals of 5.

SDKs round to nearest, by truncation or by side. Round limit prices by side, so rounding never makes an order more aggressive:

Side Round Effect
Buy down never pays more than intended
Sell up never sells for less than intended

Round the other way only to fill even one tick worse. Truncate sizes so an order never exceeds its balance; the docs say “rounded”, but truncation is always valid.

Use floor(log10(p)) + 1. The tempting ceil(log10(p)) undercounts at exact powers of ten: at 100 it gives 2, a tick of 0.001 instead of 0.01, so a quote one tick up, 100.001, is rejected.

Binary floats cannot hold most decimal prices, and JavaScript prints small numbers as 1e-7. Use a decimal library, a BigInt-backed decimal or string arithmetic:

type Side = "buy" | "sell";
// "123.45" -> { units: 12345n, scale: 2 }
function toScaled(x: string): { units: bigint; scale: number } {
if (!/^\d+(\.\d+)?$/.test(x)) throw new Error(`not a plain decimal string: ${x}`);
const [int, frac = ""] = x.split(".");
return { units: BigInt(int + frac), scale: frac.length };
}
// Back to a string with trailing zeros stripped, ready to sign.
function fromScaled(units: bigint, scale: number): string {
const digits = units.toString().padStart(scale + 1, "0");
const int = digits.slice(0, digits.length - scale);
const frac = digits.slice(digits.length - scale).replace(/0+$/, "");
return frac ? `${int}.${frac}` : int;
}
// floor(log10(x)) + 1, read off the string: "100" -> 3, "0.05" -> -1.
function intDigits(x: string): number {
const [int, frac = ""] = x.split(".");
const i = int.replace(/^0+/, "");
return i ? i.length : -(frac.match(/^0*/)?.[0].length ?? 0);
}
// Keep at most `decimals` decimals. Buys round down, sells round up.
function roundTo(x: string, decimals: number, side: Side): string {
const { units, scale } = toScaled(x);
if (scale <= decimals) return fromScaled(units, scale);
const step = 10n ** BigInt(scale - decimals);
const q = units / step + (side === "sell" && units % step !== 0n ? 1n : 0n);
return fromScaled(q, decimals);
}
// Perps: maxDecimals 6. Spot: maxDecimals 8 with the base token's szDecimals.
export function roundPx(px: string, szDecimals: number, maxDecimals: 6 | 8, side: Side): string {
const decimals = Math.max(0, Math.min(5 - intDigits(px), maxDecimals - szDecimals));
return roundTo(px, decimals, side);
}
// Outcomes: the 0.00001 grid. Clamp to 0.00001..0.99999 separately.
export const roundOutcomePx = (px: string, side: Side) => roundTo(px, 5, side);
// Sizes: truncate, never round up.
export const truncSz = (sz: string, szDecimals: number) => roundTo(sz, szDecimals, "buy");
Call Result
roundPx("1234.56", 0, 6, "buy") 1234.5
roundPx("0.012345", 1, 6, "buy") 0.01234
roundPx("99.9999", 2, 6, "sell") 100
roundPx("123456", 0, 6, "buy") 123456, integer left alone

The exchange hashes prices and sizes with trailing zeros removed, so "0.650" and "0.65" sign differently. Produce the stripped string once, as fromScaled does, and use it in body and hash alike. See signing.

  • Outcome mids and marks land on half ticks about a third of the time: round before quoting.
  • A fixed tick from szDecimals alone is wrong (one codebase did it).
  • A trailing-zero mismatch has surfaced as Price must be divisible by tick size., not a signature error.
  • Tick failures caught in pre-validation return one error for the whole batch (errors).
  • HyperEVM precompiles scale perp prices by 10^(6 - szDecimals) and spot by 10^(8 - base szDecimals).

For where szDecimals lives, see Asset ids and naming. For tick and lot size from a trader’s side, see Contract specifications.