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.
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(address _l1CrossDomainMessenger);Parameters:
_l1CrossDomainMessenger (address): Address of the L1CrossDomainMessenger contractcontract 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);
}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
);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 L1Returns 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...
}mapping(bytes32 => bool) public relayedMessages;
mapping(bytes32 => bool) public successfulMessages;
mapping(bytes32 => bool) public sentMessages;
uint256 public messageNonce;
address public l1CrossDomainMessenger;Contract Path: L2/messaging/L2StandardBridge.sol
The L2 standard bridge for withdrawing ETH and ERC20 tokens from L2 to L1.
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;
}Initializes the bridge with the L1 bridge address.
Parameters:
_l1TokenBridge (address): Address of the L1StandardBridge contractWithdraws 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 L1Usage:
// Withdraw ETH (using OVM_ETH address)
bridge.withdraw(
Lib_PredeployAddresses.OVM_ETH,
1 ether,
200000,
""
);
// Withdraw ERC20 tokens
bridge.withdraw(
l2TokenAddress,
tokenAmount,
200000,
""
);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 L1Finalizes 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 L1event 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
);Contract Path: L2/messaging/L2StandardTokenFactory.sol
Factory contract for creating standard L2 ERC20 tokens that are compatible with the bridging system.
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);
}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 tokenReturns: address - Address of the created L2 token
Usage:
// Create L2 token for L1 USDC
address l2Token = factory.createStandardL2Token(
l1UsdcAddress,
"USD Coin (Optimism)",
"USDC"
);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 tokenReturns: address - Address of the created L2 token
event StandardL2TokenCreated(
address indexed _l1Token,
address indexed _l2Token
);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
}
}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
""
);
}
}// 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
}
}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);
}
}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;
}
}