Skip to main content

Nethereum.Optimism

NuGet: Nethereum.Optimism | Source: src/Nethereum.Optimism/

Nethereum.Optimism

Contract services for Optimism L2 bridges and cross-domain messaging. Provides typed interfaces for depositing/withdrawing ETH and ERC20 tokens between Ethereum (L1) and Optimism (L2).

Overview

Nethereum.Optimism provides typed contract services for Optimism's L1 and L2 smart contracts. Use these services to interact with the Standard Bridge for token deposits/withdrawals and Cross Domain Messengers for arbitrary message passing between layers.

Contract Services:

  • L1StandardBridge - L1 contract for depositing ETH and ERC20 to L2
  • L2StandardBridge - L2 contract for withdrawing tokens back to L1
  • L1CrossDomainMessenger - L1 contract for cross-layer messaging
  • L2CrossDomainMessenger - L2 contract for cross-layer messaging
  • L2StandardERC20 - L2 ERC20 token paired with L1 token
  • L2StandardTokenFactory - Factory for creating L2 standard tokens
  • CrossMessagingWatcherService - Watch for cross-layer message relay events

Pre-deployed Addresses: Optimism deploys standard contracts at fixed L2 addresses.

Installation

dotnet add package Nethereum.Optimism

Or via Package Manager Console:

Install-Package Nethereum.Optimism

Dependencies

Package References:

  • Nethereum.Web3

Pre-deployed Contract Addresses

Optimism L2 contracts are deployed at fixed addresses:

using Nethereum.Optimism;

// L2 pre-deployed addresses (same on all Optimism networks)
var l2Bridge = PredeployedAddresses.L2StandardBridge; // 0x4200000000000000000000000000000000000010
var l2Messenger = PredeployedAddresses.L2CrossDomainMessenger; // 0x4200000000000000000000000000000000000007
var l2TokenFactory = PredeployedAddresses.L2StandardTokenFactory; // 0x4200000000000000000000000000000000000012
var sequencerFeeVault = PredeployedAddresses.OVM_SequencerFeeVault; // 0x4200000000000000000000000000000000000011
var l2ToL1MessagePasser = PredeployedAddresses.OVM_L2ToL1MessagePasser; // 0x4200000000000000000000000000000000000000
var gasPriceOracle = PredeployedAddresses.OVM_GasPriceOracle; // 0x420000000000000000000000000000000000000F

From: src/Nethereum.Optimism/PredeployedAddresses.cs:7

Bridging ETH and ERC20 Tokens

Depositing ERC20 from L1 to L2

using Nethereum.Web3;
using Nethereum.Web3.Accounts;
using Nethereum.Optimism.L1StandardBridge;
using Nethereum.Optimism.L1StandardBridge.ContractDefinition;
using Nethereum.Optimism.L2StandardERC20;
using Nethereum.Optimism.L2StandardERC20.ContractDefinition;
using Nethereum.Hex.HexConvertors.Extensions;

// Initialize Web3 for both layers
var account = new Account("PRIVATE_KEY");
var web3L1 = new Web3(account, "https://mainnet.infura.io/v3/YOUR-PROJECT-ID");
var web3L2 = new Web3(account, "https://mainnet.optimism.io");

// Get L1 bridge address from address manager or known deployment
var l1BridgeAddress = "0x..."; // L1StandardBridge contract address
var l1StandardBridge = new L1StandardBridgeService(web3L1, l1BridgeAddress);

// Approve L1 token for bridge
var l1TokenAddress = "0x...";
var tokenService = web3L1.Eth.ERC20.GetContractService(l1TokenAddress);
await tokenService.ApproveRequestAndWaitForReceiptAsync(l1BridgeAddress, 1_000_000);

// Deposit ERC20 to L2
var l2TokenAddress = "0x..."; // L2 token address (paired with L1 token)
var depositReceipt = await l1StandardBridge.DepositERC20RequestAndWaitForReceiptAsync(
new DepositERC20Function
{
L1Token = l1TokenAddress,
L2Token = l2TokenAddress,
Amount = 1_000_000,
L2Gas = 200_000,
Data = "0x".HexToByteArray()
});

// Wait for message to be relayed to L2 (see Cross-Layer Message Watching)

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:82

Withdrawing ERC20 from L2 to L1

using Nethereum.Optimism.L2StandardBridge;
using Nethereum.Optimism.L2StandardBridge.ContractDefinition;
using Nethereum.Optimism;

// L2 bridge is at pre-deployed address
var l2StandardBridge = new L2StandardBridgeService(web3L2, PredeployedAddresses.L2StandardBridge);

// Withdraw tokens back to L1
var withdrawReceipt = await l2StandardBridge.WithdrawRequestAndWaitForReceiptAsync(
new WithdrawFunction
{
L2Token = l2TokenAddress,
Amount = 1_000_000,
L1Gas = 200_000,
Data = "0x".HexToByteArray()
});

// Withdrawal must be finalized on L1 after challenge period (7 days on mainnet)

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:101

Cross-Layer Message Watching

Watch for cross-layer messages and track their relay status.

Get Message Hashes from Transaction

using Nethereum.Optimism;

var watcher = new CrossMessagingWatcherService();

// Get message hashes from L1 deposit transaction
var messageHashes = watcher.GetMessageHashes(depositReceipt);

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:93

From: src/Nethereum.Optimism/CrossMessagingWatcherService.cs:40

Wait for Message Relay

Wait for a message to be relayed to the destination layer:

using System.Linq;

var l2MessengerAddress = PredeployedAddresses.L2CrossDomainMessenger;

// Wait for message to be relayed on L2
var relayReceipt = await watcher.GetCrossMessageMessageTransactionReceipt(
web3L2,
l2MessengerAddress,
messageHashes.First());

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:95

From: src/Nethereum.Optimism/CrossMessagingWatcherService.cs:60

Deploying L2 Standard ERC20

Create an L2 token paired with an existing L1 token:

using Nethereum.Optimism.L2StandardERC20;
using Nethereum.Optimism.L2StandardERC20.ContractDefinition;
using Nethereum.Optimism;

var l2TokenDeployment = new L2StandardERC20Deployment
{
L1Token = l1TokenAddress,
L2Bridge = PredeployedAddresses.L2StandardBridge,
Name = "My Token",
Symbol = "MTK"
};

var l2TokenReceipt = await L2StandardERC20Service.DeployContractAndWaitForReceiptAsync(
web3L2,
l2TokenDeployment);

var l2TokenAddress = l2TokenReceipt.ContractAddress;

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:64

Address Manager

Get L1 contract addresses from Optimism's address manager:

using Nethereum.Optimism.Lib_AddressManager;
using Nethereum.Optimism;

var addressManagerAddress = "0x..."; // Optimism address manager contract
var addressManager = new Lib_AddressManagerService(web3L1, addressManagerAddress);

// Get L1 contract addresses
var l1BridgeAddress = await addressManager.GetAddressQueryAsync(StandardAddressManagerKeys.L1StandardBridge);
var l1MessengerAddress = await addressManager.GetAddressQueryAsync(StandardAddressManagerKeys.L1CrossDomainMessenger);

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:42

Standard Keys:

// From StandardAddressManagerKeys class
public const string L1CrossDomainMessenger = "L1CrossDomainMessenger";
public const string L1StandardBridge = "L1StandardBridge";

From: src/Nethereum.Optimism/StandardAddressManagerKeys.cs

Contract Services

L1StandardBridgeService

Deposit ETH and ERC20 tokens from L1 to L2.

Methods:

  • DepositERC20RequestAsync(l1Token, l2Token, amount, l2Gas, data) - Deposit ERC20 to L2
  • DepositERC20ToRequestAsync(l1Token, l2Token, to, amount, l2Gas, data) - Deposit ERC20 to specific address
  • DepositETHRequestAsync(l2Gas, data) - Deposit ETH to L2
  • DepositETHToRequestAsync(to, l2Gas, data) - Deposit ETH to specific address

From: src/Nethereum.Optimism/L1StandardBridge/L1StandardBridgeService.cs:45

L2StandardBridgeService

Withdraw tokens from L2 back to L1.

Methods:

  • WithdrawRequestAsync(l2Token, amount, l1Gas, data) - Withdraw ERC20 to L1
  • WithdrawToRequestAsync(l2Token, to, amount, l1Gas, data) - Withdraw ERC20 to specific L1 address
  • WithdrawETHRequestAsync(amount, l1Gas, data) - Withdraw ETH to L1
  • WithdrawETHToRequestAsync(to, amount, l1Gas, data) - Withdraw ETH to specific L1 address

From: src/Nethereum.Optimism/L2StandardBridge/L2StandardBridgeService.cs

L1CrossDomainMessengerService

Send arbitrary messages from L1 to L2.

Methods:

  • SendMessageRequestAsync(target, message, gasLimit) - Send message to L2 contract
  • RelayMessageRequestAsync(target, sender, message, messageNonce) - Relay message received from L2

From: src/Nethereum.Optimism/L1CrossDomainMessenger/L1CrossDomainMessengerService.cs

L2CrossDomainMessengerService

Send arbitrary messages from L2 to L1.

Methods:

  • SendMessageRequestAsync(target, message, gasLimit) - Send message to L1 contract
  • RelayMessageRequestAsync(target, sender, message, messageNonce, proof) - Relay message from L1 (requires Merkle proof)

From: src/Nethereum.Optimism/L2CrossDomainMessenger/L2CrossDomainMessengerService.cs

L2StandardERC20Service

Interact with L2 ERC20 tokens (standard ERC20 methods plus L2-specific methods).

L2-Specific Methods:

  • L1TokenQueryAsync() - Get paired L1 token address
  • L2BridgeQueryAsync() - Get L2 bridge address
  • BurnRequestAsync(amount) - Burn tokens (called by bridge during withdrawal)
  • MintRequestAsync(to, amount) - Mint tokens (called by bridge during deposit)

From: src/Nethereum.Optimism/L2StandardERC20/L2StandardERC20Service.cs

L2StandardTokenFactoryService

Create new L2 standard ERC20 tokens.

Methods:

  • CreateStandardL2TokenRequestAsync(l1Token, name, symbol) - Create L2 token for L1 token

From: src/Nethereum.Optimism/L2StandardTokenFactory/L2StandardTokenFactoryService.cs

CrossMessagingWatcherService

Watch for cross-layer message events and track relay status.

Methods:

  • GetMessageHashes(receipt) - Extract message hashes from transaction receipt
  • GetCrossMessageMessageTransactionReceipt(web3, messengerAddress, msgHash, token, numberOfPastBlocks) - Wait for message relay transaction

From: src/Nethereum.Optimism/CrossMessagingWatcherService.cs:19

Complete Example

using Nethereum.Web3;
using Nethereum.Web3.Accounts;
using Nethereum.Optimism;
using Nethereum.Optimism.L1StandardBridge;
using Nethereum.Optimism.L1StandardBridge.ContractDefinition;
using Nethereum.Optimism.L2StandardBridge;
using Nethereum.Optimism.L2StandardERC20;
using Nethereum.Optimism.L2StandardERC20.ContractDefinition;
using Nethereum.Hex.HexConvertors.Extensions;
using System.Linq;

var account = new Account("PRIVATE_KEY");
var web3L1 = new Web3(account, "https://mainnet.infura.io/v3/YOUR-PROJECT-ID");
var web3L2 = new Web3(account, "https://mainnet.optimism.io");

var watcher = new CrossMessagingWatcherService();

// Get L1 bridge address (from address manager or known deployment)
var l1BridgeAddress = "0x...";
var l1Bridge = new L1StandardBridgeService(web3L1, l1BridgeAddress);

// Create L2 token
var l2TokenDeployment = new L2StandardERC20Deployment
{
L1Token = l1TokenAddress,
L2Bridge = PredeployedAddresses.L2StandardBridge,
Name = "My Token",
Symbol = "MTK"
};

var l2TokenReceipt = await L2StandardERC20Service.DeployContractAndWaitForReceiptAsync(web3L2, l2TokenDeployment);

// Approve and deposit
var tokenService = web3L1.Eth.ERC20.GetContractService(l1TokenAddress);
await tokenService.ApproveRequestAndWaitForReceiptAsync(l1BridgeAddress, 1_000_000);

var depositReceipt = await l1Bridge.DepositERC20RequestAndWaitForReceiptAsync(
new DepositERC20Function
{
L1Token = l1TokenAddress,
L2Token = l2TokenReceipt.ContractAddress,
Amount = 1_000_000,
L2Gas = 200_000,
Data = "0x".HexToByteArray()
});

// Wait for L2 relay
var messageHashes = watcher.GetMessageHashes(depositReceipt);
var relayReceipt = await watcher.GetCrossMessageMessageTransactionReceipt(
web3L2,
PredeployedAddresses.L2CrossDomainMessenger,
messageHashes.First());

// Check L2 balance
var l2TokenService = new L2StandardERC20Service(web3L2, l2TokenReceipt.ContractAddress);
var balance = await l2TokenService.BalanceOfQueryAsync(account.Address);

From: tests/Nethereum.Optimism.Testing/ERC20_L1_to_L2_Deposit_and_Withdraw.cs:31

Important Notes

  • Withdrawal Challenge Period: L2 to L1 withdrawals have a 7-day challenge period on mainnet before they can be finalized
  • Gas Limits: Set appropriate L2Gas/L1Gas parameters for cross-layer transactions (typical values: 200,000 - 2,000,000)
  • Message Relay: Deposits (L1→L2) are automatically relayed; withdrawals (L2→L1) require claiming after challenge period
  • L2 Token Deployment: L2 tokens must be paired with L1 tokens and use the L2StandardBridge as the bridge address
  • Nethereum.Web3 - Base Web3 implementation
  • Nethereum.Contracts - Contract interaction infrastructure
  • Nethereum.Contracts.Standards.ERC20 - ERC20 token standard

Additional Resources