Skip to main content

Core ↔ EVM transfers

tip

Live on testnet. The EVM→Core value-transfer actions (SpotSend, SendAsset, UsdClassTransfer, VaultTransfer via CoreWriter) and Core→EVM credit materialization are operational and tested. The bridge (cross-chain custody) is live.

Value moves between Core (the L1 clearinghouse / spot ledger) and the EVM side in two directions. Both are deterministic and account-scoped.

Moving VALUE from EVM to Core

This is the lane you want if you are moving a balance. Send an ordinary EVM transaction to the system withdraw sink:

to: 0x0000000000000000000000000000000000000602
data: abi.encode(uint256 asset_id, uint256 amount) // 64 bytes, exactly two words

The node burns amount of your system token on the EVM side and credits the SAME account on Core — USDC (asset_id 0) to your perp cross-collateral, any other asset to your spot balance. The credit is always backed: it credits only what the burn actually removed, so the lane cannot mint.

RequirementWhy
The transaction must SUCCEEDA reverted tx is skipped by the scan
data is at least 64 bytesShorter calldata is ignored, silently
asset_id is the LOW 4 bytes of word 0It is read as a uint32
amount is the LOW 16 bytes of word 1It is read as a uint128
The asset must be registeredAn unresolvable asset_id is ignored
warning

A short or malformed calldata is IGNORED, not rejected. The transaction succeeds, gas is spent, and nothing moves. There is no revert to catch, so check your balance on Core rather than the EVM receipt.

info

The /exchange action core_evm_transfer with to_evm: false is REFUSED, on purpose. Crediting Core without a confirmed EVM burn would create value out of nothing, so that path fails closed and points here instead.

Which assets can cross

Not every token you hold can. Ask the chain rather than guessing:

{ "type": "markets_meta", "kind": "spot" }
{ "spot": { "tokens": [
{ "id": 101, "name": "BTC",
"evm_contract": { "address": "0x…", "variant": 0, "evm_extra_wei_decimals": 0 } },
{ "id": 5, "name": "TOKEN5", "evm_contract": null }
] } }

evm_contract is the test. A token with an object can cross; a token with null cannot. The read resolves the address through the SAME predicate the transfer path uses, so it can never offer a binding the chain would then refuse. variant names how the token is bound — 0 a deployed contract, 1 and 2 storage-slot forms — and it does not change whether the asset crosses.

The token registry is one section of markets_meta, so the same call that gives you the tradable universe gives you this. There is no separate bindings read.

Two assets cross without a bound ERC-20 row: USDC, which is the fixed FiatToken predeploy, and the native gas token, which is the EVM balance itself rather than a contract.

info

A binding is permanent. The binding vote is first-write-wins: it refuses an asset that already has a binding, and refuses a contract already bound to another asset. Nothing removes one. Read the address from markets_meta anyway, because a token can gain its FIRST binding at any time, and key your own records on the asset id.

What the binding vote decides, and what it does not

Binding a token to an ERC-20 is a ⅔-stake validator vote (FinalizeEvmContract, CoreWriter action 8). Every validator must submit a byte-equal proposal for it to tally.

The proposal fixes the asset, the variant and the contract. It does not set the token's decimals: a credit lands in the token's own wei_decimals, chosen once at spot_register_token and never changed after. So the SIZE of every credit on this lane is decided at registration, and the binding only decides where the credit goes.

warning

The proposal carries no decimals field. The tallied payload folds the registry's wei_decimals and the node_gov cast record shows it, and a bind above 18 is refused — but the proposal a validator reads carries no scale of its own. Read the token's wei_decimals from markets_meta kind: "spot" before you vote on a binding.

Offer the transfer only for the assets that resolve. An asset the chain cannot resolve is the silent-failure case above: the burn transaction succeeds and nothing moves.

EVM → Core (via CoreWriter)

A contract submits an L1 ACTION through CoreWriter (0x3333…3333). The acting account is the calling contract (msg.sender):

ActionEffect
SpotSendTransfer a spot token to another account on Core
SendAssetGeneric asset transfer (perp / spot / vault classes)
UsdClassTransfer ⚠️Rejected. One USDC pool, so there is no second class to move to. The call still burns gas and emits RawAction; the L1 rejection is silent, per the atomicity rule below. See USDC.
VaultTransferDeposit to / withdraw from a vault

These are subject to CoreWriter's atomicity rule: the call burns gas + emits RawAction; any L1-side failure afterwards is silent (no EVM revert).

A contract's CoreWriter call reaches Core, subject to the atomicity rule above.

Moving VALUE from Core to EVM

This is the lane you want if you are moving a balance the other way. Two /exchange actions do it, and both reach the same queue and land the same credit:

ActionField shapeDebitsAvailability
core_evm_transferMTF-nativethe perp collateral pool for asset: 0, else the spot ledgerlive at every height
send_to_evm_with_dataHyperliquid-compatiblethe spot ledger, alwayslive

Use core_evm_transfer if you have a choice. Both are live, and it is the only one of the two that can move USDC out of the perp collateral pool — the balance account_value / withdrawable report. Reach for send_to_evm_with_data when you are porting a client that already builds the Hyperliquid field shape. The full comparison is which Core → EVM action to use.

Both debit the sender's exchange ledger the moment the action commits, and queue one EVM credit that the node mints on the next EVM block. Because the debit lands first, the queued credit is always backed — the lane cannot mint. Both may carry an optional EVM payload of up to 4096 bytes, which runs against the recipient after the credit lands. The payload never unwinds the credit: a revert leaves the credit standing, so read its receipt.

Only assets the chain can resolve may cross — see which assets can cross.

warning

Amounts are decimal strings, and an amount too small to credit is REFUSED, not rounded to nothing. The lane truncates twice toward zero: to 8 decimal places, then to the token's own EVM decimals. So the smallest creditable amount is 10 ^ -min(8, the token's EVM decimals)0.000001 for USDC, 0.00000001 for native MTF. Below that the action refuses. Above it you are debited exactly what is credited, so no sub-quantum remainder is destroyed in transit.

Both lanes charge a fee, and the fee is MTF

The fee parameter is 0, so the chain charges no fee. A two-thirds-stake governance vote sets it, and charging starts as soon as a vote enacts a value above 0. Both actions then charge the same fee, so neither lane is cheaper.

The fee is a quantity of MTF, debited on top of the amount, and it is independent of the asset you move: a transfer of USDC debits USDC for the amount and MTF for the fee. The chain takes it from your spot MTF balance first, then from your USDC at the MTF reference price, and refuses the transfer when neither covers it.

warning

A transfer can be refused for a reason that has nothing to do with the asset you are moving. MTF is priced from its own book, so the USDC step needs that reference price. When the price is not usable the chain refuses the transfer rather than charge at a guessed price. Hold enough spot MTF to cover the fee and the reference price is never read.

The rule, the rejection strings and the governance parameter are in the fee and Fees.

Core → EVM (system pseudo-transactions)

When an L1 begin-block effect needs to land on the EVM side — e.g. a spot send whose recipient is an EVM-side address, or a bridge inbound mint — it is queued and materialized as a deterministic system pseudo-transaction on the next EVM block:

OpSourceAmount scale
SpotCreditan L1 spot balance credited to a 20-byte EVM recipient1e8 fixed-point
BridgeMinta MetaBridge inbound mint (e.g. USDC)1e6 (USDC native)

Ordering + throughput:

  • Queued by L1 round, drained in ascending round order, FIFO within a round — so two validators materialize the same ops in the same order (determinism).
  • Each op is billed a system-gas cost and drained against an elastic per-block system-gas slice (it scales with the block gas budget); leftover ops carry to the next block. Expect Core→EVM credits to land within a small number of blocks, not instantly in the same block they were triggered.

Cross-chain (a different surface)

CrossChainSend (CoreWriter action 19) does not move value to the local EVM — it queues a withdrawal into the MetaBridge custody bridge, which releases on the destination chain (Base / Arbitrum) on a ⅔ validator co-signature behind a dispute window.

See also