CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-eth-optimism--contracts

Smart contracts for Optimism Layer 2 scaling solution including L1/L2 messaging, bridging, rollup management, and predeploy contracts

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

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;
  }
}

docs

index.md

l1-messaging-bridging.md

l1-rollup-management.md

l2-messaging-bridging.md

l2-system-contracts.md

standards-interfaces.md

typescript-utilities.md

utility-libraries.md

tile.json