> For the complete documentation index, see [llms.txt](https://docs.lighter.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.lighter.xyz/evm/interop/message-passing/evm-and-core.md).

# EVM and Core

EVM and Core communicate through stateful write streams, one in each direction. These streams are [settled on L1](/evm/technical-architecture.md#batch-lifecycle-and-finalization) together with Core and EVM state, allowing the sequencer to fulfill cross-domain write requests without waiting for L1 finality.

Rather than exposing arbitrary cross-domain messaging, the protocol defines a fixed set of operations that users can request on each domain.

Writes are *asynchronous* and processed first-in, first-out within each direction. The sequencer aims to fulfill requests as soon as possible, but the protocol does not guarantee a fixed delay between submission on one domain and fulfillment on the other.

Pending writes have the same censorship-resistance protection as L1 priority requests. If a write remains unfulfilled past its 14-day expiration period, the [escape hatch](/evm/technical-architecture.md#escape-hatch) can be activated on L1.

## EVM to Core

EVM accounts can request actions on Core, such as placing orders or transferring tokens. Each request is submitted on EVM and processed asynchronously by Core.

Two EVM [predeploys](/evm/chain-info.md#predeploys) provide access to these writes.

* [`EvmToCoreEntrypoint`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) defines the supported writes. Most methods can be called directly by users. Some token-related methods can only be called by [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol).
* [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol) handles token-specific logic before calling the restricted entrypoint methods. See [Token transfers](/evm/interop/tokens/token-transfers/evm-and-core.md) and [Token linking](/evm/interop/tokens/token-linking.md#linking-the-canonical-token) for those flows.

![EVM-to-Core write flow with separate Lighter EVM and Lighter Core domains, connected by a write stream across an asynchronous boundary.](https://1283879166-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNb0n4Ug3DwUnIWDRnc85%2Fuploads%2Fgit-blob-d0ee21aa2953d968b0baed3faf60628897fdf7ff%2Fevm-to-core-write-dark-0b999e56.svg?alt=media)

Every write ultimately passes through [`EvmToCoreMessenger`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreMessenger.sol), which emits the request as raw bytes for Core to consume. However, its submission method accepts calls only from [`EvmToCoreEntrypoint`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol), so users cannot bypass the supported operations to send arbitrary messages to Core.

### Submitting a write to Core

Submit a write to Core through one of the two predeploys introduced above. Every submitted write is assigned a `uint64 reqId` that identifies the request on Core. The write methods return this ID.

If the EVM call reverts, no request is submitted. A successful EVM transaction confirms only that the request was submitted, not that Core executed it.

The Core sequencer validates each request against the state immediately before execution. This state may differ from what an earlier EVM read returned.

Core fulfillment can fail for various reasons, documented under each [supported write](#supported-writes). Since submission and fulfillment are not atomic, a failure on Core does not revert the initiating EVM transaction.

A failed request is still consumed and cannot be replayed. Trying again requires submitting a new request with a new `reqId`.

Multiple writes submitted in one EVM transaction are processed independently on Core, not atomically. One can succeed while another fails.

Keep the `reqId` to track the request's status and read its outcome from Core, as described in [Reading the result of a write](#reading-the-result-of-a-write).

### Reading the result of a write

Core records a status and a response for every request it processes. To retrieve them, call `READ_CORE` with the [transaction-status selector](/evm/interop/reading-core-from-evm.md#transaction-status) (`0x0000`) and the request's `reqId`:

```solidity
(bool success, bytes memory result) = READ_CORE.staticcall(
    abi.encodePacked(uint16(0x0000), reqId)
);
(uint8 status, bytes memory data) = abi.decode(result, (uint8, bytes));
```

Interpret the returned `status` and `data` using the following table.

| `status` | Meaning                                               | `data`                                             |
| -------- | ----------------------------------------------------- | -------------------------------------------------- |
| `0`      | Core has not processed the request yet.               | Empty.                                             |
| `1`      | Core executed the write successfully.                 | The write's return value, or empty if it has none. |
| `2`      | Core executed the write, but it failed with an error. | `abi.encode(uint32(errorCode))`.                   |

Each operation's **Core response** section describes its success data (`status == 1`) and possible error codes (`status == 2`).

**When the result is visible.** The result is never available in the EVM transaction that submitted the write. It appears in a later EVM block, once Core processes the request. Check from a later transaction, or poll off-chain with `eth_call`, until `status` is nonzero.

**Pending versus unknown.** Status `0` is also returned for request IDs that have never been issued. [`EvmToCoreMessenger.reqId()`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreMessenger.sol) returns the next request ID to be assigned. A request is pending if its status is `0` and its `reqId` is less than this counter.

#### Example: submit, then read the result

The following contract submits a cancel-all request and exposes its outcome. The contract is the sender of the write, so it must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account or one of its subaccounts.

{% hint style="warning" %}
**Warning**

Core treats the immediate EVM caller as the authority for a write. For a contract-controlled Core account, this is the contract that calls `EvmToCoreEntrypoint`. Such contracts should enforce any desired access control over who may trigger Core writes through them.
{% endhint %}

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

interface IEvmToCoreEntrypoint {
    function cancelAllOrders(uint48 accountIndex) external returns (uint64 reqId);
}

contract CoreWriter {
    address constant READ_CORE = 0x6600000000000000000000000000000000000000;
    IEvmToCoreEntrypoint constant ENTRYPOINT =
        IEvmToCoreEntrypoint(0x6600000000000000000000000000000000000009);

    address public immutable owner;

    constructor() {
        owner = msg.sender;
    }

    // Step 1: submit the request. Core executes it in a later Core block.
    function cancelAll(uint48 accountIndex) external returns (uint64 reqId) {
        require(msg.sender == owner, "not owner");
        reqId = ENTRYPOINT.cancelAllOrders(accountIndex);
    }

    // Step 2: read the outcome from a later EVM block or with eth_call.
    function cancelAllResult(uint64 reqId)
        external
        view
        returns (uint8 status, uint64 ordersToCancel, uint32 errorCode)
    {
        (bool success, bytes memory result) = READ_CORE.staticcall(
            abi.encodePacked(uint16(0x0000), reqId)
        );
        require(success, "Core status read failed");

        bytes memory data;
        (status, data) = abi.decode(result, (uint8, bytes));
        if (status == 1) {
            ordersToCancel = abi.decode(data, (uint64));
        } else if (status == 2) {
            errorCode = abi.decode(data, (uint32));
        }
    }
}
```

### Core transaction hash

Core APIs identify a fulfilled write by its Core transaction hash. To derive it, decode the `WriteToCore(address indexed sender, uint64 indexed reqId, bytes writeRequest)` event emitted by [`EvmToCoreMessenger`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreMessenger.sol) in the EVM transaction receipt. Use the event's `sender`, `reqId`, and full `writeRequest`, including its transaction-type byte.

The following helper uses the network's [**Core chain ID**](/evm/chain-info.md#chain-metadata), not its EVM chain ID. It hashes the packed fields, appends eight zero bytes, and returns the 80-character hexadecimal string expected by Core APIs, without a `0x` prefix.

```ts
import { encodePacked, keccak256, padHex, type Address, type Hex } from 'viem';

function coreTxHash(
  coreChainId: number,
  sender: Address,
  reqId: bigint,
  writeRequest: Hex,
): string {
  const digest = keccak256(encodePacked(
    ['string', 'uint32', 'uint64', 'address', 'bytes'],
    ['zkl/toCoreTx/v1', coreChainId, reqId, sender, writeRequest],
  ));
  return padHex(digest, { size: 40, dir: 'right' }).slice(2);
}
```

If an EVM transaction submits multiple writes, select the event for the intended `reqId`. Each write has its own Core transaction hash. Use this hash when [retrieving a Core-originated L1 Call](/evm/interop/message-passing/l1-and-core.md#executing-core-messages-on-l1).

### Supported writes

The following methods belong to [`EvmToCoreEntrypoint`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol).

| Function                                               | Description                                                                                                                                                                                                    |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [changePubKey](#change-public-key)                     | Update or revoke a Core API key.                                                                                                                                                                               |
| [createOrder](#create-order)                           | Place an order on a Core market.                                                                                                                                                                               |
| [cancelOrder](#cancel-order)                           | Cancel an order by its Core or client identifier.                                                                                                                                                              |
| [cancelAllOrders](#cancel-all-orders)                  | Cancel all open orders for a Core account.                                                                                                                                                                     |
| [registerSendToCore](#send-to-core)                    | Request a deposit credit on Core. Callable only by [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol).                     |
| [finalizeEvmLink](#finalize-evm-link)                  | Notify Core of an EVM-native asset's canonical token. Callable only by [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol). |
| [registerSendToEvm](#evm-initiated-send-to-evm)        | Request a Core balance debit and delivery on EVM. Callable only by [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol).     |
| [burnShares](#burn-shares)                             | Burn public-pool shares held by a Core account.                                                                                                                                                                |
| [withdrawToL1Address](#evm-initiated-withdrawal-to-l1) | Request a withdrawal from a Core balance to an L1 recipient.                                                                                                                                                   |

Each **Core response** describes the `status` and `data` returned when [reading the result of a write](#reading-the-result-of-a-write).

#### Change public key

Call [`EvmToCoreEntrypoint.changePubKey`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) to update or revoke a Core API key. The `apiKeyIndex` identifies the key to update, and `pubKey` supplies its new public key.

The sender must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account, one of its subaccounts, or a public pool it controls. Staking pool accounts are not supported.

**Call**

```solidity
function changePubKey(
    uint48 accountIndex,
    uint8 apiKeyIndex,
    bytes calldata pubKey
) external returns (uint64 reqId);
```

**Parameters**

| Type     | Parameter      | Description                                                                                 |
| -------- | -------------- | ------------------------------------------------------------------------------------------- |
| `uint48` | `accountIndex` | Core account whose API key is updated.                                                      |
| `uint8`  | `apiKeyIndex`  | Index of the API key to update, from `0` to `254`.                                          |
| `bytes`  | `pubKey`       | New public key in Core's required 40-byte encoding, or 40 zero bytes to revoke the API key. |

To generate a key pair using Lighter's SDKs, see [How to create API keys programmatically](https://docs.lighter.xyz/developers/integration-guides/api-keys#how-to-create-api-keys-programmatically).

An invalid `apiKeyIndex` or `pubKey` encoding causes the EVM call to revert before a write is submitted to Core.

**Core response**

On `status == 1`, `data` is empty.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                         |
| ---------- | ----------------------------------------------- |
| `6401`     | Sender has no pre-existing Core master account. |
| `6402`     | `accountIndex` is not controlled by sender.     |
| `6403`     | `accountIndex` does not exist.                  |
| `6404`     | `accountIndex` is a staking pool.               |
| `9999`     | Write was pending at Desert Mode activation.    |

#### Create order

Call [`EvmToCoreEntrypoint.createOrder`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) to place an order for the selected Core account. The sender must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account or one of its subaccounts.

**Call**

```solidity
function createOrder(
    uint48 accountIndex,
    uint16 marketIndex,
    uint48 clientOrderIndex,
    uint8 orderType,
    uint8 timeInForce,
    uint64 orderExpiry,
    uint48 amount,
    uint32 price,
    bool isAsk,
    bool reduceOnly,
    uint32 triggerPrice
) external returns (uint64 reqId);
```

**Parameters**

| Type     | Parameter          | Description                                                                                                                                                                                                           |
| -------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uint48` | `accountIndex`     | Core account placing the order.                                                                                                                                                                                       |
| `uint16` | `marketIndex`      | Market identifier. Perpetuals: `0 <= marketIndex < 255`; spot: `2048 <= marketIndex < 4095`.                                                                                                                          |
| `uint48` | `clientOrderIndex` | Optional account-scoped order identifier; `0` omits it.                                                                                                                                                               |
| `uint8`  | `orderType`        | `0`: limit; `1`: market; `2`: stop-loss market; `3`: stop-loss limit; `4`: take-profit market; `5`: take-profit limit; `6`: time-weighted average price (TWAP). Stop-loss and take-profit orders are perpetuals-only. |
| `uint8`  | `timeInForce`      | `0`: immediate-or-cancel; `1`: good-til-time; `2`: post-only. Market, stop-loss market and take-profit market orders must use `0`; TWAP orders must use `1`.                                                          |
| `uint64` | `orderExpiry`      | Expiration time as a Unix timestamp in milliseconds. Market orders and immediate-or-cancel limit orders must use `0`. All other orders need an expiry between 5 minutes and 30 days after the Core block time.        |
| `uint48` | `amount`           | Order size multiplied by `10^supported_size_decimals`. Immediate-or-cancel and TWAP orders are exempt from the market's minimum size and quote value.                                                                 |
| `uint32` | `price`            | Price in the quote asset multiplied by `10^supported_price_decimals`.                                                                                                                                                 |
| `bool`   | `isAsk`            | `true` for an ask (sell); `false` for a bid (buy).                                                                                                                                                                    |
| `bool`   | `reduceOnly`       | Whether the order may only reduce position size.                                                                                                                                                                      |
| `uint32` | `triggerPrice`     | Trigger price for a stop-loss or take-profit order, using the same scale as `price`. Must be `0` for every other order type.                                                                                          |

Read both decimal counts from the selected market in Core's `GET /api/v1/orderBookDetails` response, which lists spot markets under `spot_order_book_details`. They are market-specific and may differ from the asset's transfer decimals. The scaled values must be exact integers.

**Core response**

On `status == 1`, `data` is:

```solidity
abi.encode(orderIndex)
```

| Type     | Field        | Description                     |
| -------- | ------------ | ------------------------------- |
| `uint64` | `orderIndex` | Core-assigned order identifier. |

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                                            |
| ---------- | ------------------------------------------------------------------ |
| `6501`     | `clientOrderIndex` already exists.                                 |
| `6502`     | Order `amount` or quote value is outside market bounds.            |
| `6503`     | Reduce-only order would increase exposure.                         |
| `6504`     | Order expiry is invalid.                                           |
| `6505`     | Trigger configuration is invalid for `orderType`.                  |
| `6506`     | Sender has no pre-existing Core master account.                    |
| `6507`     | `accountIndex` is not controlled by sender.                        |
| `6508`     | `marketIndex` is invalid or inactive.                              |
| `6509`     | Order configuration is unsupported by the selected market.         |
| `6512`     | Order-capacity limit reached.                                      |
| `6513`     | Account is not eligible to place the order on the selected market. |
| `6514`     | `accountIndex` does not exist.                                     |
| `9999`     | Write was pending at Desert Mode activation.                       |

Core does not fail an order for insufficient margin or balance. Like any Core order, an order that passes these checks returns `status == 1` and then goes through matching, which can cancel it, for example for insufficient margin. Core's API reports the order's status by `orderIndex`.

#### Cancel order

Call [`EvmToCoreEntrypoint.cancelOrder`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) to cancel an active order belonging to `accountIndex`. The sender must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account or one of its subaccounts.

**Call**

```solidity
function cancelOrder(uint48 accountIndex, uint64 orderIndex) external returns (uint64 reqId);
```

**Parameters**

| Type     | Parameter      | Description                                                                                                                                                    |
| -------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `uint48` | `accountIndex` | Core account whose order is cancelled.                                                                                                                         |
| `uint64` | `orderIndex`   | Core-assigned `orderIndex` or client-supplied `clientOrderIndex`. Values below `2^48` are read as a `clientOrderIndex`; Core-assigned indices start at `2^48`. |

**Core response**

On `status == 1`, `data` is empty.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                                              |
| ---------- | -------------------------------------------------------------------- |
| `6601`     | `orderIndex` does not resolve to an active order for `accountIndex`. |
| `6602`     | Sender has no pre-existing Core master account.                      |
| `6603`     | `accountIndex` is not controlled by sender.                          |
| `6604`     | The selected order's market does not permit cancellation.            |
| `6605`     | `accountIndex` does not exist.                                       |
| `9999`     | Write was pending at Desert Mode activation.                         |

#### Cancel all orders

Call [`EvmToCoreEntrypoint.cancelAllOrders`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) to select all open orders on `accountIndex` for cancellation. The sender must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account or one of its subaccounts.

**Call**

```solidity
function cancelAllOrders(uint48 accountIndex) external returns (uint64 reqId);
```

**Parameters**

| Type     | Parameter      | Description                              |
| -------- | -------------- | ---------------------------------------- |
| `uint48` | `accountIndex` | Core account whose orders are cancelled. |

**Core response**

On `status == 1`, `data` is:

```solidity
abi.encode(ordersToCancel)
```

| Type     | Field            | Description                                                                    |
| -------- | ---------------- | ------------------------------------------------------------------------------ |
| `uint64` | `ordersToCancel` | Number of open orders selected for cancellation when Core accepts the request. |

The request succeeds even if there are no open orders, returning `ordersToCancel == 0` with `status == 1`.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                         |
| ---------- | ----------------------------------------------- |
| `6701`     | Sender has no pre-existing Core master account. |
| `6702`     | `accountIndex` is not controlled by sender.     |
| `6704`     | `accountIndex` does not exist.                  |
| `9999`     | Write was pending at Desert Mode activation.    |

#### Send to Core

[`EvmToCoreEntrypoint.registerSendToCore(...)`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) submits a request to credit a Core master account. Only [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol) can call it, after handling the tokens on EVM. See [EVM-to-Core transfers](/evm/interop/tokens/token-transfers/evm-and-core.md#evm-to-core) for the user-facing flow.

**Call**

```solidity
function registerSendToCore(
    address sender,
    uint32 assetIndex,
    address to,
    bool isL1Account,
    RouteType routeType,
    uint64 baseAmount
) external returns (uint64 reqId);
```

**Parameters**

| Type                                                         | Parameter     | Description                                                                            |
| ------------------------------------------------------------ | ------------- | -------------------------------------------------------------------------------------- |
| `address`                                                    | `sender`      | Original EVM caller, recorded as the message sender.                                   |
| `uint32`                                                     | `assetIndex`  | Asset identifier.                                                                      |
| `address`                                                    | `to`          | Controlling address of the recipient Core master account.                              |
| `bool`                                                       | `isL1Account` | `true` for an L1-controlled Core account; `false` for an EVM-controlled Core account.  |
| [`RouteType`](/evm/interop/tokens/token-transfers.md#routes) | `routeType`   | Destination balance: `Perps = 0` or `Spot = 1`.                                        |
| `uint64`                                                     | `baseAmount`  | Requested credit in [Core base units](/evm/interop/tokens/token-transfers.md#amounts). |

**Core response**

On `status == 1`, `data` is:

```solidity
abi.encode(acceptedAmount)
```

| Type     | Field            | Description                                                                                                                    |
| -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `uint64` | `acceptedAmount` | Amount credited on Core, in [base units](/evm/interop/tokens/token-transfers.md#amounts): `0 <= acceptedAmount <= baseAmount`. |

A successful status (`status == 1`) does not guarantee that the full deposit was credited. Check `acceptedAmount`, which may be zero. Any unaccepted amount is refunded separately; see [Send to Core Refunds](/evm/interop/tokens/token-transfers/evm-and-core.md#send-to-core-refunds).

This write never returns `status == 2`.

#### Finalize EVM link

[`EvmToCoreEntrypoint.finalizeEvmLink(...)`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) notifies Core of the canonical EVM token for an EVM-native asset. Only [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol) can call it, and the request records that contract as its sender. See [Linking the canonical token](/evm/interop/tokens/token-linking.md#linking-the-canonical-token) for the full flow.

**Call**

```solidity
function finalizeEvmLink(uint32 assetIndex, address evmToken) external;
```

**Parameters**

| Type      | Parameter    | Description                                    |
| --------- | ------------ | ---------------------------------------------- |
| `uint32`  | `assetIndex` | Index of the EVM-native Core asset.            |
| `address` | `evmToken`   | Canonical EVM token address to record on Core. |

**Core response**

On `status == 1`, `data` is empty. Core records the token address and creates a confirmation message to L1. A successful Core status does not mean that the confirmation has been executed on L1.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                      |
| ---------- | -------------------------------------------- |
| `9999`     | Write was pending at Desert Mode activation. |

#### EVM-initiated send to EVM

[`EvmToCoreEntrypoint.registerSendToEvm(...)`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) submits a request to debit a Core account and schedule delivery to an EVM recipient. Only [`EvmInteropManager`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/interop/EvmInteropManager.sol) can call it. Core checks account authorization against the supplied `sender`, not the calling contract. See [EVM-initiated transfers to EVM](/evm/interop/tokens/token-transfers/evm-and-core.md#evm-initiated-send-to-evm) for the user-facing flow.

**Call**

```solidity
function registerSendToEvm(
    address sender,
    uint48 accountIndex,
    uint32 assetIndex,
    address evmRecipient,
    RouteType routeType,
    uint64 baseAmount
) external returns (uint64 reqId);
```

**Parameters**

| Type                                                         | Parameter      | Description                                                                          |
| ------------------------------------------------------------ | -------------- | ------------------------------------------------------------------------------------ |
| `address`                                                    | `sender`       | Original EVM caller, recorded as the message sender and used for Core authorization. |
| `uint48`                                                     | `accountIndex` | Core account to debit.                                                               |
| `uint32`                                                     | `assetIndex`   | Asset identifier.                                                                    |
| `address`                                                    | `evmRecipient` | Recipient address on EVM.                                                            |
| [`RouteType`](/evm/interop/tokens/token-transfers.md#routes) | `routeType`    | Source balance: `Perps = 0` or `Spot = 1`.                                           |
| `uint64`                                                     | `baseAmount`   | Amount to transfer in [base units](/evm/interop/tokens/token-transfers.md#amounts).  |

**Core response**

On `status == 1`, `data` is empty.

When Core accepts the request, it debits the account's balance for the selected asset and route, then creates a Core-to-EVM write for delivery to `evmRecipient`. A successful Core status does not confirm delivery on EVM.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                         |
| ---------- | ----------------------------------------------- |
| `7001`     | Sender has no pre-existing Core master account. |
| `7002`     | `accountIndex` is not controlled by sender.     |
| `7003`     | Account has insufficient available balance.     |
| `7004`     | The selected account's perps route is disabled. |
| `7005`     | `baseAmount` exceeds Core's supported range.    |
| `7006`     | `accountIndex` does not exist.                  |
| `7007`     | `assetIndex` is not linked on EVM.              |
| `9999`     | Write was pending at Desert Mode activation.    |

#### Burn shares

Call [`EvmToCoreEntrypoint.burnShares`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) to burn public-pool shares held by `accountIndex`. The sender must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account or one of its subaccounts.

**Call**

```solidity
function burnShares(
    uint48 accountIndex,
    uint48 publicPoolIndex,
    uint64 shareAmount
) external returns (uint64 reqId);
```

**Parameters**

| Type     | Parameter         | Description                                  |
| -------- | ----------------- | -------------------------------------------- |
| `uint48` | `accountIndex`    | Core account whose shares are burned.        |
| `uint48` | `publicPoolIndex` | Public-pool account whose shares are burned. |
| `uint64` | `shareAmount`     | Amount of shares to burn.                    |

**Core response**

On `status == 1`, `data` is empty.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                         |
| ---------- | ----------------------------------------------- |
| `7101`     | Sender has no pre-existing Core master account. |
| `7102`     | `accountIndex` is not controlled by sender.     |
| `7103`     | `publicPoolIndex` is invalid.                   |
| `7105`     | Account has insufficient available shares.      |
| `7106`     | Pool is not currently burnable.                 |
| `7107`     | Burn output is invalid for the pool state.      |
| `7108`     | `accountIndex` does not exist.                  |
| `9999`     | Write was pending at Desert Mode activation.    |

#### EVM-initiated withdrawal to L1

Call [`EvmToCoreEntrypoint.withdrawToL1Address`](https://github.com/elliottech/lighter-contracts/blob/lighter-evm-devnet/contracts/messaging/EvmToCoreEntrypoint.sol) to withdraw from a Core account to L1. The sender must control an [EVM-controlled Core master account](/evm/interop/accounts.md#evm-controlled-accounts), and `accountIndex` must identify that master account or one of its subaccounts. The asset must be L1-native or an EVM-native asset with a finalized L1 wrapper link.

**Call**

```solidity
function withdrawToL1Address(
    uint48 accountIndex,
    uint32 assetIndex,
    address l1Recipient,
    RouteType routeType,
    uint64 baseAmount
) external returns (uint64 reqId);
```

**Parameters**

| Type                                                         | Parameter      | Description                                                                                          |
| ------------------------------------------------------------ | -------------- | ---------------------------------------------------------------------------------------------------- |
| `uint48`                                                     | `accountIndex` | Core account to debit.                                                                               |
| `uint32`                                                     | `assetIndex`   | Asset identifier; must have an active L1 representation.                                             |
| `address`                                                    | `l1Recipient`  | Recipient address on L1; must not be `address(0)`.                                                   |
| [`RouteType`](/evm/interop/tokens/token-transfers.md#routes) | `routeType`    | Source balance: `Perps = 0` or `Spot = 1`.                                                           |
| `uint64`                                                     | `baseAmount`   | Amount to withdraw in [base units](/evm/interop/tokens/token-transfers.md#amounts); must be nonzero. |

**Core response**

On `status == 1`, `data` is empty.

When Core accepts the request, it debits the account's balance for the selected asset and route, then creates the withdrawal to `l1Recipient`. A successful Core status does not confirm execution on L1; execution occurs in a separate step. Core charges no withdrawal fee for this request.

On `status == 2`, `data` is:

```solidity
abi.encode(uint32(errorCode))
```

| Error code | Meaning                                               |
| ---------- | ----------------------------------------------------- |
| `7601`     | Sender has no pre-existing Core master account.       |
| `7602`     | `accountIndex` is not controlled by sender.           |
| `7603`     | Asset has no active L1 representation.                |
| `7604`     | `baseAmount` is below the asset's minimum withdrawal. |
| `7605`     | Account has insufficient available balance.           |
| `7606`     | The selected account's perps route is disabled.       |
| `7608`     | `accountIndex` does not exist.                        |
| `9999`     | Write was pending at Desert Mode activation.          |

## Core to EVM

Currently, the only supported Core to EVM message is token transfers. See [Core-to-EVM transfers](/evm/interop/tokens/token-transfers/evm-and-core.md#core-to-evm) for more details.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.lighter.xyz/evm/interop/message-passing/evm-and-core.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
