Skip to main content

Interacting with Core

This page describes how an EVM contract reads from Core and writes to Core.

tip

Active on testnet. CoreWriter actions work. The stateless MTF derivatives precompiles (0x0900–0x0904) also work. Read precompiles backed by Core state, which query the positions and books of the chain directly, are upcoming. The bridge is active.

A contract on the MetaFlux EVM talks to Core (the L1 perps clearinghouse and on-chain CLOB) in two directions:

  • Read. A staticcall to a system precompile gets a value derived from Core.
  • Write. A call to the CoreWriter system contract submits an L1 action.

With read precompiles and a write contract, an EVM contract composes directly with current L1 state. It can quote against the formulas of the chain and then act on the clearinghouse, without a step outside the VM.

Writing to Core with CoreWriter​

To submit an L1 action, call CoreWriter at 0x3333333333333333333333333333333333333333:

interface ICoreWriter {
/// Emitted on every successful call; the L1 scanner consumes this log.
event RawAction(address indexed user, bytes data);

/// selector = keccak256("sendRawAction(bytes)")[0..4] = 0x17938e13
function sendRawAction(bytes calldata data) external;
}

data is a payload with a version prefix and an id prefix:

data = abi.encodePacked(
uint8(1), // version (currently 1)
uint24(actionId), // action id, big-endian (1..=22)
abi.encode(params) // the action's ABI-encoded parameters
);

The acting account is msg.sender, the calling contract. After a short action delay, the L1 dispatches the decoded action.

info

Atomicity. A sendRawAction call only burns gas and emits RawAction. Any failure on the L1 side after that is silent. There is no EVM revert. A contract must recover on its own. It must treat the RawAction event as the only causal link between the EVM call and the L1 outcome.

Actions​

CoreWriter exposes 22 L1 actions. The id goes big-endian in the uint24 slot above:

idActionPurpose
1LimitOrderPlaces a limit order on a perp or spot market. No record of a fill on placement exists. See unrecorded fills.
2VaultTransferDeposits to or withdraws from a vault.
3TokenDelegateDelegates stake to a validator. MTF takes an optional fourth word, the lock tier. See below.
4StakingDepositMoves tokens into the staking balance.
5StakingWithdrawMoves tokens out of the staking balance.
6SpotSendTransfers a spot token to another account.
7UsdClassTransferMoves USDC between the perp and spot class accounts.
8FinalizeEvmContractLinks an EVM contract to its Core token or contract id.
9AddApiWalletAuthorises a sub-key (agent wallet) for trading.
10CancelByOidCancels an order by server order id.
11CancelByCloidCancels an order by client order id.
12ApproveBuilderFeeAuthorises a builder to charge a fee, up to a cap.
13SendAssetGeneric asset transfer (perp, spot or vault).
14ReflectEvmSupplyChangeSyncs a supply change of an ERC-20 on the EVM side to Core.
15BorrowLendOpens or closes a borrow-lend position.
16PortfolioMarginEnrollOpts the sender in to or out of cross-asset portfolio margin.
17RfqSubmitSubmits an RFQ quote (id, market, side, size, limit price).
18FbaConfigureSets the frequent-batch-auction configuration of a market.
19CrossChainSendChain-agnostic cross-chain transfer. It goes into the queue of MetaBridge.
20EncryptedOrderSubmitThreshold-encrypted order (commitment and ciphertext).
21RfqQuoteA maker quotes against an open RFQ request.
22RfqAcceptA taker accepts a quote, and the RFQ settles off the book.

The typed parameter structs and a Solidity caller are in the public metaflux-contracts repo. The on-chain CoreWriter at 0x3333… is the production target. In tests, a deterministic Solidity stand-in emits the same RawAction payload.

Lock tier on action 3​

Do not assume parity with Hyperliquid here. Action 3 on HL encodes three words: validator, wei and isUndelegate. MTF accepts an optional fourth 32-byte word, lockMonths. A three-word call stays legal and means tier 0, so an encoder in the HL shape works with no change.

The reason for the word: tier 0 earns no revenue share. MTF splits the validator fee share by amount × lock multiplier, and the multiplier is 0× at tier 0. See the fee schedule and staking rewards. Without the fourth word, every delegation from the EVM is flexible. A contract can then bond stake and get nothing from the fee split. It still earns the Tier 1 fee discount.

lockMonthsMeaning
absent (3-word call)tier 0
0Flexible. No revenue share. Undelegate at any time.
1 / 6 / 24Locked. Draws a revenue share. Cannot start unbonding until the lock matures.

Core refuses any other value. It also refuses two cases that a locked tier can reach: a validator that is not on the governance allowlist for locked stake, and an addition to an existing row that holds a different tier.

danger

A refusal is silent, and the EVM receipt still says Success. Each refusal above is a deterministic no-op on Core. No funds move, no delegation row appears and the free staking pool does not change. The sendRawAction call itself only burns gas and emits RawAction, so it cannot revert on an L1 outcome (see the Atomicity note above). After the action delay, read staking_state to confirm the tier that the ledger stored. Do not read the receipt status as proof that the delegation landed.

Send exactly three words, or four or more. The fourth word selects the lock tier. Core refuses a params section between 97 and 127 bytes as params section truncated. That is a four-word call with a short declared length. Core ignores bytes past the fourth word, as for every other action.

encodeTokenDelegate in the reference Encoders helper still emits three words and keeps its pinned byte vector, so it stays a tier-0 encoder. A separate encodeTokenDelegateLocked takes the tier. If you do not use the helper, build the payload as abi.encodePacked(uint8(1), uint24(3), abi.encode(validator, wei_, isUndelegate, lockMonths)).

Reading Core with precompiles​

Each precompile is a staticcall to a fixed address. The input is a hand-rolled, big-endian packed encoding. It is not the Solidity ABI. Sizes and prices are on the 1e8 fixed-point plane (px_e8, size_e8). USDC margins are 1e6.

AddressPrecompileReturns
0x0900portfolio_margin_evalRequired maintenance margin in the style of SPAN, the index of the worst-case scenario, and the concentration penalty.
0x0901vault_navTotal NAV of the vault, total shares, NAV per share and unrealised PnL.
0x0902adl_pro_rata_priceThe VWAP at which an ADL of a given size clears, with a walk of the queue in side priority.
0x0903mark_settlePnL delta for each position, new accumulated funding and unrealised PnL at a mark.
0x0904rfq_book_depthRFQ book depth, filtered by side, with a capped depth.
0x0906clob_bboBest bid and best ask price and size (top of book).
0x0907clob_l2_depthThe top N aggregated (price, size) levels on each side.
0x0908inventory_riskNet and gross notional, concentration and the risk-cap gate.

Today these are stateless quoting precompiles. The caller passes the inputs (positions, queue levels, quotes, …), and the precompile returns the result. A contract can therefore reproduce a Core calculation with the formulas of the chain. Reads backed by current Core state, which query the positions and books of the chain directly, are upcoming.

portfolio_margin_eval (v1 ABI)​

The 0x0900 margin precompile uses the same SPAN engine that margins active accounts (see portfolio margin). An off-chain quote therefore matches on-chain maintenance exactly. There is no second copy of the math.

Its v1 input adds an implied-vol field for each position and a full-grid flag bit. The flag runs the complete scenario sweep instead of a faster subset. Prices and sizes are packed on the 1e8 plane and converted to the internal USD cents of the engine at the boundary.

The return gives the engine result in USD cents: the required maintenance margin, the index of the worst-case scenario, the concentration penalty, and the 100 000 USDC enrollment-equity floor that the engine applies. The typed calldata and return layout ship with the Solidity precompile interface in the public metaflux-contracts repo.

Disabling a precompile (governance)​

Governance can turn an MTF precompile off, and later back on, by a stake-weighted validator vote. A disabled precompile address returns no value derived from Core until a later vote enables it again. The set of disabled addresses is part of committed chain state, so every node agrees deterministically.

The vote has a range guard. The standard Ethereum precompiles (0x01–0x0a: ecrecover, sha256, ripemd160, identity, modexp, the bn256 and blake2f group) cannot be disabled. The chain rejects a vote that targets them at both proposal and enactment, so core EVM functions can never be disabled. Only the MTF precompiles (the 0x09xx range above) are eligible. This is a control for validator governance. It is not a user action, and it never appears on the /exchange path.

Core ↔ EVM value transfers​

  • Into Core from an EVM contract: SpotSend, SendAsset, UsdClassTransfer or VaultTransfer through CoreWriter (above).
  • Across chains: CrossChainSend goes into the queue of the MetaBridge custody bridge. The bridge releases on the destination chain on a ⅔ validator co-signature.

See also​

  • Bridge: cross-chain custody, the destination of CrossChainSend.
  • Mark prices: the 1e8 fixed-point price plane that the precompiles use.
  • Portfolio margin and ADL: the Core math that the 0x0900 and 0x0902 precompiles quote.