Reading Core from EVM

EVM contracts can read Core state synchronously through the READ_CORE precompile. Each read completes within the calling transaction and returns its result directly.

For each EVM block, the sequencer selects one sealed Core block (called the parent Core block) and pins its post-execution state for all Core reads in that EVM block. This keeps read results consistent throughout the EVM block, even if the selected snapshot lags the Core tip.

The READ_CORE precompile charges 20,000 gas per call, plus any memory expansion costs.

Execution

An EVM contract reads Core state by calling the READ_CORE precompile with an encoded query. The read completes synchronously and returns its result within the same call. Reads can also be evaluated off-chain through eth_call.

📘

Note

eth_calls that perform READ_CORE only succeed with the latest block tag. Support for earlier blocks is planned for an upcoming update.

Reads have no request ID, do not enter a queue, and require no destination transaction. They neither modify Core state nor record a request on Core.

Reads use the pinned snapshot rather than the latest Core state. Consequently:

  • The same read in a later EVM block can return a different result if that block uses a newer Core snapshot.
  • Reads do not reflect writes submitted earlier in the same EVM block, including within the same transaction.
  • A transaction-status read returns status 0 if the request was unprocessed in this Core snapshot, even if the Core tip has processed it since.

Failures

A failed read consumes all gas forwarded to READ_CORE and returns no data. In Solidity, call and staticcall report the failure by returning false.

Reads fail for unsupported selectors, malformed input, insufficient gas, or unmet query requirements—for example, a requested account, asset, or market that does not exist.

Supported reads

SelectorReadDescription
0x0000Transaction statusProcessing status and result of an EVM-initiated Core write.
0x0001Account infoAccount identity, controlling addresses, and collateral.
0x0002Core asset decimalsNumber of decimal places Core uses for an asset.
0x0003L1 token addressL1 token address associated with a Core asset.
0x0004EVM token addressEVM token address associated with a Core asset.
0x0005Market infoMarket configuration, fees, and prices.
0x0006Spot balanceAvailable and locked spot balances for an account and asset.
0x0007PositionPosition, margin, and order information for an account and market.
0x0008L1 account indexCore master account index for an L1 controlling address.
0x0009EVM account indexCore master account index for an EVM controlling address.

Reads that take a controlling address resolve to its master account, so subaccounts cannot be read.

Transaction status

Returns the processing status and result of an EVM-initiated Core write, identified by its request ID (reqId).

Input

abi.encodePacked(uint16(0x0000), reqId)
TypeFieldDescription
uint64reqIdEVM-to-Core write request identifier.

Output

abi.encode(status, data)
TypeFieldDescription
uint8status0: unprocessed; 1: successful; 2: failed.
bytesdataEmpty for status 0; otherwise the write-specific response.

Status 0 indicates that the request is either pending or has never been issued. Status 1 means the request has been successfully processed and status 2 indicates a failure.

Decode the response as (uint8 status, bytes data).

  • For status 0, data is empty.
  • For status 1, the write type determines whether data contains a return value or is empty.
  • For status 2, data contains abi.encode(uint32(errorCode)).

Account info

Returns information about a Core account identified by its controlling address and whether it is L1-controlled or EVM-controlled.

Input

abi.encodePacked(uint16(0x0001), accountAddress, isL1Account)
TypeFieldDescription
addressaccountAddressControlling address of the Core account.
boolisL1Accounttrue for an L1-controlled account; false for an EVM-controlled account.

Output

abi.encode(
    accountType,
    accountIndex,
    masterAccountIndex,
    l1Address,
    evmAddress,
    collateralBaseAmount
)
TypeFieldDescription
uint8accountType0: master account; 1: subaccount; 2: public pool; 3: insurance fund.
uint48accountIndexCore account index.
uint48masterAccountIndexControlling master account index.
addressl1AddressStored L1 address; the zero address for an EVM-controlled account.
addressevmAddressStored EVM address; the zero address for an L1-controlled account.
int128collateralBaseAmountShared USDC collateral, expressed as a signed amount in Core USDC base units. On a unified account, it includes USDC transferred on either route and can support both perpetuals and spot operations, subject to margin requirements. Excludes margin allocated to isolated positions. Rounded down.

The call fails if the account does not exist in Core state. A system address's presence in the L1 bridge mapping does not make its Core system account accessible through this read.

Core asset decimals

Returns the number of decimal places Core uses for the asset.

Input

abi.encodePacked(uint16(0x0002), assetId)
TypeFieldDescription
uint32assetIdCore asset identifier.

Output

abi.encode(decimals)
TypeFieldDescription
uint8decimalsNumber of decimal places Core uses for the asset.

The call fails if the asset does not exist.

L1 token address

Returns the L1 token address associated with the Core asset.

Input

abi.encodePacked(uint16(0x0003), assetId)
TypeFieldDescription
uint32assetIdCore asset identifier.

Output

abi.encode(l1Token)
TypeFieldDescription
addressl1TokenAssociated L1 token address; 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native ETH.

The call fails if the asset does not exist.

EVM token address

Returns the EVM token address associated with the Core asset.

Input

abi.encodePacked(uint16(0x0004), assetId)
TypeFieldDescription
uint32assetIdCore asset identifier.

Output

abi.encode(evmToken)
TypeFieldDescription
addressevmTokenAssociated EVM token address; 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for native ETH, and the zero address if no EVM token is linked.

The call fails if the asset does not exist.

Market info

Returns configuration, fees, and prices for the selected market.

Input

abi.encodePacked(uint16(0x0005), marketId)
TypeFieldDescription
uint16marketIdCore market identifier. Perpetuals: 0 <= marketId < 255; spot: 2048 <= marketId < 4095.

Output

abi.encode(
    marketType,
    baseAssetId,
    quoteAssetId,
    takerFee,
    makerFee,
    minBaseAmount,
    minQuoteAmount,
    liquidationFee,
    indexPrice,
    markPrice
)
TypeFieldDescription
uint8marketType0: perpetuals; 1: spot.
uint32baseAssetIdBase asset identifier of a spot market; 0 for perpetuals.
uint32quoteAssetIdQuote asset identifier of a spot market; 0 for perpetuals.
uint32takerFeeUpper bound on the taker fee rate: the market's maximum, in units of 10^-6 (500 is 0.05%).
uint32makerFeeUpper bound on the maker fee rate: the market's maximum, in units of 10^-6.
uint64minBaseAmountMinimum order amount.
uint64minQuoteAmountMinimum order value: amount * price must be at least this.
uint32liquidationFeeLiquidation fee rate in units of 10^-6.
uint64indexPriceCore index price; 0 for spot markets.
uint64markPriceCore mark price; 0 for spot markets.

Amounts and prices use the same market-specific scales as createOrder's amount and price. READ_CORE does not return the decimals; read them from GET /api/v1/orderBookDetails.

The fee actually charged is at the sequencer's discretion. It depends on the account's fee tier and is always at most than takerFee and makerFee. Most of the time, it is substantially lower.

The call fails if the market does not exist.

Spot balance

Returns the account's available and locked spot balances for the selected asset.

Input

abi.encodePacked(uint16(0x0006), accountAddress, isL1Account, assetId)
TypeFieldDescription
addressaccountAddressControlling address of the Core account.
boolisL1Accounttrue for an L1-controlled account; false for an EVM-controlled account.
uint32assetIdCore asset identifier.

Output

abi.encode(availableBalance, lockedBalance)
TypeFieldDescription
int128availableBalanceAvailable balance held separately from the margin balance. A unified account keeps its USDC in the margin balance, so USDC reads as 0 here even after a Spot-route deposit; its shared USDC collateral is reported by collateralBaseAmount in account info.
int128lockedBalanceBalance locked in open orders.

Both balances are expressed in Core base units, the units used by baseAmount. One base unit equals tickSize of the EVM token's smallest units. Divide the corresponding amount in EVM token units by tickSize and round down to obtain the Core balance. See Amounts for the definition of tickSize and how to look it up.

The call fails if the account or asset does not exist.

Position

Returns the account's position in the selected market.

Input

abi.encodePacked(uint16(0x0007), accountAddress, isL1Account, marketId)
TypeFieldDescription
addressaccountAddressControlling address of the Core account.
boolisL1Accounttrue for an L1-controlled account; false for an EVM-controlled account.
uint16marketIdCore market identifier. Perpetuals: 0 <= marketId < 255; spot: 2048 <= marketId < 4095. Spot markets have no positions, so a spot marketId returns an empty position.

Output

abi.encode(
    lastFundingRatePrefixSum,
    position,
    entryQuote,
    initialMarginFraction,
    totalOrderCount,
    totalPositionTiedOrderCount,
    marginMode,
    allocatedMargin
)
TypeFieldDescription
int64lastFundingRatePrefixSumCumulative funding rate last recorded for the position.
int64positionSigned position size, on the same scale as createOrder's amount. Positive for long, negative for short; magnitude at most 2^56 - 1.
uint56entryQuoteUnsigned value of the position at entry, in USDC base units (10^6 per USDC).
uint16initialMarginFractionFraction of the position value required as initial margin, in units of 10^-4 (500 is 5%). 0 means the market's default.
uint64totalOrderCountNumber of orders for the position.
uint64totalPositionTiedOrderCountNumber of orders tied to the position.
uint8marginMode0: cross; 1: isolated.
int128allocatedMarginAllocated margin in Core's extended USDC units, which use 12 decimal places (10^12 units per USDC).

The call fails if the account or market does not exist.

L1 account index

Resolves an L1 controlling address to its Core master account index.

Input

abi.encodePacked(uint16(0x0008), l1Address)
TypeFieldDescription
addressl1AddressControlling address on Ethereum L1.

Output

abi.encode(accountIndex)
TypeFieldDescription
uint48accountIndexCore master account index for the supplied address. Read from Core's view of the L1 bridge mapping, addressToAccountIndex[l1Address].

The call fails if the account does not exist in Core state. A system address's presence in the L1 bridge mapping does not make its Core system account accessible through this read or the account-info read.

EVM account index

Resolves an EVM controlling address to its Core master account index.

Input

abi.encodePacked(uint16(0x0009), accountAddress)
TypeFieldDescription
addressaccountAddressControlling address on Lighter EVM.

Output

abi.encode(accountIndex)
TypeFieldDescription
uint48accountIndexCore master account index for the supplied address. Read from evmAccountMapping[accountAddress].

The call fails if the account does not exist in Core state.

Example: reading a spot balance

The following contract reads a Core spot balance.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CoreReader {
    address constant READ_CORE = 0x6600000000000000000000000000000000000000;

    function spotBalance(
        address accountAddress,
        bool isL1Account,
        uint32 assetId
    ) external view returns (int128 availableBalance, int128 lockedBalance) {
        (bool success, bytes memory result) = READ_CORE.staticcall(
            abi.encodePacked(uint16(0x0006), accountAddress, isL1Account, assetId)
        );
        require(success, "Core balance read failed");
        return abi.decode(result, (int128, int128));
    }
}

Did this page help you?