Concepts
Two facts explain most of this library:
- There are two sets of balances: one on HyperEVM and one on HyperCore.
- When you send an action, it isn't done yet. HyperCore carries it out after the EVM block.
HyperEVM and HyperCore
Hyperliquid has two systems:
- HyperCore is the exchange. It holds the order books, perp positions, spot balances, vaults and staking.
- HyperEVM is an Ethereum-compatible chain where smart contracts run.
A contract on HyperEVM can reach HyperCore in three ways:
flowchart TB
subgraph EVM["HyperEVM"]
C["Your contract"]
P["Read precompiles<br/>0x0800 to 0x0813"]
W["CoreWriter<br/>0x3333...3333"]
D["Circle CoreDepositWallet"]
end
S["HyperCore<br/>balances, positions, order books"]
C -- "read" --> P
C -- "send action" --> W
C -- "USDC deposit" --> D
P -. "state at block start" .- S
W -- "done after the block" --> S
D -- "credited in the next block" --> S
Read precompiles. A precompile is a function built into the chain at a
fixed address. You call it with ABI-encoded arguments and get ABI-encoded bytes
back, and the precompiles module handles both sides for you.
CoreWriter. CoreWriter takes an action as raw bytes. The first 4 bytes are
a header (version 0x01, then a 3-byte action ID), and the ABI-encoded
arguments follow it. The core_writer module builds these bytes for you. Here
is a full payload:
01000006header: version 0x01, action 6 (spot send)0000000000000000000000001212121212121212121212121212121212121212to: recipient address0000000000000000000000000000000000000000000000000000000000000000token: 0 (USDC)0000000000000000000000000000000000000000000000000000000005f5e100amount_wei: 100,000,000 = 1 USDC at 8 decimalscore_writer.spot_send(0x1212…12, 0, 100_000_000). The length is 4 + 3 × 32 = 100 bytes. Zeros on the left of each word (faded) are padding.USDC transfers. A deposit goes through Circle's CoreDepositWallet contract,
and a return to EVM is a CoreWriter send_asset action to the USDC system
address. The bridge module handles both directions.
Where your USDC can be
Think of HyperEVM as your bank account and HyperCore as your brokerage account, with two balances inside it. USDC can be in three places:
| Where | What it is |
|---|---|
| HyperEVM | The USDC ERC-20 token that your contract holds |
| HyperCore spot | Cash for buying and selling tokens |
| HyperCore perp | Margin for perp (futures) positions, in USD |
Units gives the decimals of each.
flowchart LR
subgraph EVM["HyperEVM"]
E["EVM USDC"]
end
subgraph CORE["HyperCore"]
S["spot"]
P["perp"]
end
E -- "deposit: Circle CoreDepositWallet" --> S
E -- "deposit" --> P
S -- "return: send_asset" --> E
P -- "return: send_asset" --> E
S <-- "transfer_usd_class" --> P
USDC moves in two ways:
- Between HyperEVM and HyperCore. A deposit goes through Circle's
CoreDepositWallet (
bridge.bridge_usdc_to_core), and a return is a CoreWritersend_assetto the USDC system address (bridge.bridge_usdc_to_evm). Tutorial 4 does both. - Between spot and perp, inside HyperCore. One CoreWriter action,
core_writer.transfer_usd_class, moves USDC between the spot and perp balances, and nothing leaves HyperCore. Tutorial 3 shows it.
A contract that trades perps can deposit straight to perp DEX 0, or deposit to
spot and move margin with transfer_usd_class. To pay out, it moves the USDC
back to spot or returns it straight from perp to EVM.
Actions are asynchronous
sequenceDiagram
participant V as Your contract
participant W as CoreWriter
participant C as HyperCore
V->>W: sendRawAction(...)
W-->>V: EVM call succeeds
Note over V,C: The action is only in a queue
W->>C: HyperCore does the action after the EVM block
alt Action succeeds
C->>C: Core state changes
else Action fails
Note over V,C: The EVM transaction does NOT revert
end
V->>C: A later block reads the result
- A successful EVM transaction tells you only one thing: the action is in the queue.
- A precompile read returns the Core state at the start of the current EVM block, so a contract can't see the result of its own action in the same block.
- If your contract keeps accounts, it must mark each transfer as pending and later match it against the Core state it reads.
Off-chain tools have a different limit. An eth_call to a precompile ignores
the block tag and always returns the current Core state, even when you ask for
an old block. You can't read past Core state, so record each
reading when you take it, together with the EVM block and the Core block
(l1_block_number).
Units
The same token amount is a different integer depending on where it is held. For USDC:
| Place | Decimals | One USDC |
|---|---|---|
EVM USDC token (arguments of the bridge functions) |
6 | 1_000_000 |
Core token amounts (spot_send, send_asset) |
8 | 100_000_000 |
Perp USD (transfer_usd_class, vault_transfer) |
6 | 1_000_000 |
flowchart LR
E["EVM USDC token<br/>1_000_000<br/>(6 decimals)"] -- "× 100" --> K["Core spot amount<br/>100_000_000<br/>(8 decimals)"]
K -- "wei_to_perp: ÷ 100" --> U["Perp USD<br/>1_000_000<br/>(6 decimals)"]
U -- "perp_to_wei: × 100" --> K
K -- "÷ 100" --> E
The bridge functions take EVM amounts and multiply by 100 themselves.
The core_writer functions take Core amounts or perp amounts and don't convert
anything. If you pass an EVM amount to spot_send, you send 1/100 of what you
meant to.
Each Core token has metadata, which you can read with precompiles.token_info.
wei_decimalsis the number of decimals in Core token amounts. This library calls the integer unit of a Core amount "Core wei". It is not the 18-decimal Ethereum unit.sz_decimalsis the number of decimals in order sizes.evm_extra_wei_decimalsis how many more decimals the EVM token has than the Core token. For USDC the value is negative, because the EVM token has 6 decimals and the Core token has 8.
The conversions functions use these values. They use integer arithmetic and
round down, so wei_to_perp(199) returns 1 and a small amount can convert to
zero. Check for zero before you move funds. An overflow reverts.
Responsibilities
The library encodes requests, decodes replies, and checks units and bounds, but it doesn't make policy decisions. Security lists what the contract that imports it must handle.
Terms
| Term | Meaning |
|---|---|
| ABI encoding | The standard byte layout for function arguments and return values |
| Allowance | Permission for one address to spend the ERC-20 tokens of another address |
| DEX index | A number that selects a Core balance context. 0 is the default perp DEX. 2**32 - 1 is spot. |
| Gas | The execution budget of the EVM |
| Revert | A failure that cancels all changes of the EVM transaction |
| System address | An address for each token. A transfer to it moves the token between Core and EVM. |
The Hyperliquid developer documentation describes these interfaces in full.