Token Linking

📘

Note

This page is for governance and token admins who register and link assets.

Asset registration requires governance action. To get an asset registered on the devnet, reach out to the Lighter team.

Lighter supports assets whose native domain is Ethereum L1, Lighter EVM, or Lighter Core. Token linking connects tokens on their native domain to tokens on their non-native domains.

Canonical domainL1 representationEVM representationCore representation
L1Canonical ERC-20Wrapped ERC-20Wrapped Core balance
EVMWrapped ERC-20Canonical ERC-20Wrapped Core balance
CoreNoneNoneNative Core asset

Token linking is not supported for Core-native assets, which have no representation on L1 or Lighter EVM. The rest of this page therefore covers L1-native and EVM-native ERC-20 assets.

ETH is linked across all three domains by default and requires no ERC-20 linking flow.

Flow at a glance

L1-native asset

  1. Register. Governance registers the asset on L1, then on Core. See L1-native assets.
  2. Add the EVM wrapper (optional). The Core token admin initiates wrapper setup from L1. EVM deploys or verifies the wrapper. See Select the EVM wrapper.

EVM-native asset

  1. Register. Governance registers the asset on L1, then on Core. See EVM-native assets.
  2. Prove control. Set the canonical EVM token's admin slot to the Core token admin's address, then send its token-info report from EVM and execute it on L1 before linking. See Proving control of an EVM token.
  3. Link the canonical token. The Core token admin initiates linking from L1. Wait for EVM and Core to record the token, then execute Core's confirmation on L1. See Linking the canonical token.
  4. Add the L1 wrapper (optional). The Core token admin initiates wrapper setup from L1, which deploys or verifies the L1 wrapper. See Select the L1 wrapper.

Linking makes an asset transferable. Trading it on Core also needs a market, which governance creates separately.

Roles

There are two privileged roles that manage a token's linking lifecycle through the L1InteropManager contract on L1.

  • Governance registers assets on Core and appoints their Core token admins. Governance also approves control of custom L1 wrapper tokens, see Proving control of an L1 token for details.
  • The Core token admin (coreTokenAdmin) is an L1 address that governance records for each asset at registration. It is the only address that can initiate token linking and wrapper setup for that asset. Governance can change the Core token admin after registration.

Proving control of tokens

Before a coreTokenAdmin may associate a Core asset with an independently deployed token contract, it must prove control of that contract. This prevents a malicious Core token admin from claiming a token it does not own and thereby blocking the token's true owner from later linking it to the correct Core asset.

How control is proved depends on whether the token contract is on L1 or Lighter EVM.

Proving control of an EVM token

On Lighter EVM, a token contract signals its controller by storing the controller's address in a protocol-defined storage slot.

The EvmInteropManager can then send the slot value, together with the token's ERC-20 metadata, to L1InteropManager, which coordinates token-linking authorization and management on L1. When the token is proposed for linking, the reported controller is compared with the Core asset's coreTokenAdmin. The values must match before the link is authorized.

The detailed execution steps are described below.

Step 1. Set the token's admin slot to the asset's coreTokenAdmin address. The admin slot is:

// keccak256("lighter.admin.slot")
bytes32 constant TOKEN_ADMIN_SLOT =
    0x4d2e8bd502eb517f6a9601bbafcec9c20dec0101dc1413f43ae2900ce65ed098;

Store the address in the low 20 bytes of the slot. The upper 12 bytes must be zero. Before the asset is registered, use the address governance will appoint as coreTokenAdmin.

Step 2. On Lighter EVM, call EvmInteropManager.sendTokenInfo(evmToken), where evmToken is the address of the EVM token being linked. Anyone can make this call.

function sendTokenInfo(address evmToken) external;

The token-info report includes the token's admin slot value at the end of the reporting EVM block and its ERC-20 name, symbol, and decimals. If name() or symbol() is missing, the report uses an empty string for that field. If decimals() is missing, it uses 18. Each report is independent, and reports do not have to execute on L1 in EVM submission order. Executing a report overwrites the admin and metadata stored by an earlier report.

Step 3. Execute the resulting token-info report on L1. This records the token's admin address and metadata in L1InteropManager.

Step 4. Verify the recorded admin by calling L1InteropManager.evmApprovedTokenAdmin(evmToken) on L1. It must return the asset's coreTokenAdmin address.

EVM token proof of control, with a permissionless caller reporting through EvmInteropManager and EvmToL1Messenger, followed by separate execution on L1 through ZkLighter.

Proving control of an L1 token

Unlike on Lighter EVM, the protocol cannot directly inspect an arbitrary L1 token's storage to verify who controls it. Governance therefore acts as the trusted attestor and records the address authorized to link each L1 token contract.

Step 1. Request that governance call L1InteropManager.setL1ApprovedTokenAdmin(l1Token, coreTokenAdmin) on L1.

function setL1ApprovedTokenAdmin(address token, address admin) external;

Only governance can make this call. Pass the L1 token address as token and the asset's coreTokenAdmin address as admin.

Step 2. Query L1InteropManager.l1ApprovedTokenAdmin(l1Token) on L1. It must return the asset's coreTokenAdmin address.

Governance calls L1InteropManager.setL1ApprovedTokenAdmin to record the approved admin for an L1 token.

Registering on Core

Registering an asset on Core enables transfers between Core and the asset's canonical domain. For an EVM-native asset, the canonical EVM token must also be linked before those transfers become available.

Governance registers an asset in two calls on L1InteropManager:

  1. Store the L1 configuration. This call records:
    • uint32 assetIndex, the asset identifier.
    • address coreTokenAdmin, the Core token admin's address.
    • uint128 tickSize, how many of the token's smallest EVM units make up one Core base unit; see Amounts.
    • uint56 extensionMultiplier, Lighter's internal asset extension multiplier.
    • For an L1-native asset, the canonical L1 token address.
  2. Create the asset on Core. This submits a priority request that creates the asset on Core with its Core-side parameters, such as symbol, minimum transfer and withdrawal amounts, and margin mode.

Core registration is a cross-domain request and can be retried if it fails. Linking cannot complete until Core has registered the asset.

L1-native assets

Governance calls registerL1NativeAssetConfig, then registerL1NativeAssetOnCore. Registration records the canonical L1 token address, so transfers between L1 and Core are available as soon as Core registration succeeds. No further linking is needed for that path.

L1-native asset registration from governance through L1InteropManager, L1CoreMessaging, and ZkLighter to asynchronous processing by the Core sequencer.

EVM-native assets

Governance calls registerEvmNativeAssetConfig, then registerEvmNativeAssetOnCore. These calls create the asset on Core but do not associate it with an EVM token. To complete EVM-native registration, the Core token admin links the canonical token and waits for Core to record it.

EVM-native asset registration from governance through the Core priority queue, creating a Core asset whose canonical EVM token still needs to be linked.

Linking the canonical token

Step 1. Complete the governance calls for EVM-native registration.

Step 2. Ensure you have proved control of the EVM token, including delivery of its token-info report to L1.

Step 3. On L1, call L1InteropManager.linkEvmNativeToken as coreTokenAdmin.

function linkEvmNativeToken(
    uint32 assetIndex,
    address evmToken,
    uint256 gasLimit
) external payable;
TypeParameterDescription
uint32assetIndexIndex of the registered EVM-native Core asset.
addressevmTokenAddress of the canonical EVM token whose approved admin matches coreTokenAdmin.
uint256gasLimitGas limit for the EVM token-linking transaction.

Step 4. Wait for EVM to record the token and notify Core. Query EvmInteropManager.getAssetConfig(assetIndex) and confirm that its evmToken field matches the canonical EVM token address.

Step 5. Wait for Core to record the token and create its confirmation call to L1. Query Core's EVM token address and confirm that it equals evmToken.

Step 6. Execute the Core-to-L1 confirmation. Confirm that L1InteropManager.evmNativeLinkedToken(assetIndex) equals evmToken before adding an L1 wrapper.

Canonical EVM token linking with separate asynchronous boundaries for L1 messaging and settlement and the EVM-to-Core write stream, followed by execution of the Core confirmation on L1.

Enabling the external wrapper — gateway selection

The final (optional) step in the token linking life cycle enables the wrapper on the external domain opposite the canonical token: EVM for an L1-native asset, or L1 for an EVM-native asset. This process is called gateway selection.

Gateway selection chooses the external wrapper token and two gateway contracts, one on the canonical domain and one on the wrapped-token domain. The contract on the canonical domain takes custody of the underlying tokens, while the contract on the other domain manages the selected wrapper. For example, an EVM-native asset uses an escrow contract on Lighter EVM, called the escrow gateway, and a minting contract on L1, called the mint gateway. These counterpart contracts form a gateway pair and are selected together.

L1InteropManager and EvmInteropManager do not manage the wrapper token directly. They operate it through the selected gateways using standard interfaces. The mint gateway holds whatever permissions the wrapper requires for minting and burning, rather than granting those permissions to L1InteropManager or EvmInteropManager.

The gateways may also use the cross-domain messaging system to support transfers directly between L1 and Lighter EVM, independently of Core. Notably, the gateways (and therefore that bridge) can exist before the token is registered on Core and before L1InteropManager and/or EvmInteropManager become aware of it. As long as the gateways adhere to the appropriate interfaces, gateway selection can later connect the existing custody and wrapper system to the Core asset.

The default gateways provide an example of a gateway pair that enables this direct L1–EVM bridge.

Transfers between Core and the canonical domain do not require gateways. They become available once the registration process is complete.

After registration, but before gateway selection, deposits from the canonical domain into Core are escrowed by L1InteropManager for L1-native assets or EvmInteropManager for EVM-native assets. An independently operating L1–EVM bridge holds its escrow in its own gateway.

During gateway selection, any canonical tokens held by L1InteropManager or EvmInteropManager move to the selected escrow gateway, delegating custody of the tokens to the gateways.

Gateway requirements

Every gateway selected for Core integration must implement the interfaces below so that L1InteropManager and EvmInteropManager can interact with it. The Core token admin is responsible for selecting compatible contracts—it may use the protocol's default gateways or implement and deploy its own pair.

A gateway pair must preserve the asset's backing invariant—the canonical tokens held by the escrow gateway must cover the asset's combined representations on Core and the wrapped-token domain.

The interfaces define only the behavior required for Core integration. Gateways may provide additional functionality, such as direct transfers between L1 and Lighter EVM. Because the contracts exist independently of Core, a gateway pair may be deployed before or after the Core asset is created. Gateway selection can later connect an existing pair to the Core asset.

Gateway interfaces

The gateway on the canonical domain implements IEscrowGateway:

interface IEscrowGateway {
    function externalGateway() external view returns (address);
    function unlock(address localToken, address to, uint256 amount) external;
}
  • externalGateway() returns the address of the paired mint gateway on the other domain: EVM for an L1 escrow gateway, or L1 for an EVM escrow gateway.
  • unlock(localToken, to, amount) transfers amount of the canonical token at localToken from the gateway's escrow to the recipient to.

The gateway on the wrapped-token domain implements IMintGateway:

interface IMintGateway {
    function externalGateway() external view returns (address);
    function checkOrDeployToken(
        bytes32 salt,
        string calldata name,
        string calldata symbol,
        uint8 decimals
    ) external;
    function mint(address localToken, address to, uint256 amount, bytes calldata data) external;
    function managerBurn(address localToken, uint256 amount) external;
}
  • externalGateway() returns the address of the paired escrow gateway on the other domain: EVM for an L1 mint gateway, or L1 for an EVM mint gateway.
  • checkOrDeployToken(...) must ensure the selected wrapper exists. The supplied salt is bytes32(uint256(uint160(canonicalToken))). L1InteropManager or EvmInteropManager calls this function during gateway selection, after the canonical token has been successfully linked to the Core asset. The manager derives the salt from the canonical token in the authenticated asset configuration, so the gateway can treat it as a protocol-authenticated identifier within its canonical-domain namespace and safely use it for deterministic CREATE2 or CREATE3 wrapper deployment, if desired.
  • mint(...) mints wrapped tokens to the recipient.
  • managerBurn(...) is called by L1InteropManager on L1 or EvmInteropManager on Lighter EVM after that contract transfers the wrapped tokens directly from the user to the mint gateway. The function burns those tokens from the gateway's balance.

The gateway must allow managerBurn(...) calls from L1InteropManager on L1 or EvmInteropManager on Lighter EVM. Whether to reject calls from other addresses is up to the gateway designer.

For unlock(...) and mint(...), allow calls from L1InteropManager on L1 and EvmInteropManager on Lighter EVM.

External wrapper requirements

Before selecting a custom wrapper, establish that the coreTokenAdmin controls it. For an EVM wrapper, set the protocol-defined admin slot and deliver it to L1 by calling sendTokenInfo(...). For an L1 wrapper, governance records the approved admin.

The protocol also supplies the canonical token's name(), symbol(), and decimals() to the mint gateway through checkOrDeployToken(...). For an EVM-native token, these values are recorded by sendTokenInfo(...). For an L1-native token, L1InteropManager reads them during gateway selection. A missing name() or symbol() produces an empty string, and a missing decimals() produces 18. The wrapper should use the same decimals as the canonical token because gateway operations pass token amounts between domains without conversion.

Default gateways

The default gateways are a protocol-provided special case of gateway pairs. They implement the same gateway interfaces and satisfy the gateway requirements. Unlike a custom pair, their wrapper deployment behavior is fixed by the protocol.

Each default mint gateway uses CREATE2 to deploy a wrapper backed by a protocol-configured LighterWrappedERC20V1 implementation. The wrapper is initialized with the canonical token's name(), symbol(), and decimals() without adding a prefix or suffix. A missing name() or symbol() produces an empty string, and a missing decimals() produces 18.

If the wrapper does not yet exist, the transaction that deploys it must have enough gas for both CREATE2 and initialize(...), in addition to the rest of the operation. For deployment through an EVM priority request, include these costs in its gasLimit: a delivery that deploys a default EVM wrapper uses about 450,000 gas.

Default wrappers are upgradeable, and all wrappers deployed by a given mint gateway share the same implementation.

The deployed protocol contracts recognize the default L1 gateway addresses and EVM gateway addresses. Passing those addresses during gateway selection selects this known deployment path, allowing the protocol to derive and verify the wrapper address instead of requiring proof that the Core token admin controls it. The wrapper can therefore bypass the proof-of-control protocol.

The protocol provides the following default gateway implementations:

The default gateway pairs also support direct L1–EVM bridging. If you want users to bridge between L1 and EVM before the Core asset exists, and you intend to use the default gateways for its eventual Core integration, you can direct users to the default gateway pair immediately. The Core asset can be registered and linked to the same pair later.

For L1-to-EVM transfers, the default gateways use L1ReplayableMessenger. Failed delivery can be retried, but each message can execute successfully only once.

Selecting the gateway pair

Enable an EVM wrapper for an L1-native asset

During gateway selection, L1InteropManager reads the L1 token's metadata and passes it to the selected EVM mint gateway through checkOrDeployToken(...). Missing metadata fields use the fallbacks described above. The protocol treats this metadata as stable and does not synchronize changes to a wrapper that already exists, including one deployed before gateway selection.

Prerequisites. Complete L1-native registration. If using a custom EVM wrapper, also complete its proof of control.

Step 1. Choose the gateway pair and expectedEvmWrapperToken. With defaults, read EvmMintGateway.computeTokenAddress(l1Token) on EVM. With custom gateways, pass the appropriate wrapper token address.

Step 2. On L1, call L1InteropManager.selectL1NativeGatewayPair as coreTokenAdmin with the selected EVM mint and L1 escrow gateway addresses. For defaults, use EvmMintGateway and L1EscrowGateway at the addresses listed in Chain Info.

function selectL1NativeGatewayPair(
    uint32 assetIndex,
    address evmGateway,
    address l1Gateway,
    address expectedEvmWrapperToken,
    uint256 gasLimit
) external payable;
TypeParameterDescription
uint32assetIndexIndex of the registered L1-native Core asset.
addressevmGatewayAddress of the EVM mint gateway.
addressl1GatewayAddress of the L1 escrow gateway.
addressexpectedEvmWrapperTokenExpected EVM wrapper address; must match the default mint gateway's computed address when using defaults.
uint256gasLimitGas limit for the EVM gateway-selection transaction.

Step 3. Wait for EVM to deploy or verify the wrapper. Query EvmInteropManager.getAssetConfig(assetIndex) and confirm that its evmToken field equals expectedEvmWrapperToken.

Step 4. Execute the acknowledgment on L1. This finalizes the L1 gateway, moves any existing escrow into it, and notifies Core of the EVM wrapper.

Step 5. Wait for Core to record the wrapper. Query Core's EVM token address and confirm that it equals expectedEvmWrapperToken.

L1-native gateway selection, with externalGateway() counterpart checks on both domains, EVM wrapper deployment or verification, an acknowledgment executed on L1, transfer of existing escrow to the L1 escrow gateway, and a Core priority request to record the wrapper.

Enable an L1 wrapper for an EVM-native asset

During EVM-native registration, sendTokenInfo(...) reports the canonical EVM token's name(), symbol(), and decimals() to L1. Each executed report overwrites metadata previously stored by L1InteropManager. Gateway selection passes the stored snapshot to the selected L1 mint gateway through checkOrDeployToken(...). Later reports or metadata changes do not update an already deployed wrapper.

Prerequisites. Complete EVM-native registration. If using a custom L1 wrapper, also complete its governance approval.

Step 1. Choose the gateway pair and expectedL1WrapperToken. With defaults, read L1MintGateway.computeTokenAddress(evmToken) on L1. With custom gateways, pass the appropriate wrapper token address.

Step 2. On L1, call L1InteropManager.selectEvmNativeGatewayPair as coreTokenAdmin with the selected EVM escrow and L1 mint gateway addresses. For defaults, use EvmEscrowGateway and L1MintGateway at the addresses listed in Chain Info. This call deploys or verifies the L1 wrapper using the previously reported token metadata.

function selectEvmNativeGatewayPair(
    uint32 assetIndex,
    address evmGateway,
    address l1Gateway,
    address expectedL1WrapperToken,
    uint256 gasLimit
) external payable;
TypeParameterDescription
uint32assetIndexIndex of the EVM-native Core asset. Its canonical token link must be finalized on L1.
addressevmGatewayAddress of the EVM escrow gateway.
addressl1GatewayAddress of the L1 mint gateway.
addressexpectedL1WrapperTokenExpected L1 wrapper address; must match the default mint gateway's computed address when using defaults.
uint256gasLimitGas limit for the EVM gateway-selection transaction.

Step 3. Wait for EVM to select its escrow gateway and move any existing escrow into it. Query EvmInteropManager.getAssetConfig(assetIndex) and confirm that its evmGateway field matches the selected EVM escrow gateway address.

Step 4. Execute the acknowledgment on L1. This records the L1 gateway and wrapper and notifies Core of the L1 wrapper.

Step 5. Wait for Core to record the L1 wrapper. Query Core's L1 token address and confirm that it equals expectedL1WrapperToken.

EVM-native gateway selection, with externalGateway() counterpart checks on both domains, L1 wrapper deployment or verification, transfer of existing escrow to the EVM escrow gateway, an acknowledgment executed on L1, and a Core priority request to record the wrapper.

Did this page help you?