or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdl1-messaging-bridging.mdl1-rollup-management.mdl2-messaging-bridging.mdl2-system-contracts.mdstandards-interfaces.mdtypescript-utilities.mdutility-libraries.md
tile.json

l2-messaging-bridging.mddocs/

Layer 2 Messaging & Bridging

L2 contracts deployed on Optimism that provide the Layer 2 side of cross-domain messaging and bridging functionality, enabling message passing to L1 and token withdrawal processes.

L2CrossDomainMessenger

Contract Path: L2/messaging/L2CrossDomainMessenger.sol

The L2 side of the cross-domain messaging system that sends messages from L2 to L1 and receives messages from L1.

Constructor

constructor(address _l1CrossDomainMessenger);

Parameters:

  • _l1CrossDomainMessenger (address): Address of the L1CrossDomainMessenger contract

Key Functions

contract L2CrossDomainMessenger {
  function sendMessage(
    address _target,
    bytes calldata _message,
    uint32 _gasLimit
  ) external;
  
  function relayMessage(
    address _target,
    address _sender,
    bytes memory _message,
    uint256 _messageNonce
  ) external;
  
  function xDomainMessageSender() external view returns (address);
}

sendMessage

Sends a message from L2 to L1.

Parameters:

  • _target (address): Target contract address on L1
  • _message (bytes): Encoded message data to send
  • _gasLimit (uint32): Gas limit for L1 execution (used for fee calculation)

Usage:

// Send message from L2 to L1
messenger.sendMessage(
  l1ContractAddress,
  abi.encodeWithSignature("handleL2Message(uint256)", value),
  200000
);

relayMessage

Relays a message from L1 to L2 (called automatically by the system when L1 messages are processed).

Parameters:

  • _target (address): Target contract address on L2
  • _sender (address): Original sender address on L1
  • _message (bytes): Message data to relay
  • _messageNonce (uint256): Message nonce from L1

xDomainMessageSender

Returns the address of the cross-domain message sender when called within a cross-domain message.

Returns: address - The sender address from the other domain

Usage:

function handleCrossDomainMessage() external {
  require(
    msg.sender == address(messenger),
    "Must be called by messenger"
  );
  
  address l1Sender = messenger.xDomainMessageSender();
  require(l1Sender == trustedL1Address, "Untrusted sender");
  
  // Process message...
}

State Variables

mapping(bytes32 => bool) public relayedMessages;
mapping(bytes32 => bool) public successfulMessages;
mapping(bytes32 => bool) public sentMessages;
uint256 public messageNonce;
address public l1CrossDomainMessenger;

L2StandardBridge

Contract Path: L2/messaging/L2StandardBridge.sol

The L2 standard bridge for withdrawing ETH and ERC20 tokens from L2 to L1.

Key Functions

contract L2StandardBridge {
  function initialize(address _l1TokenBridge) external;
  
  function withdraw(
    address _l2Token,
    uint256 _amount,
    uint32 _l1Gas,
    bytes calldata _data
  ) external;
  
  function withdrawTo(
    address _l2Token,
    address _to,
    uint256 _amount,
    uint32 _l1Gas,
    bytes calldata _data
  ) external;
  
  function finalizeDeposit(
    address _l1Token,
    address _l2Token,
    address _from,
    address _to,
    uint256 _amount,
    bytes calldata _data
  ) external;
}

initialize

Initializes the bridge with the L1 bridge address.

Parameters:

  • _l1TokenBridge (address): Address of the L1StandardBridge contract

withdraw

Withdraws tokens from L2 to L1 for the sender.

Parameters:

  • _l2Token (address): L2 token contract address
  • _amount (uint256): Amount of tokens to withdraw
  • _l1Gas (uint32): Gas limit for L1 execution
  • _data (bytes): Optional data to pass to L1

Usage:

// Withdraw ETH (using OVM_ETH address)
bridge.withdraw(
  Lib_PredeployAddresses.OVM_ETH,
  1 ether,
  200000,
  ""
);

// Withdraw ERC20 tokens
bridge.withdraw(
  l2TokenAddress,
  tokenAmount,
  200000,
  ""
);

withdrawTo

Withdraws tokens from L2 to L1 for a specific recipient.

Parameters:

  • _l2Token (address): L2 token contract address
  • _to (address): Recipient address on L1
  • _amount (uint256): Amount of tokens to withdraw
  • _l1Gas (uint32): Gas limit for L1 execution
  • _data (bytes): Optional data to pass to L1

finalizeDeposit

Finalizes a deposit from L1 (called by the messenger when processing L1 deposits).

Parameters:

  • _l1Token (address): L1 token contract address
  • _l2Token (address): L2 token contract address
  • _from (address): Sender address on L1
  • _to (address): Recipient address on L2
  • _amount (uint256): Amount of tokens deposited
  • _data (bytes): Optional data from L1

Events

event WithdrawalInitiated(
  address indexed _l1Token,
  address indexed _l2Token,
  address indexed _from,
  address _to,
  uint256 _amount,
  bytes _data
);

event DepositFinalized(
  address indexed _l1Token,
  address indexed _l2Token,
  address indexed _from,
  address _to,
  uint256 _amount,
  bytes _data
);

event DepositFailed(
  address indexed _l1Token,
  address indexed _l2Token,
  address indexed _from,
  address _to,
  uint256 _amount,
  bytes _data
);

L2StandardTokenFactory

Contract Path: L2/messaging/L2StandardTokenFactory.sol

Factory contract for creating standard L2 ERC20 tokens that are compatible with the bridging system.

Key Functions

contract L2StandardTokenFactory {
  function createStandardL2Token(
    address _l1Token,
    string memory _name,
    string memory _symbol
  ) external returns (address);
  
  function createStandardL2TokenWithDecimals(
    address _l1Token,
    string memory _name,
    string memory _symbol,
    uint8 _decimals
  ) external returns (address);
}

createStandardL2Token

Creates a standard L2 token for a given L1 token.

Parameters:

  • _l1Token (address): Address of the corresponding L1 token
  • _name (string): Name of the L2 token
  • _symbol (string): Symbol of the L2 token

Returns: address - Address of the created L2 token

Usage:

// Create L2 token for L1 USDC
address l2Token = factory.createStandardL2Token(
  l1UsdcAddress,
  "USD Coin (Optimism)",
  "USDC"
);

createStandardL2TokenWithDecimals

Creates a standard L2 token with specified decimals.

Parameters:

  • _l1Token (address): Address of the corresponding L1 token
  • _name (string): Name of the L2 token
  • _symbol (string): Symbol of the L2 token
  • _decimals (uint8): Number of decimals for the token

Returns: address - Address of the created L2 token

Events

event StandardL2TokenCreated(
  address indexed _l1Token,
  address indexed _l2Token
);

Common Cross-Domain Patterns

Message Authentication

contract L2Receiver {
  IL2CrossDomainMessenger public messenger;
  address public trustedL1Sender;
  
  modifier onlyFromL1(address expectedSender) {
    require(
      msg.sender == address(messenger),
      "Must be called by messenger"
    );
    require(
      messenger.xDomainMessageSender() == expectedSender,
      "Invalid L1 sender"
    );
    _;
  }
  
  function handleL1Message(bytes calldata data) 
    external 
    onlyFromL1(trustedL1Sender) 
  {
    // Process message from L1
  }
}

Token Withdrawal Flow

contract TokenWithdrawer {
  IL2StandardBridge public bridge;
  
  function withdrawTokens(
    address l2Token,
    address l1Recipient,
    uint256 amount
  ) external {
    // Ensure user owns the tokens
    require(
      IERC20(l2Token).balanceOf(msg.sender) >= amount,
      "Insufficient balance"
    );
    
    // Approve bridge to burn tokens
    IERC20(l2Token).approve(address(bridge), amount);
    
    // Initiate withdrawal
    bridge.withdrawTo(
      l2Token,
      l1Recipient,
      amount,
      200000, // L1 gas limit
      ""
    );
  }
}

Usage Examples

Cross-Domain Communication

// L2 contract sending message to L1
contract L2Contract {
  IL2CrossDomainMessenger public messenger;
  
  function notifyL1(address l1Target, uint256 value) external {
    bytes memory message = abi.encodeWithSignature(
      "handleNotification(uint256)",
      value
    );
    
    messenger.sendMessage(l1Target, message, 200000);
  }
  
  function receiveFromL1(bytes calldata data) external {
    require(
      msg.sender == address(messenger),
      "Only messenger can call"
    );
    
    address l1Sender = messenger.xDomainMessageSender();
    // Verify l1Sender is trusted...
    
    // Process data from L1
  }
}

Complete Bridge Integration

contract L2DApp {
  IL2StandardBridge public bridge;
  IL2CrossDomainMessenger public messenger;
  address public l1Contract;
  
  function withdrawAndNotify(
    address token,
    uint256 amount,
    bytes calldata notificationData
  ) external {
    // First withdraw tokens
    bridge.withdraw(token, amount, 300000, "");
    
    // Then send notification message
    bytes memory message = abi.encodeWithSignature(
      "handleWithdrawal(address,uint256,bytes)",
      msg.sender,
      amount,
      notificationData
    );
    
    messenger.sendMessage(l1Contract, message, 200000);
  }
}

Token Factory Usage

contract TokenManager {
  IL2StandardTokenFactory public factory;
  mapping(address => address) public l1ToL2Token;
  
  function deployL2Token(
    address l1Token,
    string memory name,
    string memory symbol
  ) external returns (address) {
    require(l1ToL2Token[l1Token] == address(0), "Token already deployed");
    
    address l2Token = factory.createStandardL2Token(
      l1Token,
      name,
      symbol
    );
    
    l1ToL2Token[l1Token] = l2Token;
    return l2Token;
  }
}