## 1. Project Overview & Quickstart (ethereum-optimism/op-wheel)

# op-wheel

Open-source repository ethereum-optimism/op-wheel

### Repository Details
- **Repository:** [ethereum-optimism/op-wheel](https://github.com/ethereum-optimism/op-wheel)
- **Primary Language:** Code

*Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.*

## 2. Official Technical Reference & Guides (ethereum-optimism/docs)

## File: app-developers/guides/bridging/basics.mdx

---
title: Bridging basics
---
OP Mainnet is a "Layer 2" system and is fundamentally connected to Ethereum.
However, OP Mainnet is also a distinct blockchain with its own blocks and transactions.
App developers commonly need to move data and tokens between OP Mainnet and Ethereum.
This process of moving data and tokens between the two networks is called "bridging".

## Sending tokens

One of the most common use cases for bridging is the need to send ETH or ERC-20 tokens between OP Mainnet and Ethereum.
OP Mainnet has a system called the [Standard Bridge](./standard-bridge) that makes it easy to move tokens in both directions.
If you mostly need to bridge tokens, make sure to check out the [Standard Bridge](./standard-bridge) guide.

## Sending data

Under the hood, the Standard Bridge is just an application that uses the OP Mainnet [message passing system to send arbitrary data between Ethereum and OP Mainnet](./messaging).
Applications can use this system to have a contract on Ethereum interact with a contract on OP Mainnet, and vice versa.
All of this is easily accessible with a simple, clean API.

## Next steps

Ready to start bridging?
Check out these tutorials to get up to speed fast.

*   [Learn how to bridge ERC-20 tokens with viem](/app-developers/tutorials/bridging/cross-dom-bridge-erc20)
*   [Learn how to create a standard bridged token](/app-developers/tutorials/bridging/standard-bridge-standard-token)
*   [Learn how to create a custom bridged token](/app-developers/tutorials/bridging/standard-bridge-custom-token)
*   [Learn how to submit transactions from L1](/app-developers/tutorials/bridging/cross-dom-bridge-eth)

---

## File: app-developers/guides/bridging/custom-bridge.mdx

---
title: Custom bridges
---

Custom token bridges are any bridges other than the [Standard Bridge](./standard-bridge).
You may find yourself in a position where you need to build a custom token bridge because the Standard Bridge doesn't completely support your use case.
This guide provides important information you should be aware of when building a custom bridge.


  Custom bridges can bring a significant amount of complexity and risk to any project.
  Before you commit to a custom bridge, be sure that the [Standard Bridge](./standard-bridge) definitely does not support your use case.
  [Building a custom bridged token](/app-developers/tutorials/bridging/standard-bridge-custom-token) is often sufficient for projects that need more flexibility.


## Guidelines

Custom bridges can use any design pattern you can think of.
However, with increased complexity comes increased risk.
Consider directly extending or modifying the [`StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/87dd5a4743380b717dac44a4e55c5e6e60e32684/packages/contracts-bedrock/src/universal/StandardBridge.sol) contract before building your own bridge contracts from scratch.
Doing so will provide you with an audited foundation upon which you can add extra logic.

If you choose not to extend the `StandardBridge` contract, you may still want to follow the interface that the `StandardBridge` provides.
Bridges that extend this interface will be compatible with the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs&utm_medium=docs).
You can read more about the design of the Standard Bridge in the guide on [Using the Standard Bridge](./standard-bridge).

## The Superchain Token List

The [Superchain Token List](/app-developers/reference/tokens/tokenlist) exists to help users and developers find the right bridged representations of tokens native to another blockchain.
Once you've built and tested your custom bridge, make sure to register any tokens meant to flow through this bridge by [making a pull request against the Superchain Token List repository](https://github.com/ethereum-optimism/ethereum-optimism.github.io#adding-a-token-to-the-list).
You **must** deploy your bridge to OP Sepolia before it can be added to the Superchain Token List.

## Next steps

You can explore several examples of custom bridges for OP Mainnet:

*   [NFT Bridge](https://github.com/ethereum-optimism/optimism/blob/v1.1.4/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol)
*   [L2 DAI Token Bridge](https://optimistic.etherscan.io/address/0x467194771dae2967aef3ecbedd3bf9a310c76c65#code) and [deployed addresses](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/data/DAI/data.json)
*   [SNX Bridge](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/data/SNX/data.json)

---

## File: app-developers/guides/bridging/messaging.mdx

---
title: Sending data between L1 and L2
---

Smart contracts on L1 (Ethereum) can interact with smart contracts on L2 (OP Mainnet) through a process called "bridging".
This page explains how bridging works, how to use it, and what to watch out for.


  This is a high-level overview of the bridging process.
  For a step-by-step tutorial on how to send data between L1 and L2, check out the [Solidity tutorial](/app-developers/tutorials/bridging/cross-dom-solidity).


## Understanding contract calls

It can be easier to understand bridging if you first have a basic understanding of how contracts on EVM-based blockchains like OP Mainnet and Ethereum communicate within the *same* network.
The interface for sending messages *between* Ethereum and OP Mainnet is designed to mimic the standard contract communication interface as much as possible.

Here's how a contract on Ethereum might trigger a function within another contract on Ethereum:

```solidity
contract MyContract {
    function doTheThing(address myContractAddress, uint256 myFunctionParam) public {
        MyOtherContract(myContractAddress).doSomething(myFunctionParam);
    }
}
```

Here, `MyContract.doTheThing` triggers a call to `MyOtherContract.doSomething`.
Under the hood, Solidity is triggering the code for `MyOtherContract` by sending an [ABI encoded](https://docs.soliditylang.org/en/v0.8.23/abi-spec.html) call for the `doSomething` function.
A lot of this complexity is abstracted away to simplify the developer experience.
Solidity also has manual encoding tools that allow us to demonstrate the same process in a more verbose way.

Here's how you might manually encode the same call:

```solidity
contract MyContract {
    function doTheThing(address myContractAddress, uint256 myFunctionParam) public {
        myContractAddress.call(
            abi.encodeCall(
                MyOtherContract.doSomething,
                (
                    myFunctionParam
                )
            )
        );
    }
}
```

Here you're using the [low-level "call" function](https://docs.soliditylang.org/en/v0.8.23/units-and-global-variables.html#members-of-address-types) and one of the [ABI encoding functions built into Solidity](https://docs.soliditylang.org/en/v0.8.23/units-and-global-variables.html#abi-encoding-and-decoding-functions).
Although these two code snippets look a bit different, they're doing the exact same thing.
Because of limitations of Solidity, **the OP Stack's bridging interface is designed to look like the second code snippet**.

## Basics of communication between layers

At a high level, the process for sending data between L1 and L2 is pretty similar to the process for sending data between two contracts on Ethereum (with a few caveats).
Communication between L1 and L2 is made possible by a pair of special smart contracts called the "messenger" contracts.
Each layer has its own messenger contract, which serves to abstract away some lower-level communication details, a lot like how HTTP libraries abstract away physical network connections.

We won't get into *too* much detail about these contracts here.
The most important thing that you need to know is that each messenger contract has a `sendMessage` function that allows you to send a message to a contract on the other layer.

```solidity
function sendMessage(
    address _target,
    bytes memory _message,
    uint32 _minGasLimit
) public;
```

The `sendMessage` function has three parameters:

1.  The `address _target` of the contract to call on the other layer.
2.  The `bytes memory _message` calldata to send to the contract on the other layer.
3.  The `uint32 _minGasLimit` minimum gas limit that can be used when executing the message on the other layer.

This is basically equivalent to:

```solidity
address(_target).call{gas: _minGasLimit}(_message);
```

Except, of course, that you're calling a contract on a completely different network.

This is glossing over a lot of the technical details that make this whole thing work under the hood, but this should be enough to get you started.
Want to call a contract on OP Mainnet from a contract on Ethereum?
It's dead simple:

```solidity
// Pretend this is on L2
contract MyOptimisticContract {
    function doSomething(uint256 myFunctionParam) public {
        // ... some sort of code goes here
    }
}

// And pretend this is on L1
contract MyContract {
    function doTheThing(address myOptimisticContractAddress, uint256 myFunctionParam) public {
        messenger.sendMessage(
            myOptimisticContractAddress,
            abi.encodeCall(
                MyOptimisticContract.doSomething,
                (
                    myFunctionParam
                )
            ),
            1000000 // or use whatever gas limit you want
        )
    }
}
```


  You can find the addresses of the `L1CrossDomainMessenger` and the `L2CrossDomainMessenger` contracts on OP Mainnet and OP Sepolia on the [Contract Addresses](/op-mainnet/network-information/op-addresses) page.


## Communication speed

Unlike calls between contracts on the same blockchain, calls between Ethereum and OP Mainnet are *not* instantaneous.
The speed of a cross-chain transaction depends on the direction in which the transaction is sent.

### For L1 to L2 transactions

Transactions sent from L1 to L2 take **approximately 1-3 minutes** to get from Ethereum to OP Mainnet, or from Sepolia to OP Sepolia.
This is because the Sequencer waits for a certain number of L1 blocks to be created before including L1 to L2 transactions to avoid potentially annoying [reorgs](https://www.alchemy.com/overviews/what-is-a-reorg).

### For L2 to L1 transactions

Transactions sent from L2 to L1 take **approximately 7 days** to get from OP Mainnet to Ethereum, or from OP Sepolia to Sepolia.
This is because the bridge contract on L1 must wait for the L2 state to be *proven* to the L1 chain before it can relay the message.

The process of sending transactions from L2 to L1 involves four distinct steps:

1.  The L2 transaction that sends a message to L1 is sent to the Sequencer.
    This is just like any other L2 transaction and takes just a few seconds to be confirmed by the Sequencer.

2.  The block containing the L2 transaction is proposed to the L1.
    This typically takes approximately 20 minutes.

3.  A proof of the transaction is submitted to the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/111f3f3a3a2881899662e53e0f1b2f845b188a38/packages/contracts-bedrock/src/L1/OptimismPortal.sol#L209) contract on L1.
    This can be done any time after step 2 is complete.

4.  The transaction is finalized on L1.
    This can *only* be done after the [fault challenge period](#understanding-the-challenge-period) has elapsed.
    This period is 7 days on Ethereum and a few seconds on Sepolia.
    This waiting period is a core part of the security model of the OP Stack and cannot be circumvented.

## Accessing `msg.sender`

Contracts frequently make use of `msg.sender` to make decisions based on the calling address.
For example, many contracts will use the [Ownable](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) pattern to selectively restrict access to certain functions.
Because messages are essentially shuttled between L1 and L2 by the messenger contracts, **the `msg.sender` you'll see when receiving one of these messages will be the messenger contract** corresponding to the layer you're on.

In order to get around this, you can find a `xDomainMessageSender` function to each messenger:

```solidity
function xDomainMessageSender() public returns (address);
```

If your contract has been called by one of the messenger contracts, you can use this function to see who's *actually* sending this message.
Here's how you might implement an `onlyOwner` modifier on L2:

```solidity
modifier onlyOwner() {
    require(
        msg.sender == address(messenger)
        && messenger.xDomainMessageSender() == owner
    );
    _;
}
```

## Fees for sending data between L1 and L2

### For L1 to L2 transactions

The majority of the cost of an L1 to L2 transaction comes from the smart contract execution on L1.
When sending an L1 to L2 transaction, you send to the [`L1CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/111f3f3a3a2881899662e53e0f1b2f845b188a38/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol) contract, which then sends a call to the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/111f3f3a3a2881899662e53e0f1b2f845b188a38/packages/contracts-bedrock/src/L1/OptimismPortal.sol) contract.
This involves some execution on L1, which costs gas.
The total cost is ultimately determined by gas prices on Ethereum when you're sending the cross-chain transaction.

L1 to L2 execution also triggers contract execution on L2.
The `OptimismPortal` contract charges you for this L2 execution by burning a dynamic amount of L1 gas during your L1 to L2 transaction, depending on the gas limit you requested on L2.
The amount of L1 gas charged increases when more people are sending L1 to L2 transactions (and decreases when fewer people are sending L1 to L2 transactions).


  Since the gas amount charged is dynamic, the gas burn can change from block to block.
  You should always add a buffer of at least 20% to the gas limit for your L1 to L2 transaction to avoid running out of gas.


### For L2 to L1 transactions

Each message from L2 to L1 requires three transactions:

1.  An L2 transaction that *initiates* the transaction, which is priced the same as any other transaction made on OP Mainnet.

2.  An L1 transaction that *proves* the transaction.
    This transaction can only be submitted after the L2 block, including your L2 transaction, is proposed on L1.
    This transaction is expensive because it includes verifying a [Merkle trie](/connect/resources/glossary#merkle-patricia-trie) inclusion proof on L1.

3.  An L1 transaction that *finalizes* the transaction.
    This transaction can only be submitted after the transaction challenge period (7 days on mainnet) has passed.

The total cost of an L2 to L1 transaction is therefore the combined cost of the L2 initialization transaction and the two L1 transactions.
The L1 proof and finalization transactions are typically significantly more expensive than the L2 initialization transaction.

## Understanding the challenge period

One of the most important things to understand about L1 ⇔ L2 interaction is that **mainnet messages sent from Layer 2 to Layer 1 cannot be relayed for at least 7 days**.
This means that any messages you send from Layer 2 will only be received on Layer 1 after this one-week period has elapsed.
We call this period of time the "challenge period" because it is the time during which a transaction can be challenged with a [fault proof](/op-stack/protocol/overview#fault-proofs).

Optimistic Rollups are "optimistic" because they're based around the idea of publishing the *result* of a transaction to Ethereum without actually executing the transaction on Ethereum.
In the "optimistic" case, this transaction result is correct and one can completely avoid the need to perform complicated (and expensive) logic on Ethereum.

However, one still needs to find a way to prevent incorrect transaction results from being published in place of correct ones.
Here's where the "fault proof" comes into play.
Whenever a transaction result is published, it's considered "pending" for a period of time known as the challenge period.
During this period of time, anyone may re-execute the transaction *on Ethereum* in an attempt to demonstrate that the published result was incorrect.

If someone can prove that a transaction result is faulty, then the result is scrubbed from existence and anyone can publish another result in its place (hopefully the correct one this time, financial punishments make faulty results *very* costly for their publishers).
Once the window for a given transaction result has fully passed without a challenge, the result can be considered fully valid (or else someone would've challenged it).

Anyway, the point here is that **you don't want to be making decisions about Layer 2 transaction results from inside a smart contract on Layer 1 until this challenge period has elapsed**.
Otherwise you might be making decisions based on an invalid transaction result.
As a result, L2 ⇒ L1 messages sent using the standard messenger contracts cannot be relayed until they've waited out the full challenge period.

---

## File: app-developers/guides/bridging/standard-bridge.mdx

---
title: Using the Standard Bridge
---

The Standard Bridge is a basic token bridging system available on OP Mainnet and all other standard OP Stack chains.
The Standard Bridge allows you to easily move ETH and most ERC-20 tokens between Ethereum and OP Mainnet.
Transfers from Ethereum to OP Mainnet via the Standard Bridge are usually completed within 1-3 minutes.
Transfers from OP Mainnet to Ethereum are completed in 7 days as a result of the [withdrawal challenge period](./messaging#understanding-the-challenge-period).

The Standard Bridge is fully permissionless and supports standard ERC-20 tokens.
Other bridging systems also exist that provide different features and security properties.
You may wish to explore some of these options to find the bridge that works best for you and your application.


  The Standard Bridge **does not** support [**fee on transfer tokens**](https://github.com/d-xo/weird-erc20#fee-on-transfer) or [**rebasing tokens**](https://github.com/d-xo/weird-erc20#balance-modifications-outside-of-transfers-rebasingairdrops) because these types of tokens may cause bridge accounting errors.


## Design

The Standard Bridge allows users to convert tokens that are native to one chain (like Ethereum) into a representation of those tokens on the other chain (like OP Mainnet).
Users can then convert these bridged representations back into their original native tokens at any time.

This bridging mechanism functions identically in both directions — tokens native to OP Mainnet can be bridged to Ethereum, just like tokens native to Ethereum can be bridged to OP Mainnet.
Here you'll get to understand how the Standard Bridge works when moving tokens from Ethereum to OP Mainnet.
Since the bridging mechanism is mirrored on both sides, this will also explain how the bridge works in the opposite direction.

### Architecture

The Standard Bridge is composed of two contracts, the [`L1StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/65ec61dde94ffa93342728d324fecf474d228e1f/packages/contracts-bedrock/contracts/L1/L1StandardBridge.sol) (on `Ethereum`) and the [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/65ec61dde94ffa93342728d324fecf474d228e1f/packages/contracts-bedrock/contracts/L2/L2StandardBridge.sol) (on `OP Mainnet`).
These two contracts interact with one another via the `CrossDomainMessenger` system for sending messages between Ethereum and OP Mainnet.
You can read more about the `CrossDomainMessenger` in the guide on [Sending Data Between L1 and L2](./messaging).

### Bridged tokens

The Standard Bridge utilizes bridged representations of tokens that are native to another blockchain.
Before a token native to one chain can be bridged to the other chain, a bridged representation of that token must be created on the receiving side.

A bridged representation of a token is an ERC-20 token that implements the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/IOptimismMintableERC20.sol#L6-L18) interface.
This interface includes a few functions that the `StandardBridge` contracts use to manage the bridging process.
All bridged versions of tokens **must** implement this interface to be used with the `StandardBridge`.
Native tokens do not need to implement this interface.

A native token may have more than one bridged representation at the same time.
Users must always specify which bridged token they wish to use when using the bridge.
Different bridged representations of the same native token are considered entirely independent tokens.

### Bridging native tokens

The Standard Bridge uses a "lock-and-mint" mechanism to convert native tokens into their bridged representations.
This means that **native tokens are locked** into the Standard Bridge on one side, after which **bridged tokens are minted** on the other side.
The process for bridging a native token involves a few steps.

<Steps>
  <Step title="User gives the Standard Bridge an allowance">
The Standard Bridge must be able to pull tokens from the user to lock them into the bridge contract.
    To do this, the user must first give the bridge an [allowance](https://eips.ethereum.org/EIPS/eip-20#approve) to transfer the number of tokens that the user wishes to convert into a bridged representation.

  </Step>
  
    <Step title="User calls the bridging function">
After providing a sufficient allowance, the user calls the [`bridgeERC20To`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L193-L217) function on the `StandardBridge` contract on the chain where the native token lives (e.g., the `L1StandardBridge` contract if the token is native to Ethereum).
  
    The user must provide the following parameters to this function call:
  
    *   `address _localToken`: Address of the native token on the sending side.
    *   `address _remoteToken`: Address of the bridged representation on the receiving side.
    *   `address _to`: Address of the recipient of these tokens, usually the sender's address.
    *   `uint256 _amount`: Number of tokens to transfer.
    *   `uint32 _minGasLimit`: Gas to use to complete the transfer on the receiving side.
    *   `bytes calldata _extraData`: Optional identity extra data.
  
    
      Users can also trigger the [`bridgeERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L168-L191) function instead of `bridgeERC20To` to avoid needing to specify the `address _to` parameter.
      Doing so will automatically set the `address _to` parameter to the `msg.sender`.
  
      **The `bridgeERC20` function can be potentially dangerous for users with [smart contract wallets](https://web.archive.org/web/20231012141406/https://blockworks.co/news/what-are-smart-contract-wallets) as some smart contract wallets cannot be deployed at the same address on every blockchain.**
      To help users avoid potentially losing access to tokens by accident, the `bridgeERC20` function will always revert when triggered from a smart contract.
      Smart contract wallet users and other smart contracts should therefore use the `bridgeERC20To` function instead.
    

  
    </Step>
  
    <Step title="The Standard Bridge locks the transferred tokens">
When the user triggers the `bridgeERC20To` function while transferring a native token, the Standard Bridge will pull the `_amount` of `_localToken` tokens from the user's address and lock them inside of the bridge contract.
    A record of all locked tokens is stored within a [`deposits` mapping](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L41-L42) that keeps track of the total number of tokens deposited for a given `_localToken` and `_remoteToken` pair.
  
    Since a native token may have more than one bridged representation, the `deposits` token must keep track of the deposit pools for each `_localToken`/`_remoteToken` pair independently.
  
    To illustrate, suppose that two users deposit 100 units of the same native token, `Token A`, but wish to receive two different bridged tokens, `Token B` and `Token C`.
    Although the Standard Bridge would now have a total balance of 200 units of `Token A`, the mapping would show that the `Token A`/`Token B` pool and the `Token A`/`Token C` pool both have only 100 units.

  
    </Step>
  
    <Step title="The Standard Bridge sends a minting message">
After locking the native tokens, the Standard Bridge contract on the sending side will trigger a cross-chain message to the Standard Bridge contract on the receiving side via the [`CrossDomainMessenger`](./messaging) system.
    This message tells the receiving side to **mint** tokens according to the parameters specified by the user.
    Specifically, this message is an encoded call to the [`finalizeBridgeERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L250-L287) function on the other Standard Bridge contract.
    At this point, execution ends on the sending side.

  
    </Step>
  
    <Step title="The minting message is executed">
Once the minting message is sent, it must be relayed to the receiving side.
    Message relaying is automatic when sending from Ethereum to OP Mainnet but requires additional user transactions when sending from OP Mainnet to Ethereum.
    Read more about the message relaying process in the guide to [Sending Data Between L1 and L2](./messaging#communication-speed).
  
    When the message is relayed, the `finalizeBridgeERC20` function will be triggered on the receiving Standard Bridge contract.
    This function will receive the `_minGasLimit` gas defined by the user to execute to completion.

  
    </Step>
  
    <Step title="The minting message is authenticated">
Upon execution, `finalizeBridgeERC20` verifies a number of things about the incoming request:
  
    *   [The request must have originated from the Standard Bridge contract on the other blockchain](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L269).
    *   [The Standard Bridge must not be in an emergency paused state](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L271).
    *   [The bridged token must properly implement the `IOptimismMintableERC20` interface](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L272).
    *   [The bridged token must recognize the original native token as its `remoteToken()`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L273-L276).

  
    </Step>
  
    <Step title="The bridged token is minted">
If the minting message is fully verified, `finalizeBridgeERC20` will [mint tokens to the recipient](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L278) equal to the number of tokens originally deposited on the other blockchain.
    For this to work properly, the bridged representation of the native token must correctly implement a `mint` function that allows the Standard Bridge to mint tokens arbitrarily.
    This is part of the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/8ed8be8806208976e63421bd68779477d12295b5/packages/contracts-bedrock/src/universal/IOptimismMintableERC20.sol) interface.
  
    This completes the process of bridging native tokens.
    This process is identical in both the Ethereum to OP Mainnet and OP Mainnet to Ethereum directions.

  
    </Step></Steps>

### Bridging non-native tokens

The Standard Bridge uses a "burn-and-unlock" mechanism to convert bridged representations of tokens back into their native tokens.
This means that **bridged tokens are burned** on the Standard Bridge on one side, after which **native tokens are unlocked** on the other side.
The process for bridging a non-native, bridged representation of a token involves a few steps.

<Steps>
  <Step title="User calls the bridging function">
Unlike when bridging native tokens, users do not need to provide an approval to trigger a transfer of a bridged token because the Standard Bridge should already have the ability to `burn` these tokens.
    Here, the user calls the [`bridgeERC20To`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L193-L217) function on the `StandardBridge` contract on the chain where the bridged token lives (e.g., the `L2StandardBridge` contract if the token is bridged to OP Mainnet).
  
    The user must provide the following parameters to this function call:
  
    *   `address _localToken`: Address of the bridged token on the sending side.
    *   `address _remoteToken`: Address of the native token on the receiving side.
    *   `address _to`: Address of the recipient of these tokens, usually the sender's address.
    *   `uint256 _amount`: Number of tokens to transfer.
    *   `uint32 _minGasLimit`: Gas to use to complete the transfer on the receiving side.
    *   `bytes calldata _extraData`: Optional identity extra data.

  </Step>
  
    <Step title="The Standard Bridge burns the transferred tokens">
When the user triggers the `bridgeERC20To` function while transferring a bridge token, [the Standard Bridge will burn the corresponding `_amount` of tokens from the sender's address](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L345).

  
    </Step>
  
    <Step title="The Standard Bridge sends an unlock message">
After burning the bridged tokens, the Standard Bridge contract on the sending side will trigger a cross-chain message to the Standard Bridge contract on the receiving side via the [`CrossDomainMessenger`](./messaging) system.
    This message tells the receiving side to **unlock** tokens according to the parameters specified by the user.
    Specifically, this message is an encoded call to the [`finalizeBridgeERC20`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L250-L287) function on the other Standard Bridge contract.
    At this point, execution ends on the sending side.

  
    </Step>
  
    <Step title="The unlock message is executed">
Once the unlock message is sent, it must be relayed to the receiving side.
    Message relaying is automatic when sending from Ethereum to OP Mainnet but requires additional user transactions when sending from OP Mainnet to Ethereum.
    Read more about the message relaying process in the guide to [Sending Data Between L1 and L2](./messaging#communication-speed).
  
    When the message is relayed, the `finalizeBridgeERC20` function will be triggered on the receiving Standard Bridge contract.
    This function will receive the `_minGasLimit` gas defined by the user to execute to completion.

  
    </Step>
  
    <Step title="The unlock message is authenticated">
Upon execution, `finalizeBridgeERC20` verifies a number of things about the incoming request:
  
    *   [The request must have originated from the Standard Bridge contract on the other blockchain](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L269).
    *   [The Standard Bridge must not be in an emergency paused state](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L271).

  
    </Step>
  
    <Step title="The native token is unlocked">
If the unlock message is fully verified, `finalizeBridgeERC20` will [unlock and transfer tokens to the recipient](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L280-L281) equal to the number of tokens originally burned on the other blockchain.
  
    This completes the process of bridging native tokens.
    This process is identical in both the Ethereum to OP Mainnet and OP Mainnet to Ethereum directions.

  
    </Step></Steps>

### Bridging ETH

The Standard Bridge contracts can also be used to bridge ETH from Ethereum to OP Mainnet and vice versa.
The ETH bridging process is generally less complex than the ERC-20 bridging process.
Users simply need to trigger and send ETH to the [`bridgeETH`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L143-L150) or [`bridgeETHTo`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L152-L166) functions on either blockchain.


  Users can also deposit ETH from Ethereum to OP Mainnet by sending a basic ETH transfer from an EOA to the `L1StandardBridgeProxy`.
  This works because the `L1StandardBridgeProxy` contains a [`receive`](https://github.com/ethereum-optimism/optimism/blob/2e647210882d961f04055e656590d90ad98c9934/packages/contracts-bedrock/src/universal/StandardBridge.sol#L119-L121) function.
  You can find the mainnet and testnet addresses on the [Contract Addresses](/op-mainnet/network-information/op-addresses) page.


## Tutorials

*   [Learn how to bridge ERC-20 tokens with viem](/app-developers/tutorials/bridging/cross-dom-bridge-erc20)
*   [Learn how to create a standard bridged token](/app-developers/tutorials/bridging/standard-bridge-standard-token)
*   [Learn how to create a custom bridged token](/app-developers/tutorials/bridging/standard-bridge-custom-token)
*   [Learn how to submit transactions from L1](/app-developers/tutorials/bridging/cross-dom-bridge-eth)

## Superchain Token List

The [Superchain Token List](/app-developers/reference/tokens/tokenlist) exists to help users discover the right bridged token addresses for any given native token.
Consider checking this list to make sure that you're not using the wrong bridged representation of a token when bridging a native token.

Developers who are creating their own bridged tokens should consider [adding their token](https://github.com/ethereum-optimism/ethereum-optimism.github.io#adding-a-token-to-the-list) to the Superchain Token List.
Tokens on the Superchain Token List will automatically appear on certain tools like the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs&utm_medium=docs).

### Searching the Token List

You should strongly consider using the Superchain Token List to verify that you're using the correct bridged representation of a token when bridging a native token.
Doing so can help you avoid accidentally bridging to the wrong token and locking up your native token permanently.

You can easily find the bridged representation of a token for OP Mainnet on the [Bridged Token Addresses](/app-developers/reference/tokens/tokenlist) page.
If you want to find the bridged representation of a token for another chain, use the following steps.

<Steps>
  <Step title="Find the token you want to bridge">
The Superchain Token List is organized by the token's address and native blockchain.
    [Search the token list](https://github.com/ethereum-optimism/ethereum-optimism.github.io/blob/master/optimism.tokenlist.json) for the token you want to bridge to confirm that it's included in the list.
    Make sure that the chain ID in the entry matches the chain ID of the blockchain you're bridging from.
    Retrieve the token's name and symbol from the list.

  </Step>
  
    <Step title="Find the bridged representation of the token">
Once you've found the token you want to bridge, look for the token's name and symbol in the list.
    Find the entry that matches the name and symbol of the token you want to bridge and where the chain ID matches the chain ID of the blockchain you're bridging to.
    The address of this entry is the address of the bridged representation of the token you want to bridge.

  
    </Step>
        <Step title="Find the bridge contract address used by the token pair">
Identify the cross-chain bridge contract address used by the token pair. 
You can observe that some token pairs in the list utilize **custom bridge contracts** instead of the default Standard Bridge. 
Verify the bridge contract specified in the token list entry to ensure you are interacting with the correct bridge implementation for that token.

  
    </Step>
    </Steps>

---

## File: app-developers/guides/interoperability/get-started.mdx

---
title: "Build interoperable apps on OP Stack devnet"
---

Reimagine your app with OP Stack interop to deliver the unified UX your users expect. Hack on net-new, bold use cases on Interop devnet.


  Explore the [Superchain Dev Console](https://console.optimism.io/?utm_source=op-docs&utm_medium=docs) to build, launch, and grow your app on OP Stack chains.


## Connect to OP Stack Interop

Choose your development environment to build, test, and quickly iterate on your apps.

| Environment           | Purpose                                   | Getting Started                                            |
| --------------------- | ----------------------------------------- | ---------------------------------------------------------- |
| **Local development** | Rapid iteration and testing with Supersim | [Setup Supersim guide](/app-developers/tutorials/development/supersim) |
| **Interop devnet**    | Large-scale testing on testnets           | [Network specs](/app-developers/guides/building-apps)                     |

## Tools & resources for building interoperable apps

| Tool                                                                                       | Description                                                             |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
| [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs&utm_medium=docs) | One-stop shop to grab testnet ETH for any OP Stack network.           |
| [Supersim](/> app-developers/tools/development/supersim)                                                        | Local multi-chain testing environment for smart contracts.              |
| [Super CLI](https://github.com/ethereum-optimism/super-cli)                                | Command-line tool for seamless multichain app deployment and testing.   |
| [Superchain Relayer](https://github.com/ethereum-optimism/superchain-relayer)              | UI for monitoring and managing cross-chain transactions.                |
| [Interop Docs](/> op-stack/interop/explainer)                                                         | Comprehensive Interop information in the Optimism Docs.                 |
| [Developer Console](https://console.optimism.io/?utm_source=op-docs&utm_medium=docs)  | Comprehensive tool to build, launch, and grow your app on OP Stack chains. |
| [Developer Support GitHub](https://github.com/ethereum-optimism/developers/discussions)    | Quick and easy developer support.                                       |

## Handy step-by-step guides

<CardGroup cols={2}>
  <Card title="Supersim tutorials" icon="flask" href="/app-developers/tutorials/supersim">
    
  </Card>
  <Card title="Interop tutorials" icon="network-wired" href="/app-developers/tutorials/interop">
    
  </Card>
  <Card title="Transaction tutorials" icon="arrow-right-arrow-left" href="/app-developers/tutorials/transactions">
    
  </Card>
  <Card title="Bridging tutorials" icon="bridge-water" href="/app-developers/tutorials/bridging">
    
  </Card>
</CardGroup>

## Discover and build net-new use cases with OP Stack Interop

There is so much more than just bridge abstraction. Hack on the various cutting-edge applications that are uniquely enabled by OP Stack Interop. Here are some ideas to get you started:

| Superloans                                                                         | Superlend                                                                                                           | SuperCDP                                                                                                         |
| :--------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------- |
| Use collateral on ChainA and ChainB to execute an arbitrage opportunity on ChainA. | Deposit ETH into lending protocols across chains for optimal yield, with automatic rebalancing based on best rates. | Collateralized crosschain debt protocol that holds assets and issues cross-chain tokens on user preferred chains. |

---

## File: app-developers/guides/interoperability/message-expiration.mdx

---
title: Message expiration
---

# Message expiration

[Messages](/app-developers/guides/interoperability/message-passing) referenced between OP Stack chains have a limited validity period called the expiry window. Once this window elapses, the referenced message becomes invalid and can no longer be referenced.

For messages using [`L2ToL2CrossDomainMessenger`](/app-developers/guides/interoperability/message-passing), if a message expires before being referenced, developers can reemit the message on the source chain. This triggers a fresh `SentMessage` event, enabling the message to be relayed.

## The expiry window

The expiry window is an offchain constant, defined by [OP-Supervisor](/chain-operators/reference/components/op-supervisor), that defines how long a cross-chain message or event emitted remains valid. For any chain in the [Superchain interop cluster](/interop/explainer#superchain-interop-cluster), messages must be referenced within 7 days (604,800 seconds) of the log being created.

After this period, a message can no longer be referenced unless the event is remitted.

## Reemitting an expired message

The `resendMessage` function on the [`L2ToL2CrossDomainMessenger`](/app-developers/guides/interoperability/message-passing) contract allows developers to reemit a message that was sent but not yet relayed.

This emits a new `SentMessage` log with the same content as the original message, enabling offchain relayers to pick it up again.

The process to reemit an expired message:

1.  Call [`resendMessage`](https://github.com/ethereum-optimism/optimism/blob/a979a9444dbb482843f2a42f437ced54a8ac1053/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol#L110-L128) on the origin chain to reemit the message event. The contract verifies the message hash was originally sent. The call requires   [every parameter](https://github.com/ethereum-optimism/optimism/blob/a979a9444dbb482843f2a42f437ced54a8ac1053/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol#L110-L128) to rebuild the original message.

2.  [Relay the new message](/interop/message-passing#executing-message) as normal.

Note: Re-emitting an already relayed message will have no effect on the destination chain since the re-emitted event won't be logged by OP-Supervisor. 

## Next steps

*   Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing)

---

## File: app-developers/guides/interoperability/message-passing.mdx

---
title: Interop message passing overview
---

OP Stack interop is in active development. Some features may be experimental.

  This is an explanation of how interop works.
  You can find a step by step tutorial [here](/app-developers/tutorials/interoperability/message-passing).


The low-level [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol) contract handles basic message execution. It verifies whether an initiating message exists but does not check the message's destination, processing status, or other attributes.

The [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract extends `CrossL2Inbox` by providing complete cross-domain messaging functionality.

For high-level interoperability, both messages use the `L2ToL2CrossDomainMessenger` contract on their respective chains.

## Initiating message

```mermaid

sequenceDiagram
    participant app as Application
    box rgba(0,0,0,0.1) Source Chain
      participant srcContract as Source Contract
      participant srcXdom as L2ToL2CrossDomainMessenger
      participant log as Event Log
    end
    app->>srcContract: 1. Send a message
    srcContract->>srcXdom: 2. Call contract A<br/>in chainId B<br/>with calldata C
    note over srcXdom: 3. Sanity checks
    srcXdom->>log: 4. Log event SentMessage
    
```

1.  The application sends a transaction to a contract on the source chain.

2.  The contract calls [`L2ToL2CrossDomainMessenger.SendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L125-L142).
    The call requires these parameters:

    *   `_destination`: The chain ID of the destination blockchain.
    *   `_target`: The address of the contract on that blockchain.
    *   `_message`: The actual message.
    
    This message is provided to `_target` as calldata, which means it includes a function selector and the parameters for that function call.

3.  `L2ToL2CrossDomainMessenger` on the source chain verifies the message is legitimate:
    *   The destination chain is one to which this chain can send messages.
    *   The destination chain is *not* the source chain.
    *   The target is neither `CrossL2Inbox` nor `L2ToL2CrossDomainMessenger`.

4.  `L2ToL2CrossDomainMessenger` emits a log entry.
    In addition to the parameters, the log entry also includes:

    *   `_nonce`: A [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce) value to ensure the message is only executed once.

    *   `_sender`: The contract that sent the cross domain message.

## Executing message

```mermaid

sequenceDiagram
    participant app as Autorelayer
    box rgba(0,0,0,0.1) Source Chain
      participant log as Event Log
    end    
    participant super as OP-Supervisor<br/>(destination chain<br/>node)
    box rgba(0,0,0,0.1) Destination Chain
      participant dstXdom as L2ToL2CrossDomainMessenger
      participant Xinbox as CrossL2Inbox
      participant dstContract as Destination Contract
    end
    log->>super: 1. Initiating message log event
    app->>dstXdom: 2. Send an executing message
    dstXdom->>Xinbox: 3. Verify the initiating message is real 
    note over dstXdom: 4. Sanity checks
    dstXdom->>dstContract: 5. Call with provided calldata
```

1.  Before the executing message is processed, the log event of the initiating message has to get to `op-supervisor` on the destination chain before the [expiry window](/app-developers/guides/interoperability/message-expiration) of 7 days. 

2.  The autorelayer, the application, or a contract calling on the application's behalf calls [`L2ToL2CrossDomainMessenger.relayMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L150-L203).
    This call includes the message that was sent (`_sentMessage`), as well as the [fields required to find that message (`_id`)](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/L2/ICrossL2Inbox.sol#L4-L10).

3.  The `L2ToL2CrossDomainMessenger` uses `CrossL2Inbox` to verify the message was sent from the source.

4.  `L2ToL2CrossDomainMessenger` on the destination chain verifies the message is legitimate:

    *   `_destination`: Chain ID of the destination chain.
    *   `_nonce`: Nonce of the message sent
    *   `_sender`: Address that sent the message
    *   `_target`: Target contract or wallet address.
    *   `message`: Message payload to call target with.


5.  If everything checks out, `L2ToL2CrossDomainMessenger` calls the destination contract with the calldata provided in the message.

## Next steps

*   Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing).

---

## File: app-developers/guides/interoperability/reading-logs.mdx

---
title: Reading Logs with OP Stack Interop
---

OP Stack interop is in active development. Some features may be experimental.
OP Stack interop enables developers to leverage current and historical logs from other blockchains within the [OP Stack interop cluster](/interop/explainer#superchain-interop-cluster) directly on their local chain.
This allows smart contracts to consume local and cross-chain logs with low latency in a trust-minimized way.

## Overview

Instead of relying solely on [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol), developers can use [`CrossL2Inbox#validateMessage`](https://github.com/ethereum-optimism/optimism/blob/af091753917c1d7101314cbfe8ac5cbc2efe0e5e/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L49) and treat `CrossL2Inbox` as an oracle for logs that occurred on different chains or even their local chain.

This enables developers to:

*   Build cross-chain applications that react to events happening across OP Stack chains.
*   Create novel applications that leverage data from multiple chains.


When reading logs, you must reference logs created within the [expiry window of 7 days](/app-developers/guides/interoperability/message-expiration).


## Why use `CrossL2Inbox`?

*   **Reference existing logs**: Allows contracts to verify and use logs that were already emitted, without requiring those logs to have been sent as cross-chain messages.
*   **Trust-minimized security**: Leverages the existing OP Stack security model with no additional trust assumptions.
*   **Flexibility**: Can be used to validate events from another chain or even the local chain.

## How it works

### Architecture

The process works through the [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/af091753917c1d7101314cbfe8ac5cbc2efe0e5e/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L33) contract, which serves as an oracle for logs from other chains in the OP Stack interop cluster:

1.  A smart contract on `Chain A` emits a log (event)
2.  Your contract on `Chain B` calls `CrossL2Inbox#validateMessage` with the log's identifier
3.  The `CrossL2Inbox` contract verifies the log's authenticity
4.  Your contract can then use the validated log data

### Key components

*   **Identifier**: A struct containing information about the log, including `chainId`, `origin` (contract address), and other log metadata
*   **[validateMessage](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L79)**: Function that verifies a log's authenticity before allowing its use

## Example: cross-chain attestation verification

Let's walk through a conceptual example of verifying an Ethereum Attestation Service (EAS) attestation across chains.
EAS is a [predeploy](/> app-developers/reference/contracts/interop/predeploy) in the OP Stack for making attestations on or off-chain about anything.

### Source chain: creating an attestation

On the source chain (e.g., OP Mainnet), a user creates an attestation using EAS:

```mermaid
sequenceDiagram
    participant User
    participant App as Application
    participant EAS as EAS Contract
    participant Log as Event Log
    
    User->>App: Request attestation
    App->>EAS: createAttestation()
    EAS->>Log: Emit AttestationCreated event
    Note over Log: Event contains attestation data
```

1.  The user initiates a request for an attestation through an application.

2.  The application calls the `createAttestation()` function on the EAS (Ethereum Attestation Service) contract on the source chain.

3.  The EAS contract processes the attestation request and emits an `AttestationCreated` event.

4.  The event is recorded in the chain's log, containing all necessary attestation data.

### Destination chain: verifying the attestation

On the destination chain (e.g., Unichain), a DeFi application wants to verify this attestation:

```mermaid
sequenceDiagram
    participant User
    participant DeFi as DeFi Application
    participant Verifier as AttestationVerifier
    participant CrossL2 as CrossL2Inbox
    participant OP as OP-Supervisor Service
    
    User->>DeFi: Request access using attestation
    DeFi->>Verifier: verifyAttestation(id, attestationEvent)
    Verifier->>CrossL2: validateMessage(id, keccak256(attestationEvent))
    CrossL2->>OP: Check if log exists
    OP-->>CrossL2: Confirm log validity
    CrossL2-->>Verifier: Return validation result
    Verifier-->>DeFi: Return verification status
    DeFi-->>User: Grant access based on attestation
```

1.  The user requests access to a DeFi application on the destination chain, referencing an attestation created on the source chain.

2.  The DeFi application calls a verification function on an attestation verifier contract, passing the attestation's identifier and event data.

3.  The attestation verifier calls `validateMessage()` on the `CrossL2Inbox` contract, passing the attestation identifier and a hash of the event data.

4.  The [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol) contract interacts with the [`OP-Supervisor`](/chain-operators/reference/components/op-supervisor) service to check if the specified log exists on the source chain.

5.  The `OP-Supervisor` confirms the validity of the log to the `CrossL2Inbox` contract.

6.  The `CrossL2Inbox` returns the validation result to the attestation verifier.

7.  The attestation verifier returns the verification status to the DeFi application.

8.  If validation is successful, the DeFi application grants the user access based on the verified attestation.

The primary benefit of this approach is that it allows your contract to verify attestations that already exist on another chain without requiring those attestations to have been explicitly sent as cross-chain messages.

## Overview of the process

To implement cross-chain log reading:

```mermaid
flowchart TD
    A[1. Identify log to consume] --> B[2. Create Identifier struct]
    B --> C[3. Call validateMessage]
    C --> D[4. Process validated log data]
    
    subgraph "Conceptual Approach"
    E["Define an Identifier struct with:
       - chainId: The source chain ID
       - origin: The source contract address
       - Other required identifier parameters"]
    
    F["Call validateMessage on CrossL2Inbox
       Pass the identifier and hash of log data"]
    end
    
    B --> E
    C --> F
```

1.  First, identify which log from another chain you want to consume in your application.

2.  Create an Identifier struct that contains all necessary information about the log, including the chain ID and the contract address that emitted the log.

3.  Call the `validateMessage()` function on the `CrossL2Inbox` contract, passing the identifier and a hash of the log data.

4.  After validation, process the log data according to your application's requirements.

## Important considerations

*   This feature works between chains within the [OP Stack interop cluster](/interop/explainer#superchain-interop-cluster).
*   The same functionality can be used on a single chain (for example, to maintain a consistent architecture).

### Handling validation failures

*   The `validateMessage` call will revert the entire transaction if validation fails.
*   Consider implementing a try-catch pattern in your application's frontend to handle these failures.
*   Design your contract to allow for retry mechanisms where appropriate.

## Comparison with `L2ToL2CrossDomainMessenger`

| Feature    | L2ToL2CrossDomainMessenger                     | CrossL2Inbox#validateMessage                      |
| ---------- | ---------------------------------------------- | ------------------------------------------------- |
| Purpose    | Send messages between chains                   | Verify logs from other chains or local chain      |
| Initiation | Source explicitly sends message to destination | Destination queries for existing logs from source |
| Use Case   | Transfer tokens, trigger actions               | Verify attestations, reference events             |
| Flow       | Push model                                     | Pull model                                        |

## End-to-End flow comparison

```mermaid
flowchart LR
    subgraph "L2ToL2CrossDomainMessenger (Push Model)"
        A[Source Contract] -->|sendMessage| B[Source L2ToL2CrossDomainMessenger]
        B -->|emit event| C[Event Log]
        C -.->|relayed by| D[Autorelayer]
        D -->|relayMessage| E[Destination L2ToL2CrossDomainMessenger]
        E -->|execute| F[Destination Contract]
    end
    
    subgraph "CrossL2Inbox (Pull Model)"
        G[Source Contract] -->|emit event| H[Event Log]
        H -.->|monitored by| I[OP-Supervisor]
        J[Destination Contract] -->|validateMessage| K[CrossL2Inbox]
        K <--->|verify log| I
    end
```

This diagram compares the two approaches for cross-chain communication:

### L2ToL2CrossDomainMessenger (Push Model):

1.  A source contract calls `sendMessage()` on the `L2ToL2CrossDomainMessenger`.

2.  The messenger emits an event to the event log.

3.  An autorelayer detects the event and relays it to the destination chain.

4.  The destination `L2ToL2CrossDomainMessenger` receives the relayed message.

5.  The destination messenger executes the message on the target contract.

### CrossL2Inbox (Pull Model):

1.  A source contract emits an event to the event log.

2.  The `OP-Supervisor` service monitors events across chains.

3.  A destination contract calls `validateMessage()` on the `CrossL2Inbox`.

4.  The `CrossL2Inbox` verifies the log's existence by communicating with the `OP-Supervisor`.

5.  The destination contract receives verification and proceeds with its logic.

## Next steps

*   Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing)

---

## File: app-developers/guides/transactions/estimates.mdx

---
title: Estimating transaction fees on OP Mainnet
---


Check out the guide on understanding [Transaction Fees on OP Mainnet](/op-stack/transactions/fees) for an in-depth explanation of how OP Mainnet transaction fees work.


It's important to properly estimate the cost of a transaction on OP Mainnet before submitting it to the network.
Here you'll learn how to estimate both of the components that make up the total cost of an OP Mainnet transaction, the [execution gas fee](/op-stack/transactions/fees#execution-gas-fee) and the [L1 data fee](/op-stack/transactions/fees#l1-data-fee).
Make sure to read the guide on [Transaction Fees on OP Mainnet](/op-stack/transactions/fees) for a detailed look at how these fees work under the hood.

## Execution gas fee 


Estimating the execution gas fee on OP Mainnet is just like estimating the execution gas fee on Ethereum.
Steps are provided here for reference and convenience, but you can use the same tooling that you'd use to estimate the execution gas fee for a transaction on Ethereum.


A transaction's execution gas fee is exactly the same fee that you would pay for the same transaction on Ethereum.
This fee is equal to the amount of gas used by the transaction multiplied by the gas price attached to the transaction.
Refer to the guide on [Transaction Fees on OP Mainnet](/op-stack/transactions/fees#execution-gas-fee) for more information about the execution gas fee.

When estimating the execution gas fee for a transaction, you'll need to know the gas limit and the [max fee per gas](https://ethereum.org/en/developers/docs/gas/#maxfee) for the transaction.
Your transaction fee will then be the product of these two values.
Refer to the guide on [Setting Transaction Gas Parameters on OP Mainnet](./parameters) to learn how to select an appropriate gas limit and max fee per gas for your transaction.

<Steps>
  <Step title="Estimate the gas limit">
Using the same tooling that you'd use to estimate the gas limit for a transaction on Ethereum, estimate the gas limit for your transaction on OP Mainnet.
  OP Mainnet is designed to be [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) so transactions will use the same amount of gas on OP Mainnet as they would on Ethereum.
  This means you can feed your transaction to the [`eth_estimateGas`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_estimategas) JSON-RPC method just like you would on Ethereum. Alternatively, use Tenderly's [`tenderly_estimateGas`](https://docs.tenderly.co/node/rpc-reference/optimism-mainnet/tenderly_estimateGas) for 100% accurate gas estimations.

  </Step>
  
  <Step title="Estimate the max fee per gas">
Like Ethereum, OP Mainnet uses an `EIP-1559` style fee market to determine the current base fee per gas.
  You can then additionally specify a priority fee (also known as a tip) to incentivize the Sequencer to include your transaction more quickly.
  Make sure to check out the guide on [Setting Transaction Gas Parameters on OP Mainnet](./parameters) to learn more about how to select an appropriate max fee per gas for your transaction.

  
  </Step></Steps>

## L1 data fee


The Viem library provides a convenient method for estimating the L1 data fee for a transaction.
Check out the tutorial on [Estimating Transaction Costs on OP Mainnet](/app-developers/tutorials/transactions/sdk-estimate-costs) to learn how to use the Viem library to estimate the L1 data fee for your transaction.
Keep reading if you'd like to learn how to estimate the L1 data fee without the Viem library.


The L1 data fee is a fee paid to the Sequencer for the cost of publishing your transaction to Ethereum.
This fee is paid in ETH and is calculated based on the size of your transaction in bytes and the current gas price on Ethereum.
Refer to the guide on [Transaction Fees on OP Mainnet](/op-stack/transactions/fees#l1-data-fee) for more information about the L1 data fee.

Unlike the execution gas fee, the L1 data fee is an **intrinsic** fee for every transaction.
This fee is automatically charged based on the size of your transaction and the current Ethereum gas price.
You currently cannot specify a custom L1 data fee for your transaction.


The L1 data fee is paid based on the current Ethereum gas price as tracked within the [`GasPriceOracle`](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) smart contract.
This gas price is updated automatically by the OP Mainnet protocol.
Your transaction will be charged the Ethereum gas price seen by the protocol at the time that your transaction is included in an OP Mainnet block.
This means that the L1 data fee for your transaction may differ from your estimated L1 data fee.


<Steps>
  <Step title="Serialize your transaction">
The L1 data fee is calculated based on the size of your serialized transaction in bytes.
  Most Ethereum tooling will provide a method for serializing a transaction.
  For instance, Ethers.js provides the [`ethers.utils.serializeTransaction`](https://docs.ethers.org/v5/api/utils/transactions/#utils-serializeTransaction) method.

  </Step>
  
  <Step title="Estimate the L1 data fee">
Once you have serialized your transaction, you can estimate the L1 data fee by calling the [`getL1Fee`](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol#L109-L124) method on the [`GasPriceOracle`](https://github.com/ethereum-optimism/optimism/blob/233ede59d16cb01bdd8e7ff662a153a4c3178bdd/packages/contracts/contracts/L2/predeploys/OVM_GasPriceOracle.sol) smart contract available on OP Mainnet and all OP Stack chains.
  This method takes the serialized transaction as input and returns the L1 data fee in wei using the formula described in the [Transaction Fees on OP Mainnet](/op-stack/transactions/fees#l1-data-fee) guide.
  
  
  Fee estimation is typically performed before the transaction is signed.
  As a result, the `getL1Fee` method assumes that your input is an **unsigned** Ethereum transaction.
  

  
  </Step></Steps>

### Tooling

Several tools are available to help you estimate the L1 Data Fee for your transaction.
Selecting the right tool for your use case will depend on your specific needs.

*   [Viem](https://viem.sh/op-stack#getting-started-with-op-stack) provides first-class support for OP Stack chains, including OP Mainnet. You can use Viem to estimate gas costs and send cross-chain transactions (like transactions through the Standard Bridge system). It's strongly recommended to use Viem if you are able to do so as it will provide the best native support at the moment.

### Future proofing

The L1 Data Fee formula is subject to change in the future, especially as the data availability landscape evolves.
As a result, it's important to future-proof your transaction fee estimation code to ensure that it will continue to function properly as the L1 Data Fee formula changes.

*   Use existing [tooling](#tooling) to estimate the L1 Data Fee for your transaction if possible. This tooling will be updated to reflect any changes to the L1 Data Fee formula. This way you won't need to modify your code to account for any changes to the formula.
*   Use the `getL1Fee` method on the `GasPriceOracle` if you are unable to use existing tooling. The `getL1Fee` method will be updated to reflect any changes to the L1 Data Fee formula. It's strongly recommended that you do **not** implement the L1 Data Fee formula yourself.

---

## File: app-developers/guides/transactions/flashblocks-and-gas-usage.mdx

---
title: "Flashblocks and Gas Usage on OP Stack"
---

This document explains how **Flashblocks** affect block gas usage, transaction inclusion, large-transaction handling, and how to avoid unexpected transaction rejections.

If you are unfamiliar with Flashblocks you can read more [here](https://docs.optimism.io/op-stack/features/flashblocks#flashblocks).

---

## Background: Block Gas Limit vs. Transaction Gas Limit

On OP Mainnet, the **theoretical block gas limit** is currently **40M gas**. In a traditional EVM mental model, a single transaction can use up to the full block gas limit, as long as it does not exceed it.

With **Flashblocks**, block construction is incremental and *effective transaction inclusion depends on the gas already consumed within the block*, not just the theoretical maximum.

---

## What Are Flashblocks?

Flashblocks are an incremental block-building mechanism used by the sequencer and block builder.

Instead of constructing the full block at once, the block is built in **N flashblocks** (currently 8):

- Each flashblock increases the *cumulative gas budget* available for inclusion
- Earlier flashblocks can only include smaller transactions
- Larger transactions can only be included later, *if enough gas remains*

### Example

Assume:

- Block gas limit **B = 40M**
- Number of flashblocks **N = 8**

Then the cumulative gas limit increases as follows:

| Flashblock | Max cumulative gas available |
| ---------- | ---------------------------- |
| 1          | 5M                           |
| 2          | 10M                          |
| 3          | 15M                          |
| 4          | 20M                          |
| 5          | 25M                          |
| 6          | 30M                          |
| 7          | 35M                          |
| 8          | 40M                          |

A transaction with a **20M gas limit** can only be included starting from flashblock 4, *and only if prior flashblocks have not already consumed that gas*.

---

## Why a Transaction Can Get Stuck

A transaction may be submitted with a gas limit lower than the total block gas limit (e.g. **38M < 40M**) and still remain pending or, depending on the EL implementation, be rejected with the error: `exceeds block gas limit`.

This happens because transaction acceptance and transaction inclusion are decided by different components.

Genrally:
- Incoming transactions first enter the Execution Layer txpool.
- The EL checks whether `tx.gasLimit` exceeds the **full block gas limit**.
  - If it does, it rejects with `exceeds block gas limit`.
  - Otherwise, it keeps the transaction in the **pending/queued mempool** and **P2P-gossips** it eventually reaching **rbuilder**.
- **rbuilder** applies the incremental **remaining block gas** check to decide whether to include the tx in a flashblock or leave it pending.

### Real-World Example

- Block gas limit: **40M**
- Gas already used in earlier flashblocks: **13M**
- Remaining gas: **27M**
- Incoming transaction gas limit: **38M**

The **EL** checks if the transaction exceeds **40M**. Since **38M < 40M**, the transaction is accepted into the pending/queued mempool and gossiped to **rbuilder**.
**rbuilder** applies the incremental remaining-gas constraint. With only **27M** gas remaining and the tx requiring **38M**, the transaction cannot be included yet and stays pending.
 
**Result:** the transaction is not rejected, but remains pending in the mempool until there is enough remaining gas to include it in a flashblock.

---

## Client Behavior and Error Messages

There are two distinct outcomes depending on the transaction size:

- **Rejected immediately (txpool admission failure):**  
  The EL (e.g. **op-geth**, **op-reth**) rejects transactions only when the transaction gas limit exceeds the **full block gas limit**:
  - Condition: `tx.gasLimit > blockGasLimit`
  - Error returned to the user: `exceeds block gas limit`

- **Accepted but pending inclusion:**  
  If the transaction gas limit is **within** the block gas limit, but still too large to fit into the **remaining block gas** at that point in rbuilder, it will not be included yet.
  - The transaction remains in the **pending/queued mempool** or rbuilder, waiting for enough remaining gas to become available in a later flashblock.
  - From the user’s perspective, the transaction may appear **pending** until it gets enough gas available for inclusion.
 
In practice, transactions are sent to Execution Layer RPC nodes (e.g. via proxyd), checked, and only if accepted into the EL txpool are eventually propagated to the sequencer/rbuilder for potential inclusion.

Implementing mechanisms such as txpool rebroadcasting can mitigate this divergence automatically, so users do not notice retries. However, when an error is surfaced, it may originate from the ingress client’s gas checks.

---

## Practical Guidance for Application Developers and End Users

### Leave Headroom for Large Transactions

If you submit large transactions, avoid targeting the full block gas limit.

**Recommended approach:**

- Leave **20–30% headroom** relative to the block gas limit
- For a 40M block, aim for ≤ **28–32M gas**

Or:

- Proactively cap single-transaction gas usage to ~16.7M gas to align with the upcoming L2 Fusaka transaction limit 

This increases the chance that the transaction fits within Execution Layer gas checks under partial block utilization.

More generally, designing applications to avoid extremely large single transactions is good long-term practice since, with upcoming protocol changes (Fusaka on L2), transactions will be capped at 16.7M gas anyway.

---

### Expect Variable Gas Availability During Congestion

Under high demand:

- Early flashblocks are often full
- Remaining gas later in the block may be limited
- Very large transactions may be rejected or require retries

This is expected behavior with Flashblocks and not necessarily an issue with the transaction itself.

---

### Transaction Resubmission

Because block state evolves quickly:

- Retrying submission in a later block may succeed
- Large transactions are more likely to be included when earlier flashblocks are less congested

---

## Key Takeaways

- Flashblocks build blocks incrementally, unlocking gas capacity over time rather than all at once  
  *(e.g. in a 40M gas block with 8 flashblocks, only 5M is available at the first flashblock, then 10M, and so on)*

- A transaction must be **within the full block gas limit** to be accepted into the EL txpool, but it must fit within the **remaining block gas** to be included by rbuilder in the next flashblock 
  *(e.g. if 13M gas out of 40M has already been used, a 38M gas transaction can be accepted by EL but cannot be included until enough remaining gas is available once it reaches rbuilder)*

- As a result, large transactions may be **accepted but remain pending** even when their gas limit is below the nominal block limit  
  *(e.g. a 38M gas transaction can be accepted in a 40M gas block but delayed during periods of high activity)*

- Leaving meaningful headroom is the most reliable way to improve inclusion success for large transactions  
  *(e.g. targeting 28–32M gas instead of the full 40M or proactively implementing the Fusaka limit of 16.7M gas)*

---

## File: app-developers/guides/transactions/parameters.mdx

---
title: Setting transaction gas parameters on OP Mainnet
---

OP Mainnet is designed to be [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306) which means that it is as compatible with Ethereum as possible, down to the client software used to run OP Mainnet nodes.
Like Ethereum, OP Mainnet has an EIP-1559 style fee mechanism that dynamically adjusts a [base fee](https://ethereum.org/en/developers/docs/gas/#base-fee) that acts as the minimum fee that a transaction must pay to be included in a block.
OP Mainnet also allows transactions to pay a [priority fee](https://ethereum.org/en/developers/docs/gas/#priority-fee) (also known as a tip) to incentivize the Sequencer to include transactions more quickly.

Setting the base fee and the priority fee appropriately is important to ensure that your transactions are included in a timely manner.
This guide will walk you through some best practices for determining the base fee and priority fee for your transactions.

## Selecting the base fee

The base fee is the minimum fee that a transaction must pay to be included in a block.
Transactions that specify a maximum fee per gas that is less than the current base fee cannot be included in the blockchain.

The simplest way to select a base fee is to look at the latest available OP Mainnet block.
Each OP Mainnet block includes the current base fee and the amount of gas used within that block.
You can use this information to predict a reasonable maximum fee for your transaction.

Note that, like Ethereum, the base fee is not explicitly defined within a transaction.
Instead, the maximum base fee is determined as the difference between the `maxFeePerGas` and the `maxPriorityFeePerGas` fields of any given transaction.

<Steps>
  <Step title="Retrieve the latest block">
Using the JSON-RPC API or your favorite Ethereum library, retrieve the latest block on OP Mainnet.

  </Step>

  <Step title="Retrieve the base fee and gas used">
From the block, retrieve the `baseFeePerGas` and `gasUsed` fields.


  </Step>

  <Step title="Predict the next base fee">
OP Mainnet adjusts the base fee based on the amount of gas used in the previous block.
  If the previous block used more than 5m gas (of the 30m gas limit), then the base fee will increase by up to 10%.
  If the previous block used less than 5m gas, then the base fee will decrease by up to 10%.
  Refer to the [OP Mainnet EIP-1559 Parameters](/op-stack/protocol/differences#eip-1559-parameters) section for more details.


  </Step>

  <Step title="Select a base fee per gas">
Using the current base fee per gas and the amount of gas used in the previous block, you can predict the next base fee per gas.
  If you are highly sensitive to the base fee, you may want to select a base fee per gas that is either 10% higher or 10% lower than the previous base fee.
  However, you may run the risk that your transaction will not be included in a block quickly.
  If you are less sensitive to the base fee, you may wish to simply use a large multiple of the previous base fee (e.g. 2x).


  </Step></Steps>

## Selecting the priority fee

The priority fee is an optional tip that can be paid to the Sequencer to incentivize them to include your transaction more quickly.
The priority fee is paid in addition to the base fee.

The simplest way to select a priority fee is to use the [`eth_maxPriorityFeePerGas`](https://docs.alchemy.com/reference/eth-maxpriorityfeepergas) JSON-RPC method to retrieve an estimate for an acceptable priority fee.
Many Ethereum libraries will provide a function to call this JSON-RPC method.
You can also use the [`eth_feeHistory`](https://docs.alchemy.com/reference/eth-feehistory) JSON-RPC method to retrieve historical priority fee data.
You can then use this data to predict a reasonable priority fee for your transaction.

Alternatively, you can rely on Tenderly's [`tenderly_gasPrice`](https://docs.tenderly.co/node/rpc-reference/optimism-mainnet/tenderly_gasPrice?mtm_campaign=ext-docs\&mtm_kwd=optimism) to get real-time gas predictions with 3 levels of likelihood for transaction inclusion.