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 performREAD_COREonly succeed with thelatestblock 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
0if 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
| Selector | Read | Description |
|---|---|---|
0x0000 | Transaction status | Processing status and result of an EVM-initiated Core write. |
0x0001 | Account info | Account identity, controlling addresses, and collateral. |
0x0002 | Core asset decimals | Number of decimal places Core uses for an asset. |
0x0003 | L1 token address | L1 token address associated with a Core asset. |
0x0004 | EVM token address | EVM token address associated with a Core asset. |
0x0005 | Market info | Market configuration, fees, and prices. |
0x0006 | Spot balance | Available and locked spot balances for an account and asset. |
0x0007 | Position | Position, margin, and order information for an account and market. |
0x0008 | L1 account index | Core master account index for an L1 controlling address. |
0x0009 | EVM account index | Core 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)| Type | Field | Description |
|---|---|---|
uint64 | reqId | EVM-to-Core write request identifier. |
Output
abi.encode(status, data)| Type | Field | Description |
|---|---|---|
uint8 | status | 0: unprocessed; 1: successful; 2: failed. |
bytes | data | Empty 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,datais empty. - For status
1, the write type determines whetherdatacontains a return value or is empty. - For status
2,datacontainsabi.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)| Type | Field | Description |
|---|---|---|
address | accountAddress | Controlling address of the Core account. |
bool | isL1Account | true for an L1-controlled account; false for an EVM-controlled account. |
Output
abi.encode(
accountType,
accountIndex,
masterAccountIndex,
l1Address,
evmAddress,
collateralBaseAmount
)| Type | Field | Description |
|---|---|---|
uint8 | accountType | 0: master account; 1: subaccount; 2: public pool; 3: insurance fund. |
uint48 | accountIndex | Core account index. |
uint48 | masterAccountIndex | Controlling master account index. |
address | l1Address | Stored L1 address; the zero address for an EVM-controlled account. |
address | evmAddress | Stored EVM address; the zero address for an L1-controlled account. |
int128 | collateralBaseAmount | Shared 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)| Type | Field | Description |
|---|---|---|
uint32 | assetId | Core asset identifier. |
Output
abi.encode(decimals)| Type | Field | Description |
|---|---|---|
uint8 | decimals | Number 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)| Type | Field | Description |
|---|---|---|
uint32 | assetId | Core asset identifier. |
Output
abi.encode(l1Token)| Type | Field | Description |
|---|---|---|
address | l1Token | Associated 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)| Type | Field | Description |
|---|---|---|
uint32 | assetId | Core asset identifier. |
Output
abi.encode(evmToken)| Type | Field | Description |
|---|---|---|
address | evmToken | Associated 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)| Type | Field | Description |
|---|---|---|
uint16 | marketId | Core market identifier. Perpetuals: 0 <= marketId < 255; spot: 2048 <= marketId < 4095. |
Output
abi.encode(
marketType,
baseAssetId,
quoteAssetId,
takerFee,
makerFee,
minBaseAmount,
minQuoteAmount,
liquidationFee,
indexPrice,
markPrice
)| Type | Field | Description |
|---|---|---|
uint8 | marketType | 0: perpetuals; 1: spot. |
uint32 | baseAssetId | Base asset identifier of a spot market; 0 for perpetuals. |
uint32 | quoteAssetId | Quote asset identifier of a spot market; 0 for perpetuals. |
uint32 | takerFee | Upper bound on the taker fee rate: the market's maximum, in units of 10^-6 (500 is 0.05%). |
uint32 | makerFee | Upper bound on the maker fee rate: the market's maximum, in units of 10^-6. |
uint64 | minBaseAmount | Minimum order amount. |
uint64 | minQuoteAmount | Minimum order value: amount * price must be at least this. |
uint32 | liquidationFee | Liquidation fee rate in units of 10^-6. |
uint64 | indexPrice | Core index price; 0 for spot markets. |
uint64 | markPrice | Core 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)| Type | Field | Description |
|---|---|---|
address | accountAddress | Controlling address of the Core account. |
bool | isL1Account | true for an L1-controlled account; false for an EVM-controlled account. |
uint32 | assetId | Core asset identifier. |
Output
abi.encode(availableBalance, lockedBalance)| Type | Field | Description |
|---|---|---|
int128 | availableBalance | Available 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. |
int128 | lockedBalance | Balance 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)| Type | Field | Description |
|---|---|---|
address | accountAddress | Controlling address of the Core account. |
bool | isL1Account | true for an L1-controlled account; false for an EVM-controlled account. |
uint16 | marketId | Core 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
)| Type | Field | Description |
|---|---|---|
int64 | lastFundingRatePrefixSum | Cumulative funding rate last recorded for the position. |
int64 | position | Signed position size, on the same scale as createOrder's amount. Positive for long, negative for short; magnitude at most 2^56 - 1. |
uint56 | entryQuote | Unsigned value of the position at entry, in USDC base units (10^6 per USDC). |
uint16 | initialMarginFraction | Fraction of the position value required as initial margin, in units of 10^-4 (500 is 5%). 0 means the market's default. |
uint64 | totalOrderCount | Number of orders for the position. |
uint64 | totalPositionTiedOrderCount | Number of orders tied to the position. |
uint8 | marginMode | 0: cross; 1: isolated. |
int128 | allocatedMargin | Allocated 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)| Type | Field | Description |
|---|---|---|
address | l1Address | Controlling address on Ethereum L1. |
Output
abi.encode(accountIndex)| Type | Field | Description |
|---|---|---|
uint48 | accountIndex | Core 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)| Type | Field | Description |
|---|---|---|
address | accountAddress | Controlling address on Lighter EVM. |
Output
abi.encode(accountIndex)| Type | Field | Description |
|---|---|---|
uint48 | accountIndex | Core 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));
}
}Updated about 7 hours ago
