# Use the Docs with AI
Source: https://docs.optimism.io/ai-docs
Read the Optimism docs as an agent with llms.txt and per-page markdown, connect the hosted MCP server, and start from curated prompts.
These docs are built to be read by AI assistants and agents, not just browsers.
Every page is available as plain markdown, the whole site publishes a machine-readable index, and a hosted MCP server exposes search over the content.
This page covers the three ways to put them to work: [reading the docs as an agent](#read-the-docs-as-an-agent), [connecting the hosted MCP server](#connect-the-hosted-mcp-server), and [starting from curated prompts](#prompt-starters).
## Read the Docs as an Agent
### The Documentation Index: llms.txt
The site publishes an [llms.txt](https://llmstxt.org/) index at:
```text theme={null}
https://docs.optimism.io/llms.txt
```
It is a plain-markdown listing of the site's pages with their URLs.
Point an assistant at it to discover what exists before fetching individual pages.
This is the recommended entry point for any agent working with these docs.
### Per-Page Markdown
Every page is served as raw markdown at its own URL with `.md` appended.
For example, the [Node Operator Overview](/node-operators/overview) page is available as:
```text theme={null}
https://docs.optimism.io/node-operators/overview.md
```
Fetching the `.md` form skips the HTML shell, navigation, and scripts, so an assistant gets exactly the content of the page in a fraction of the tokens.
Prefer it over the HTML URL whenever your tooling fetches pages programmatically.
### The Contextual Menu
Every page in these docs has a contextual menu (next to the page title) with assistant-ready actions:
* **Copy page**: copies the page as markdown, ready to paste into any chat.
* **Open in ChatGPT**: opens a ChatGPT conversation preloaded with the page.
* **Open in Claude**: opens a Claude conversation preloaded with the page.
* **Copy MCP server URL**: copies the [hosted MCP server](#connect-the-hosted-mcp-server) address for your client configuration.
Use it when you are reading a page and want to hand it to an assistant without constructing URLs by hand.
### Tips for Agents Reading These Docs
* Start from `llms.txt` to map the site, then fetch only the `.md` pages you need.
* These docs describe how to *use* the OP Stack. The normative protocol definition lives in the [OP Stack specifications](https://specs.optimism.io) - when a docs page and the specs disagree, the specs win.
* Configuration flags and versions change between releases. Confirm load-bearing values against the linked source or release notes before acting on them.
## Connect the Hosted MCP Server
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is an open standard for connecting AI assistants to external tools and data sources.
These docs are hosted on Mintlify, which [automatically generates and hosts an MCP server](https://www.mintlify.com/docs/ai/model-context-protocol) for the site.
The server exposes a search tool over the documentation, so a connected assistant (Claude Code, Claude Desktop, Cursor, and others) can look up current OP Stack content instead of relying on stale training data.
### Option 1: The Hosted MCP Server (Recommended)
The docs MCP server is available over HTTP at:
```text theme={null}
https://docs.optimism.io/mcp
```
For Claude Code, run:
```bash theme={null}
claude mcp add --transport http optimism-docs https://docs.optimism.io/mcp
```
For Cursor, Claude Desktop, and other clients that use a JSON configuration, add the server URL:
```json theme={null}
{
"mcpServers": {
"optimism-docs": {
"url": "https://docs.optimism.io/mcp"
}
}
}
```
You can also connect from any page of these docs: the contextual menu (next to the page title) includes an option to copy the MCP server details for your client.
Ask your assistant:
```text theme={null}
Using the optimism-docs MCP server, find the page about running a node
from source and summarize its hardware requirements.
```
A correctly wired assistant calls the server's search tool, locates [Building and Running an OP Stack Node From Source](/node-operators/tutorials/run-node-from-source), and answers from the live page.
### Option 2: Direct Fetching (No MCP Server Required)
If your assistant can fetch web pages but does not support MCP, no setup is needed.
The site's [machine-readable surfaces](#read-the-docs-as-an-agent) - the `llms.txt` index and per-page markdown - work with any assistant that can fetch URLs.
Give it the index and the URL convention:
```text theme={null}
The Optimism docs publish a machine-readable index at
https://docs.optimism.io/llms.txt. Any page is available as raw
markdown by appending .md to its URL. Use the index to find relevant
pages, then fetch their .md versions.
```
Add that to your assistant's project instructions (for example `CLAUDE.md`, `.cursorrules`, or a system prompt) and it will resolve OP Stack questions against the live docs.
If your tooling only reaches the network through MCP and cannot use the hosted server, the reference [fetch server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch) (`uvx mcp-server-fetch`) plus the instruction block above achieves the same result.
If the hosted MCP endpoint is not responding, [open an issue](https://github.com/ethereum-optimism/optimism/issues) so we can investigate.
## Prompt Starters
Copy-paste prompts for common OP Stack tasks, ready for any AI assistant that can fetch URLs.
Each prompt names its intended persona, grounds the assistant in specific docs pages (fetched as [per-page markdown](#per-page-markdown)), and tells it what to produce.
Replace the bracketed placeholders with your own details before sending.
If your assistant cannot fetch URLs, open the linked pages and use the contextual menu's **Copy page** action to paste them in instead.
### Audit My Batcher Configuration for Cost
**Persona:** chain operator
Data availability is usually a chain's largest onchain operating cost, and the batcher configuration controls it.
Grounding pages: [Configure the Batcher](/chain-operators/guides/configuration/batcher) and [Transaction Fees 101](/chain-operators/guides/management/transaction-fees-101).
```text theme={null}
Read https://docs.optimism.io/chain-operators/guides/configuration/batcher.md
and https://docs.optimism.io/chain-operators/guides/management/transaction-fees-101.md
Here is my current op-batcher configuration: [paste your flags or env vars].
My chain posts roughly [N] transactions per day and targets [blobs / calldata].
Audit the configuration for data-availability cost: identify settings that
increase posting cost, explain the trade-off each one controls (cost vs.
latency vs. safety), and propose a revised configuration with reasoning.
Flag any flag you are not sure still exists so I can check it against
op-batcher --help for my release.
```
### Walk Me Through the Deposit Flow
**Persona:** app developer
Understand what actually happens between an L1 deposit call and the transaction appearing on L2 before you build on it.
Grounding pages: [Deposit Flow](/op-stack/bridging/deposit-flow) and [Deposit Transactions](/app-developers/tutorials/bridging/deposit-transactions).
```text theme={null}
Read https://docs.optimism.io/op-stack/bridging/deposit-flow.md and
https://docs.optimism.io/app-developers/tutorials/bridging/deposit-transactions.md
Walk me through the full lifecycle of a deposit from L1 to L2: which
contract I call, what events are emitted, how the L2 transaction is
derived, and what latency and failure modes to expect. Then show me the
minimal code to trigger a deposit from my app. I am building
[describe your app].
```
### Plan a Fault-Proof Challenger Deployment
**Persona:** chain operator
Every permissionless fault-proof chain needs an honest challenger watching its dispute games.
Grounding pages: [OP-Challenger Explainer](/op-stack/fault-proofs/challenger) and [How to Configure Challenger for Your Chain](/chain-operators/guides/configuration/op-challenger-config-guide).
```text theme={null}
Read https://docs.optimism.io/op-stack/fault-proofs/challenger.md and
https://docs.optimism.io/chain-operators/guides/configuration/op-challenger-config-guide.md
I operate an OP Stack chain with fault proofs enabled on [network].
Produce a deployment plan for op-challenger: the role it plays, the
resources and keys it needs, the bond funding it requires, the
configuration I must set, and the monitoring I should attach. List open
questions I need to answer about my own chain before deploying.
```
### Bridge ETH From L1 in My App
**Persona:** app developer
Move ETH between Ethereum and an OP Stack chain programmatically.
Grounding page: [Submitting Transactions From L1](/app-developers/tutorials/bridging/cross-dom-bridge-eth).
```text theme={null}
Read https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-eth.md
Using the approach in this tutorial, write the code for my app to bridge
ETH from [L1 network] to [L2 network] and report deposit status to the
user. My stack is [TypeScript framework / environment]. Point out where
testnet and mainnet configuration differ, and what the user experience
should show while the deposit is in flight.
```
### Explain My Transaction's Fee Breakdown
**Persona:** app developer
OP Stack transactions pay an execution fee and a data-availability fee; estimating only one of them causes bugs.
Grounding page: [Transaction Fees on OP Mainnet](/op-stack/transactions/fees).
```text theme={null}
Read https://docs.optimism.io/op-stack/transactions/fees.md
Explain every component of the fee my transaction pays on an OP Stack
chain, how each is calculated, and which ones fluctuate with L1 gas
prices. Then show me how to estimate the total fee for a transaction
correctly in my app, and the common estimation mistakes to avoid.
Here is my current estimation code: [paste code, optional].
```
### Debug a Stuck Withdrawal
**Persona:** app developer
Withdrawals are multi-step and time-delayed by design; most "stuck" withdrawals are actually mid-flight.
Grounding pages: [Withdrawal Flow](/op-stack/bridging/withdrawal-flow) and [Transaction Finality](/op-stack/transactions/transaction-finality).
```text theme={null}
Read https://docs.optimism.io/op-stack/bridging/withdrawal-flow.md and
https://docs.optimism.io/op-stack/transactions/transaction-finality.md
A withdrawal from [L2 network] appears stuck: [describe what you did,
when, and the transaction hash or current status]. Using the withdrawal
lifecycle in these docs, determine which stage the withdrawal is in,
whether the delay is expected protocol behavior or an actual problem,
and exactly what action (if any) unblocks the next step.
```
## Contributing
The prompts above are curated: each one maps to a task readers actually arrive with, and each grounds the assistant in maintained docs pages.
To propose a new prompt or fix a stale one, [open an issue](https://github.com/ethereum-optimism/optimism/issues) or edit this page.
# Bridging basics
Source: https://docs.optimism.io/app-developers/guides/bridging/basics
Learn about the fundamentals of sending data and tokens between Ethereum and OP Mainnet.
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 or custom bridged token](/app-developers/tutorials/bridging/standard-bridge-standard-token)
* [Learn how to submit transactions from L1](/app-developers/tutorials/bridging/cross-dom-bridge-eth)
# Custom bridges
Source: https://docs.optimism.io/app-developers/guides/bridging/custom-bridge
Important considerations when building custom bridges for OP Mainnet.
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-standard-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/develop/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/develop/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)
# Sending data between L1 and L2
Source: https://docs.optimism.io/app-developers/guides/bridging/messaging
Understand how bridging between L1 and L2 works, the messenger contracts that carry messages, what messages cost, and why the challenge period exists.
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: the messenger contracts that carry messages between layers, how long delivery takes in each direction, what messages cost, and why messages from L2 to L1 must wait out a challenge period.
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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
// 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.
Transactions sent from L1 to L2 take **approximately 1-3 minutes**, 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).
Transactions sent from L2 to L1 take **approximately 7 days**: the message must be initiated on L2, proven on L1 against an output root, and finalized on L1 only after the [challenge period](#understanding-the-challenge-period) (7 days on mainnet, shorter on test networks) has elapsed.
This waiting period is a core part of the security model of the OP Stack and cannot be circumvented.
For the step-by-step mechanics in each direction, see [Deposit flow](/op-stack/bridging/deposit-flow) and [Withdrawal flow](/op-stack/bridging/withdrawal-flow).
## 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 theme={null}
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 theme={null}
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/develop/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol) contract, which then sends a call to the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal2.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 period of time is called the "challenge period" because it is the window during which a published transaction result can be challenged with a [fault proof](/op-stack/protocol/overview#fault-proofs): Optimistic Rollups publish transaction *results* to Ethereum without executing the transactions there, so L1 contracts must give challengers time to prove a published result faulty before acting on it.
The practical consequence for app developers 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**, and L2 ⇒ L1 messages sent using the standard messenger contracts cannot be relayed until they've waited out the full challenge period.
For how the challenge period fits into the withdrawal process, see [Withdrawal flow](/op-stack/bridging/withdrawal-flow); for why it exists, see the [fault proofs overview](/op-stack/protocol/overview#fault-proofs).
# Using the Standard Bridge
Source: https://docs.optimism.io/app-developers/guides/bridging/standard-bridge
Learn how the Standard Bridge moves ETH and ERC-20 tokens between Layer 1 and Layer 2.
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 they can 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/develop/packages/contracts-bedrock/src/L1/L1StandardBridge.sol) (on `Ethereum`) and the [`L2StandardBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/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/develop/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20.sol#L10-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.
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.
After providing a sufficient allowance, the user calls the [`bridgeERC20To`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L217-L229) 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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L194-L206) 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.
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L41) 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.
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L272-L299) function on the other Standard Bridge contract.
At this point, execution ends on the sending side.
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.
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L281).
* [The Standard Bridge must not be in an emergency paused state](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L283).
* [The bridged token must properly implement the `IOptimismMintableERC20` interface](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L284).
* [The bridged token must recognize the original native token as its `remoteToken()`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L285-L288).
If the minting message is fully verified, `finalizeBridgeERC20` will [mint tokens to the recipient](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L290) 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/develop/packages/contracts-bedrock/interfaces/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.
### 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.
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L217-L229) 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.
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L359).
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L272-L299) function on the other Standard Bridge contract.
At this point, execution ends on the sending side.
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.
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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L281).
* [The Standard Bridge must not be in an emergency paused state](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L283).
If the unlock message is fully verified, `finalizeBridgeERC20` will [unlock and transfer tokens to the recipient](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L292-L293) 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.
### 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/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L166-L168) or [`bridgeETHTo`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/StandardBridge.sol#L182-L184) 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/develop/packages/contracts-bedrock/src/L1/L1StandardBridge.sol#L134-L136) 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 or custom bridged token](/app-developers/tutorials/bridging/standard-bridge-standard-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.
Because a native token may have more than one bridged representation, and different bridged representations are entirely independent tokens, using the wrong one can lock up your native tokens permanently.
Follow the guide on [verifying bridged token addresses](/app-developers/guides/bridging/verify-bridged-tokens) to confirm that you're using the correct bridged representation of a token before bridging.
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).
# Verifying bridged token addresses
Source: https://docs.optimism.io/app-developers/guides/bridging/verify-bridged-tokens
Use the Superchain Token List to find and verify the correct bridged representation of a token before using the Standard Bridge.
A native token may have more than one bridged representation on the other chain, and different bridged representations of the same native token are entirely independent tokens.
Bridging to the wrong representation can lock up your native tokens permanently.
This guide shows you how to use the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) to find and verify the correct bridged token address before you bridge a token with the [Standard Bridge](/app-developers/guides/bridging/standard-bridge).
## Find the bridged representation of a token for OP Mainnet
You can easily find the bridged representation of a token for OP Mainnet on the [Bridged token addresses](/app-developers/reference/tokens/tokenlist) page.
That page is generated automatically from the Superchain Token List.
## Find the bridged representation of a token for another chain
If you want to find the bridged representation of a token for another chain, use the following steps.
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.
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.
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.
## Add a token to the Superchain Token List
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).
## Next steps
* Learn how the [Standard Bridge](/app-developers/guides/bridging/standard-bridge) moves tokens between Ethereum and OP Mainnet.
* Follow the tutorial to [bridge ERC-20 tokens with viem](/app-developers/tutorials/bridging/cross-dom-bridge-erc20).
# Building apps on OP Stack chains
Source: https://docs.optimism.io/app-developers/guides/building-apps
Learn the basics of building apps on OP Stack chains.
This guide explains the basics of OP Stack development.
OP Stack chains are [EVM equivalent](https://web.archive.org/web/20231127160757/https://medium.com/ethereum-optimism/introducing-evm-equivalence-5c2021deb306), meaning they run a slightly modified version of the same `geth` you run on mainnet.
Therefore, the differences between OP Stack development and Ethereum development are minor.
But a few differences [do exist](/op-stack/protocol/differences).
## OP Stack chains endpoint URLs
To access any Ethereum type network you need an endpoint. [These providers](/app-developers/reference/rpc-providers) support our networks.
### Network choice
For development purposes we recommend you use either a local development network or [OP Sepolia](https://sepolia-optimism.etherscan.io).
That way you don't need to spend real money.
If you need ETH on OP Sepolia for testing purposes, [you can use this faucet](https://console.optimism.io/faucet?utm_source=op-docs\&utm_medium=docs).
## Interacting with contracts on OP Stack chains
We have Hardhat's Greeter contract on OP Sepolia at address [0x9d334aFBa83865E67a9219830ADA57aaA9406681](https://testnet-explorer.optimism.io/address/0x9d334aFBa83865E67a9219830ADA57aaA9406681#code).
You can verify your development stack configuration by interacting with it.
## Development stacks
As you can see in the different development stacks below, the way you deploy contracts and interact with them on OP Stack chains is almost identical to the way you do it with L1 Ethereum.
The most visible difference is that you have to specify a different endpoint (of course).
For more detail, see the guide on [Differences between Ethereum and OP Stack Chains](/op-stack/protocol/differences).
* [Foundry](https://getfoundry.sh/)
* [Hardhat](https://hardhat.org/)
* [Apeworx](https://www.apeworx.io/)
* [Brownie](https://eth-brownie.readthedocs.io/en/stable/install.html)
* [Remix](https://remix.ethereum.org)
* [Waffle](https://getwaffle.io/)
## Best practices
### Use provided EVM
It is best to start development with the EVM provided by the development stack.
Not only is it faster, but such EVMs often have extra features, such as the [ability to log messages from Solidity](https://hardhat.org/tutorial/debugging-with-hardhat-network.html) or a [graphical user interface](https://trufflesuite.com/ganache/).
### Debug before deploying
After you are done with that development, debug your decentralized application locally and then on a [Sepolia test network](/op-mainnet/network-information/connecting-to-op).
This lets you debug parts that are OP Stack chains specific such as calls to bridges to transfer ETH or tokens between layers.
Only when you have a version that works well on a test network should you deploy to the production network, where every transaction has a cost.
**Running your app in production**
A production application depends on infrastructure your team does not run: RPC endpoints that hold up under real traffic (the public endpoints are rate-limited and not built for production), bridges your users rely on, and a chain whose operator keeps sequencing, upgrades, and incident response going around the clock. These docs cover building and testing. If your application is growing toward dedicated blockspace of its own, [OP Enterprise](https://optimism.io/op-enterprise?utm_source=docs\&utm_medium=docs\&utm_campaign=op-enterprise) offers managed and supported paths to running a chain. These docs stay the reference for what you build either way. OP Enterprise is Optimism's managed offering.
### Contract source verification
You don't have to upload your source code to [block explorers](/app-developers/tools/infrastructure/block-explorers), but it is a good idea.
On the test network, it lets you issue queries and transactions from the explorer's user interface.
On the production network, it lets users know exactly what your contract does, which is conducive to trust.
Just remember, if you use [the Etherscan API](https://explorer.optimism.io/apis?utm_source=op-docs\&utm_medium=docs), you need one API key for OP Stack chains and a separate one for OP Sepolia.
# Configuring Actions SDK
Source: https://docs.optimism.io/app-developers/guides/configuring-actions
Learn how to configure Actions SDK for your application.
Actions SDK lets you choose which assets, markets, chains, protocols, and providers you want to support in your application via configuration file.
Follow the
[quickstart](/app-developers/quickstarts/actions) guide to
add Actions SDK as a dependency in your app.
Follow the [connecting a wallet to Actions
SDK](/app-developers/guides/connect-wallet-to-actions) guide, choose and
install a Wallet Provider.
`actions.ts` - An accessible file that holds all of your configuration preference.
Let Actions SDK know which Wallet Provider you've chosen:
Select a wallet provider:
```typescript title="actions.ts" theme={null}
const walletConfig = {
hostedWalletConfig: {
provider: {
type: "privy" as const,
},
},
smartWalletConfig: {
provider: {
type: "default" as const,
attributionSuffix: "actions",
},
},
};
```
```typescript title="actions.ts" theme={null}
const walletConfig = {
hostedWalletConfig: {
provider: {
type: "turnkey" as const,
},
},
smartWalletConfig: {
provider: {
type: "default" as const,
attributionSuffix: "actions",
},
},
};
```
```typescript title="actions.ts" theme={null}
const walletConfig = {
hostedWalletConfig: {
provider: {
type: "dynamic" as const,
},
},
smartWalletConfig: {
provider: {
type: "default" as const,
attributionSuffix: "actions",
},
},
};
```
Select a wallet provider:
```typescript title="actions.ts" theme={null}
import { PrivyClient } from '@privy-io/node'
const privyClient = new PrivyClient(
process.env.PRIVY_APP_ID,
process.env.PRIVY_APP_SECRET,
)
const walletConfig = {
hostedWalletConfig: {
provider: {
type: "privy" as const,
config: {
privyClient,
},
},
},
smartWalletConfig: {
provider: {
type: "default" as const,
attributionSuffix: "actions",
},
},
};
```
```typescript title="actions.ts" theme={null}
import { Turnkey } from '@turnkey/sdk-server'
const turnkeyClient = new Turnkey({
apiBaseUrl: 'https://api.turnkey.com',
apiPublicKey: process.env.TURNKEY_API_KEY,
apiPrivateKey: process.env.TURNKEY_API_SECRET,
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
})
const walletConfig = {
hostedWalletConfig: {
provider: {
type: "turnkey" as const,
config: {
client: turnkeyClient.apiClient(),
},
},
},
smartWalletConfig: {
provider: {
type: "default" as const,
attributionSuffix: "actions",
},
},
};
```
Configure which assets you want to support across all lend providers:
```typescript title="actions.ts" theme={null}
// Additional config from previous steps...
// Import popular assets
import { USDC } from '@eth-optimism/actions-sdk/assets'
import type { Asset, AssetsConfig } from "@eth-optimism/actions-sdk";
// Or define custom assets
export const CustomToken: Asset = {
address: {
[mainnet.id]: '0x123...',
[unichain.id]: '0x456...',
[baseSepolia.id]: '0x789...',
},
metadata: {
decimals: 6,
name: 'Custom Token',
symbol: 'CUSTOM',
},
type: 'erc20',
}
// Configure allowed/blocked assets
const assetsConfig: AssetsConfig = {
allow: [USDC, CustomToken],
block: [], // Optional
}
```
Define which markets you want to support or block within your app:
```typescript title="actions.ts" theme={null}
// Additional config from previous steps...
export const GauntletUSDC: LendMarketConfig = {
address: '0xabc...',
chainId: unichain.id,
name: 'Gauntlet USDC',
asset: USDC,
lendProvider: 'morpho',
}
```
Configure which lend protocols you want to support. You can enable one or multiple providers:
```typescript title="actions.ts" theme={null}
// Additional config from previous steps...
import type { LendConfig } from "@eth-optimism/actions-sdk";
const lendConfig: LendConfig = {
morpho: {
marketAllowlist: [GauntletUSDC],
marketBlocklist: [], // Optional
},
aave: {
marketAllowlist: [AaveWETH],
marketBlocklist: [], // Optional
},
};
```
Configure supported chains:
```typescript title="actions.ts" theme={null}
// Additional config from previous steps...
import { optimism, base } from "viem/chains";
// Define any EVM chain
const OPTIMISM = {
chainId: optimism.id,
rpcUrls: env.OPTIMISM_RPC_URL,
bundler: {
// Bundle and sponsor txs with a gas paymaster
type: "simple" as const,
url: env.OPTIMISM_BUNDLER_URL,
},
};
const BASE = {
chainId: base.id,
rpcUrls: env.BASE_RPC_URL,
bundler: {
// Bundle and sponsor txs with a gas paymaster
type: "simple" as const,
url: env.BASE_BUNDLER_URL,
},
};
const chains = [OPTIMISM, BASE];
```
Finally bring it all together and initialize Actions:
```typescript title="actions.ts" theme={null}
// Additional config from previous steps...
export const actions = createActions({
wallet: walletConfig,
assets: assetsConfig,
lend: lendConfig,
chains,
});
```
Once you've initialized your actions instance, import it anywhere you need to take action:
```typescript theme={null}
import { actions } from './actions';
// Use actions anywhere in your app
const market = await actions.lend.getMarket({ ... });
const wallet = await actions.wallet.createSmartWallet({ ... });
const receipt = await wallet.lend.openPosition({ ... });
```
## Next Steps
For detailed API documentation and type definitions, see the [Actions SDK Reference](/app-developers/reference/actions/integrating-wallets).
# Connecting a wallet to Actions SDK
Source: https://docs.optimism.io/app-developers/guides/connect-wallet-to-actions
Connect an embedded provider wallet to Actions SDK so it can perform DeFi actions like Lend, Borrow, Swap, and Pay.
Actions SDK works with wallets from popular embedded wallet providers, regardless of where and how transactions are signed.
This guide shows you how to connect a provider wallet to Actions so it can call Actions functions.
For help deciding which wallet schema fits your use case, see [Integrating wallets](/app-developers/reference/actions/integrating-wallets).
## Connect your wallet
Follow embedded wallet provider documentation and installation steps.
Actions works with Typescript clients, both frontend React and backend Node.
Import [Actions SDK](https://actions.money/) alongside your chosen wallet
provider SDK.
The [quickstart](/app-developers/quickstarts/actions#installation) covers
installing Actions SDK in your project.
Follow embedded wallet provider documentation for wallet creation and
access.
Call `actions.wallet.toActionsWallet(...)`, and [pass
in](/app-developers/quickstarts/actions#choose-a-wallet-provider) the
provider wallet.
The quickstart includes provider-specific code examples for both frontend
and backend clients.
The returned
[Wallet](/app-developers/reference/actions/wallet-definitions) is now
capable of calling Actions
[functions](/app-developers/quickstarts/actions#take-action) like Lend,
Borrow, Swap and Pay!
## Next steps
* Follow the [Configuring Actions](/app-developers/guides/configuring-actions) guide to define which protocols, chains, and assets to support.
* Learn about [smart wallets & signers](/app-developers/reference/actions/integrating-wallets#smart-wallets--signers) if you want Actions to deploy smart contract wallets controlled by your users' embedded wallets.
* See the [Wallet Documentation](/app-developers/reference/actions/wallet-definitions) for API details on wallet classes, functions, and parameters.
# Build interoperable apps on OP Stack devnet
Source: https://docs.optimism.io/app-developers/guides/interoperability/get-started
Learn about deploying contracts, cross-chain messaging, and tutorials to help you build applications on OP Stack chains.
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/installation) |
| **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-sdks/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
## 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. |
# Message expiration
Source: https://docs.optimism.io/app-developers/guides/interoperability/message-expiration
What message expiration is, why it exists, and how to reemit a previously sent message if it has expired and was never relayed.
# 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 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/develop/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol#L114-L122) 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/develop/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol#L114-L122) 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 destination chain only honors the original initiating message.
## Next steps
* Learn how to [pass messages between blockchains](/app-developers/tutorials/interoperability/message-passing)
# Interop message passing overview
Source: https://docs.optimism.io/app-developers/guides/interoperability/message-passing
Understand how interop message passing works, from the initiating message on the source chain to the executing message on the destination chain.
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 theme={null}
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
in chainId B
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 theme={null}
sequenceDiagram
participant app as Autorelayer
box rgba(0,0,0,0.1) Source Chain
participant log as Event Log
end
box rgba(0,0,0,0.1) Destination Chain
participant dstNode as Destination Chain Node
participant dstXdom as L2ToL2CrossDomainMessenger
participant Xinbox as CrossL2Inbox
participant dstContract as Destination Contract
end
log->>dstNode: 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 reach the destination chain's node 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).
# Reading Logs with OP Stack Interop
Source: https://docs.optimism.io/app-developers/guides/interoperability/reading-logs
Understand how contracts use CrossL2Inbox to validate logs from other interop chains, and how this pull model differs from sending messages.
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/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L76) 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/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol#L31) 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 theme={null}
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 theme={null}
sequenceDiagram
participant User
participant DeFi as DeFi Application
participant Verifier as AttestationVerifier
participant CrossL2 as CrossL2Inbox
User->>DeFi: Request access using attestation
DeFi->>Verifier: verifyAttestation(id, attestationEvent)
Verifier->>CrossL2: validateMessage(id, keccak256(attestationEvent))
Note over CrossL2: Check the log exists on the source chain.
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 checks whether the specified log exists on the source chain.
5. The `CrossL2Inbox` returns the validation result to the attestation verifier.
6. The attestation verifier returns the verification status to the DeFi application.
7. 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 theme={null}
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 theme={null}
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]
J[Destination Contract] -->|validateMessage| K[CrossL2Inbox]
K -.->|verify log exists on source chain| H
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. A destination contract calls `validateMessage()` on the `CrossL2Inbox`.
3. The `CrossL2Inbox` verifies that the log exists on the source chain.
4. 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)
# Testing apps for OP Stack chains
Source: https://docs.optimism.io/app-developers/guides/testing-apps
Learn best practices for testing apps on OP Stack chains.
For the most part, running applications on OP Stack chains is identical to running them on Ethereum, so the testing is identical too.
In this guide, you learn the best practices for OP Stack testing where there are differences.
## Unit tests and single layer integration tests
The vast majority of tests do not involve any OP Stack-specific features.
In those cases, while you *could* test everything on an OP Stack chain or a test network, that would normally be inefficient.
Most Ethereum development stacks include features that make testing easier, which normal Ethereum clients, such as geth (and our modified version, `op-geth`) don't support.
Therefore, it is a good idea to run the majority of tests, which do not rely on OP Stack-specific features, in the development stack.
It is a lot faster.
It is a best practice to design and run thorough tests across an OP test network, either in your [local multichain development environment](/app-developers/tools/development/supersim), our [devnets](/op-stack/introduction/op-stack), or on [the test network](/op-mainnet/network-information/connecting-to-op#op-sepolia), depending on your use case. Alternatively, with [Tenderly Virtual TestNets](https://docs.tenderly.co/virtual-testnets?mtm_campaign=ext-docs\&mtm_kwd=optimism)you can run tests with complete integration with existing protocols, access to unlimited faucets, continuous state sync, and access to development tools such as Debugger and Simulator UI.
Running proper testing is key to identifying fringe cases where the equivalence between OP Stack chains and Ethereum breaks down (or where Ethereum mainnet itself and the development stack may be non-equivalent in a production environment).
## Multilayer integration tests
Some apps need OP Stack-specific features that aren't available as part of the development stack.
For example, if your decentralized application relies on [inter-domain communication](/app-developers/guides/bridging/messaging), the effort of developing a stub to let you debug it in a development stack is probably greater than the hassle of having the automated test go to [a local multichain development environment](/app-developers/tools/development/supersim) each time.
## Testing and Staging with Tenderly
Tenderly [Virtual TestNets](https://docs.tenderly.co/virtual-testnets?mtm_campaign=ext-docs\&mtm_kwd=optimism) provide a powerful environment for testing OP Stack applications with mainnet-like conditions. They offer several advantages for testing OP Stack applications:
* **Mainnet State Replication**: Virtual TestNets can sync with the latest OP Stack mainnet state, allowing you to test against real network conditions and interact with up-to-date protocols without spending real assets.
* **Unlimited Faucet**: Access [unlimited test tokens](https://docs.tenderly.co/virtual-testnets/unlimited-faucet?mtm_campaign=ext-docs\&mtm_kwd=optimism) for both native currency and ERC-20 tokens, enabling comprehensive testing of complex DeFi interactions.
* **Collaborative Testing**: Your entire team can access the same testing environment, making it easier to debug issues and validate fixes.
* **CI/CD Integration**: Incorporate automated testing in your deployment pipeline using [Virtual TestNets' API](https://docs.tenderly.co/reference/api?mtm_campaign=ext-docs\&mtm_kwd=optimism#/operations/createAlert) and [GitHub Actions integration](https://docs.tenderly.co/virtual-testnets/ci-cd/github-actions-foundry?mtm_campaign=ext-docs\&mtm_kwd=optimism).
* **Development tools**: Rely on the built-in [developer explorer](https://docs.tenderly.co/developer-explorer?mtm_campaign=ext-docs\&mtm_kwd=optimism) and debugging tools to analyze test transactions and contract interactions.
## Integration with other products
In many cases a decentralized application requires the services of other contracts.
For example, [Perpetual v. 2](https://docs.perp.com/docs/guides/integration-guide) cannot function without [Uniswap v. 3](https://uniswap.org/blog/uniswap-v3).
* If that is the case, you can use [mainnet forking](/app-developers/reference/tools/supersim/fork). It works with OP Stack chains.
* Create a Virtual TestNet to get access to third party contracts (e.g. Uniswap) and it's latest or historical state.
* Alternatively, you can connect to our [test network](/op-mainnet/network-information/connecting-to-op#op-sepolia) if those contracts are also deployed there (in many cases they are).
# Estimating transaction fees on OP Mainnet
Source: https://docs.optimism.io/app-developers/guides/transactions/estimates
Learn how to properly estimate the total cost of a transaction 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.
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.
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.
## 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/develop/packages/contracts-bedrock/src/L2/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.
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.
Once you have serialized your transaction, you can estimate the L1 data fee by calling the [`getL1Fee`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L64-L71) method on the [`GasPriceOracle`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/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.
### 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.
# Integrate Subblocks in your app
Source: https://docs.optimism.io/app-developers/guides/transactions/integrating-subblocks
Query pre-confirmed Subblocks state from your app using standard JSON-RPC methods with the pending tag, or through viem and ethers.
A chain that streams [Subblocks](/op-stack/features/subblocks) publishes a partial block every 200 ms, so your app can show a transaction's result well before the block containing it is sealed.
This guide shows how to read that pre-confirmed state over standard Ethereum JSON-RPC and from the viem and ethers libraries.
Most apps need only a change of endpoint and the `pending` block tag.
* In production, point your app to a subblocks-aware RPC endpoint from your provider of choice. If your provider doesn't support subblocks yet, let us know on [Discord](https://discord.gg/jPQhHTdemH)
and we'll work with them to get it added.
* To run your own subblocks-aware node, start `op-reth` with `--flashblocks-url=` pointing at the chain's stream. The node then serves `pending` from the subblock state.
OP Mainnet and OP Sepolia stream URLs are listed on the [network information page](/op-mainnet/network-information/connecting-to-op).
This guide covers reading pre-confirmed state through an RPC endpoint, which is what most apps want.
If you consume the WebSocket stream directly instead, four payload fields are zeroed and need handling — see the [Subblocks notice](/notices/subblocks).
## Supported RPC methods
You read subblock state with the same Ethereum JSON-RPC calls you already use.
The difference is using the "pending" tag in some of them to explicitly query the pre-confirmed state instead of the last finalized block.
* **`eth_getBlockByNumber`**: Use the `pending` tag to retrieve the latest subblock snapshot.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"number": "0x1234",
"hash": "0x...",
"transactions": [...]
}
}
```
* **`eth_call`**: Use the `pending` tag to execute calls against the most recent pre-confirmed state.
```json theme={null}
{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{"to": "0x...", "data": "0x..."}, "pending"],
"id": 1
}
```
* `eth_getBalance` / `eth_getTransactionCount`: Use the `pending` tag to fetch balances or transaction counts respectively as they evolve within the block window.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x0234"
}
```
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x1b" // 27 transactions
}
```
Other methods, like `eth_getTransactionReceipt`, `eth_getTransactionByHash` or `eth_simulateV1`, return data from pre-confirmed transactions without requiring the `pending` tag.
Consult the [Flashblocks specification](https://specs.optimism.io/protocol/flashblocks.html#flashblock-json-rpc-apis) for more details on each of these methods.
## Libraries
You need a subblocks‑aware RPC endpoint to use the following libraries:
### Viem
```ts theme={null}
import { createPublicClient, createWalletClient, http, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { opSepolia } from 'viem/chains';
import { publicActionsL2, walletActionsL2 } from 'viem/op-stack';
const account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY}`);
const walletClient = createWalletClient({
account,
chain: opSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(walletActionsL2());
const publicClient = createPublicClient({
chain: opSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(publicActionsL2());
const submissionTime = new Date();
const hash = await walletClient.sendTransaction({
to: '0x...',
value: parseEther('0.0001'),
});
// Wait for pre-confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const confirmTime = new Date();
console.log('pre-confirmed in ms:', confirmTime.getTime() - submissionTime.getTime());
```
### Ethers
```ts theme={null}
import { ethers } from 'ethers';
// Here, provider is a subblocks-enabled RPC provider
const provider = new ethers.JsonRpcProvider("https://sepolia.optimism.io");
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY as string, provider);
const tx = { to: '0x...', value: ethers.parseEther('0.0001') };
const submission = new Date();
const sent = await wallet.sendTransaction(tx);
await sent.wait(0);
const confirmed = new Date();
// should represent the transaction (pre)confirmation faster than standard RPC
console.log('Pre-confirmed in ms:', confirmed.getTime() - submission.getTime());
// validates the transaction hash returned by the pre-confirmed transaction above
const receipt = await provider.getTransactionReceipt(sent.hash);
const validated = new Date();
console.log('Receipt validated in ms:', validated.getTime() - submission.getTime());
console.log('Transaction receipt:', receipt);
```
## Next steps
* Understand how subblocks affect [gas usage and large transactions](/app-developers/guides/transactions/subblocks-and-gas-usage).
* Learn how subblocks work in the [Subblocks explainer](/op-stack/features/subblocks).
* Review the [technical specs](https://specs.optimism.io/protocol/flashblocks.html) for architecture details.
* Join our [community](https://discord.gg/jPQhHTdemH) to share best practices and get support!
# Setting transaction gas parameters on OP Mainnet
Source: https://docs.optimism.io/app-developers/guides/transactions/parameters
Learn how to set gas parameters for transactions 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.
Using the JSON-RPC API or your favorite Ethereum library, retrieve the latest block on OP Mainnet.
From the block, retrieve the `baseFeePerGas` and `gasUsed` fields.
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.
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).
## 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.
# Transaction statuses on OP Mainnet
Source: https://docs.optimism.io/app-developers/guides/transactions/statuses
Reference for the statuses a transaction can have on OP Mainnet.
This page is a reference for the statuses a transaction can have on OP Mainnet, listed in the order a transaction moves through them.
Each entry gives the status, how long it typically takes to reach, what the status guarantees, and the JSON-RPC call used to observe it.
Consult it when you need to troubleshoot a transaction, decide how many confirmations your application should wait for, or display accurate status information to your users.
For an explanation of *why* a transaction moves through these states, see [Transaction flow](/op-stack/transactions/transaction-flow).
## Pending
**Instant after sending to the Sequencer**
A transaction is considered "pending" when it has been sent to the Sequencer but has not yet been included in a block.
This is the first status a transaction will have after being sent to the Sequencer.
At this point the transaction is not part of the blockchain and there is no guarantee that the transaction will be included in the blockchain.
The list of all pending transactions can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `pending` as the block number.
## Sequencer confirmed or unsafe
**Typically within 2-4 seconds**
A transaction is considered "sequencer confirmed" or "unsafe" when it has been included in a block by the Sequencer but that block has **not** yet been published to Ethereum.
Although the transaction is included in a block, it is still possible for the transaction to be excluded from the final blockchain if the Sequencer fails to publish the block to Ethereum within the [Sequencing Window](/connect/resources/glossary#sequencing-window) (approximately 12 hours).
Applications should make sure to consider this possibility when displaying information about transactions that are in this state.
The latest "sequencer confirmed" block can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `safe` as the block number and comparing this to the result returned for the `latest` block.
If the `safe` block is behind the `latest` block, then the earliest "sequencer confirmed" block is the `safe` block plus one.
## Published to Ethereum or safe
**Typically within 5-10 minutes, up to 12 hours**
A transaction is considered "safe" when it has been included in a block by the Sequencer and that block has been published to Ethereum but that block is not yet finalized.
Once a block has been published to Ethereum there is a high likelihood that the block will be included in the final blockchain.
However, it is still possible for the block to be excluded from the final blockchain if the Ethereum blockchain experiences a reorganization.
The latest "safe" block can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `safe` as the block number.
Transactions typically become "safe" within a few minutes of becoming "sequencer confirmed".
## Finalized
**Typically within 15-30 minutes, up to 12 hours**
A transaction is considered "finalized" when it has been included in a block by the Sequencer, that block has been published to Ethereum, and that block has been finalized.
Once a block has been finalized it is guaranteed to be included in the OP Mainnet blockchain.
Applications that require the highest level of certainty that a transaction will be included in the blockchain should wait until the transaction is "finalized" before considering the transaction to be successful.
The latest "finalized" block can be retrieved by calling the standard JSON-RPC method [`eth_getBlockByNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) with the parameter `finalized` as the block number.
# Subblocks and gas usage on OP Stack
Source: https://docs.optimism.io/app-developers/guides/transactions/subblocks-and-gas-usage
Explains how Subblocks affect block gas usage, transaction inclusion, and why large transactions can be rejected or stay pending.
This document explains how **subblocks** affect block gas usage, transaction inclusion, large-transaction handling, and how to avoid unexpected transaction rejections.
If you are unfamiliar with subblocks you can read more in the [Subblocks explainer](/op-stack/features/subblocks).
***
## 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 **subblocks**, block construction is incremental and *effective transaction inclusion depends on the gas already consumed within the block*, not just the theoretical maximum.
***
## What Are Subblocks?
Subblocks are an incremental block-building mechanism: instead of constructing the full block at once, the sequencer's execution client assembles it as a series of subblocks, each of which adds to the *cumulative gas budget* available for inclusion.
For what subblocks are and how a block's subblock count is determined, see the [Subblocks explainer](/op-stack/features/subblocks).
The consequence for gas usage is that earlier subblocks can only include smaller transactions, and larger transactions fit only later in the block, *if enough gas remains*.
### Example
Assume:
* Block gas limit **B = 40M**
* Number of subblocks **N = 10**, the count a 2-second block time and a 200 ms interval imply
Then the cumulative gas limit increases as follows:
| Subblock | Max cumulative gas available |
| -------- | ---------------------------- |
| 1 | 4M |
| 2 | 8M |
| 3 | 12M |
| 4 | 16M |
| 5 | 20M |
| 6 | 24M |
| 7 | 28M |
| 8 | 32M |
| 9 | 36M |
| 10 | 40M |
A transaction with a **20M gas limit** can only be included starting from subblock 5, *and only if prior subblocks have not already consumed that gas*.
The subblock count is not fixed. Heavy execution in one interval leaves less time for the ones after it, so a real block may contain fewer subblocks than the interval implies.
***
## 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.
Generally:
* 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 the **sequencer**.
* The **sequencer's execution client** applies the incremental **remaining block gas** check to decide whether to include the tx in a subblock or leave it pending.
### Real-World Example
* Block gas limit: **40M**
* Gas already used in earlier subblocks: **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 the **sequencer**.
The sequencer's execution client 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 subblock.
***
## 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-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 the block, it will not be included yet.
* The transaction remains in the **pending/queued mempool**, waiting for enough remaining gas to become available in a later subblock.
* 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 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 subblocks 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 subblocks 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 subblocks are less congested
***
## Key Takeaways
* Subblocks build blocks incrementally, unlocking gas capacity over time rather than all at once\
*(e.g. in a 40M gas block with 10 subblocks, only 4M is available at the first subblock, then 8M, 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 the sequencer in the next subblock
*(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 the sequencer)*
* 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)*
# Troubleshooting transactions
Source: https://docs.optimism.io/app-developers/guides/transactions/troubleshooting
Learn how to troubleshoot common problems with transactions.
## Transactions stuck in the transaction pool
OP Chain uses EIP-1559, but with different parameters than L1 Ethereum.
As a result, while the base fee on L1 can grow by up to 12.5% in a twelve-second period (in the case of a single 30M gas block), the L2 base fee can grow by up to 77% (in the case of six 30M gas blocks).
However, it still shrinks by only up to 12.5% in the same twelve-second period (if all the blocks are empty).
If the maximum fee per gas specified by the transaction is less than the block base fee, it does not get included until the base fee drops to below the value in the transaction.
When this happens, some users may see their transaction become stuck.
No ETH is lost, but the transaction does not clear on its own.
We have a workaround that users and wallet operators can implement immediately, and we expect a protocol-level fix to be live by the end of Q4.
### Recommendation
Set the maximum fee per gas for transactions to a relatively high value, such as 0.1 gwei.
This will *not* increase the transaction cost because the same base fee, determined by a formula, is charged to all the transactions in the block.
To save on the cost of L2 gas you want to minimize the max priority fee.
Also, if the [current base fee](https://optimistic.grafana.net/public-dashboards/c84a5a9924fe4e14b270a42a8651ceb8?orgId=1\&refresh=5m) is comparable to 0.1 gwei or higher, you might want to suggest to users a higher multiple of the base fee than you would on L1 Ethereum because it can grow faster in the time interval between transaction creation and transaction signing and submission.
#### Recommendations for wallet developers
Wallets are usually in charge of determining the default priority fee and max fee that a transaction would include, so the above recommendations can be applied directly.
#### Recommendations for app developers
As an app developer, you can usually override the default recommendation of the wallet
(see, for example, [ethers](https://github.com/ethers-io/ethers.js/blob/v5.7/packages/contracts/lib/index.d.ts#L10-L11)).
As long as not all wallets are upgraded according to our recommendations, it makes sense for apps to get the current base fee and recommend a value based on that.
#### Recommendations for users
As a user, you are the final authority on transaction fields. Sometimes when submitting a transaction, the gas fee is set too low, and it gets stuck in the transaction pool (a.k.a. mempool). If you want to push that transaction through, [you can cancel it by submitting another transaction with the same nonce](https://info.etherscan.com/how-to-cancel-ethereum-pending-transactions/). This method increases the fee and the sequencer will process it from the mempool quicker.
## Deposit transactions don't have a chainId on L2
[Deposit transactions](https://specs.optimism.io/protocol/deposits.html?utm_source=op-docs\&utm_medium=docs#the-deposited-transaction-type) are transactions added to the L2 blockchain as part of the block derivation process.
These transactions come from a dummy address and don't have a signature.
Because in Ethereum the chainID is encoded as part of the signature, this means there is no recoverable chainID for these transactions.
This is not a problem because the only source of deposit transactions is the block derivation process.
There shouldn't be a need to recover the chainID.
# App developers
Source: https://docs.optimism.io/app-developers/index
Routes app developers to the quickstarts, guides, tutorials, tools, and reference for building on OP Mainnet and other OP Stack chains.
You are building an app on OP Mainnet or another OP Stack chain. Pick the
path that matches where you are: start from zero, solve a specific task, or
look something up.
Go from zero to a working app on an OP Stack chain with the quickstart.
Follow a guide for building and testing apps, bridging, interoperability,
or transactions.
Work through step-by-step tutorials, from deploying a contract to
cross-chain messaging with Supersim.
Choose supported SDKs, faucets, block explorers, and data tools for your
stack.
Find RPC providers, Actions SDK definitions, token lists, and Supersim
reference material.
Route by role from the documentation home: chain operators, node
operators, and protocol learners each have their own section.
# Integrating DeFi with Actions SDK
Source: https://docs.optimism.io/app-developers/quickstarts/actions
Perform DeFi actions with lightweight, composable, and type-safe modules.
Actions SDK is still under construction and not ready for production use! This
guide is meant for early testing purposes only.
The [Actions SDK](https://actions.money/) is an open source Typescript development toolkit that simplifies the act of integrating DeFi into your application.
## How it works
Here's a breakdown of what's under the hood:
* **Modular Providers**: Actions is built with a set of core adapters called "Providers". Providers let you to pick an choose the right services and protocols for your use-case.
* **Embedded Wallets**: Actions supports popular embedded [wallet providers](https://actions.money/#wallet), allowing your users to access DeFi with email authentication flows alone.
* **Configure Actions**: Extend your embedded wallet with DeFi actions like Lend, Borrow, Swap, and Pay. Set multiple providers for each Action to choose the best markets across DeFi.
* **Customize assets & chains**: Allow and block assets, markets, chains, and protocols from your application from a single config.
## Installation
Install the Actions SDK in your project:
```bash npm theme={null}
npm install @eth-optimism/actions-sdk
```
```bash pnpm theme={null}
pnpm add @eth-optimism/actions-sdk
```
```bash yarn theme={null}
yarn add @eth-optimism/actions-sdk
```
```bash bun theme={null}
bun add @eth-optimism/actions-sdk
```
```bash deno theme={null}
deno add @eth-optimism/actions-sdk
```
## Choose a Wallet Provider
Actions works with both frontend and backend wallets depending on your needs:
Select a wallet provider:
**Set Up Privy**
Sign up for Privy and follow the full Privy installation [guide](https://docs.privy.io/basics/react/installation).
**Configure Wallet Provider**
Once you have set up Privy embedded wallets, pass them to Actions SDK:
```typescript theme={null}
import { actions } from './config'
import { useWallets } from '@privy-io/react-auth'
// PRIVY: Fetch wallet
const { wallets } = useWallets()
const embeddedWallet = wallets.find(
(wallet) => wallet.walletClientType === 'privy',
)
// ACTIONS: Let wallet make onchain Actions
const wallet = await actions.wallet.toActionsWallet({
connectedWallet: embeddedWallet,
})
```
**Configure Smart Wallets**
Optionally, create signers for smart wallets you control:
```typescript theme={null}
import { actions } from './config'
import { useWallets } from '@privy-io/react-auth'
// PRIVY: Fetch wallet
const { wallets } = useWallets()
const embeddedWallet = wallets.find(
(wallet) => wallet.walletClientType === 'privy',
)
// ACTIONS: Create signer from hosted wallet
const signer = await actions.wallet.createSigner({
connectedWallet: embeddedWallet,
})
// ACTIONS: Create smart wallet
const { wallet } = await actions.wallet.createSmartWallet({
signer: signer
})
```
**Set Up Turnkey**
Sign up for Turnkey and follow the full Turnkey installation [guide](https://docs.turnkey.com/sdks/react/getting-started).
**Configure Wallet Provider**
Once you have set up Turnkey embedded wallets, pass them to Actions SDK:
```typescript theme={null}
import { useTurnkey, WalletSource } from "@turnkey/react-wallet-kit"
import { actions, USDC, ExampleMarket } from './config'
// Fetch Turnkey wallet
const { wallets, httpClient, session } = useTurnkey()
const embeddedWallet = wallets.find(
(wallet) =>
wallet.accounts.some(
(account) => account.addressFormat === 'ADDRESS_FORMAT_ETHEREUM',
) && wallet.source === WalletSource.Embedded,
)
const walletAddress = embeddedWallet.accounts[0].address
// Convert to Actions wallet
const wallet = await actions.wallet.toActionsWallet({
client: httpClient,
organizationId: session.organizationId,
signWith: walletAddress,
})
// Wallet can now take action
const receipt = await wallet.lend.openPosition({
amount: 100,
asset: USDC,
...ExampleMarket
})
```
**Configure Smart Wallets**
Optionally, create signers for smart wallets you control:
```typescript theme={null}
import { useTurnkey, WalletSource } from "@turnkey/react-wallet-kit"
import { actions } from './config'
// Fetch Turnkey wallet
const { wallets, httpClient, session } = useTurnkey()
const embeddedWallet = wallets.find(
(wallet) =>
wallet.accounts.some(
(account) => account.addressFormat === 'ADDRESS_FORMAT_ETHEREUM',
) && wallet.source === WalletSource.Embedded,
)
const walletAddress = embeddedWallet.accounts[0].address
// Create signer
const signer = await actions.wallet.createSigner({
client: httpClient,
organizationId: session.organizationId,
signWith: walletAddress,
})
// Create smart wallet
const { wallet } = await actions.wallet.createSmartWallet({
signer: signer
})
```
**Set Up Dynamic**
Sign up for Dynamic and follow the full Dynamic installation [guide](https://www.dynamic.xyz/docs/wallets/embedded-wallets/mpc/setup).
**Configure Wallet Provider**
Once you have set up Dynamic embedded wallets, pass them to Actions SDK:
```typescript theme={null}
import { useDynamicContext } from "@dynamic-labs/sdk-react-core"
import { actions, USDC, ExampleMarket } from './config'
// Fetch Dynamic wallet
const { primaryWallet } = useDynamicContext()
// Convert to Actions wallet
const wallet = await actions.wallet.toActionsWallet({
wallet: primaryWallet,
})
// Wallet can now take action
const receipt = await wallet.lend.openPosition({
amount: 100,
asset: USDC,
...ExampleMarket
})
```
**Configure Smart Wallets**
Optionally, create signers for smart wallets you control:
```typescript theme={null}
import { useDynamicContext } from "@dynamic-labs/sdk-react-core"
import { actions } from './config'
// Fetch Dynamic wallet
const { primaryWallet } = useDynamicContext()
// Create signer
const signer = await actions.wallet.createSigner({
wallet: primaryWallet
})
// Create smart wallet
const { wallet } = await actions.wallet.createSmartWallet({
signer: signer
})
```
Select a wallet provider:
**Set Up Privy**
Sign up for Privy and follow the full Privy installation [guide](https://docs.privy.io/basics/nodeJS/installation).
**Configure Wallet Provider**
Once you have set up Privy embedded wallets, pass them to Actions SDK:
```typescript theme={null}
import { actions } from './config'
import { PrivyClient } from '@privy-io/node'
// PRIVY: Create wallet
const privyClient = new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET)
const privyWallet = await privyClient.wallets().create({
chain_type: 'ethereum',
owner: { user_id: 'privy:did:xxxxx' },
})
// ACTIONS: Let wallet make onchain Actions
const wallet = await actions.wallet.toActionsWallet({
walletId: privyWallet.id,
address: privyWallet.address,
})
```
**Configure Smart Wallets**
Optionally, create signers for smart wallets you control:
```typescript theme={null}
import { actions } from './config'
import { PrivyClient } from '@privy-io/node'
import { getAddress } from 'viem'
const privyClient = new PrivyClient(env.PRIVY_APP_ID, env.PRIVY_APP_SECRET)
// PRIVY: Create wallet
const privyWallet = await privyClient.wallets().create({
chain_type: 'ethereum',
owner: { user_id: 'privy:did:xxxxx' },
})
// ACTIONS: Create signer
const signer = await actions.wallet.createSigner({
walletId: privyWallet.id,
address: getAddress(privyWallet.address),
})
// ACTIONS: Create smart wallet
const { wallet } = await actions.wallet.createSmartWallet({
signer: signer
})
```
**Set Up Turnkey**
Sign up for Turnkey and follow the full Turnkey installation [guide](https://docs.turnkey.com/sdks/javascript-server).
**Configure Wallet Provider**
Once you have set up Turnkey embedded wallets, pass them to Actions SDK:
```typescript theme={null}
import { Turnkey } from '@turnkey/sdk-server'
import { actions, USDC, ExampleMarket } from './config'
const turnkeyClient = new Turnkey({
apiBaseUrl: 'https://api.turnkey.com',
apiPublicKey: process.env.TURNKEY_API_KEY,
apiPrivateKey: process.env.TURNKEY_API_SECRET,
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
})
// Create Turnkey wallet
const turnkeyWallet = await turnkeyClient.apiClient().createWallet({
walletName: 'ETH Wallet',
accounts: [{
curve: 'CURVE_SECP256K1',
pathFormat: 'PATH_FORMAT_BIP32',
path: "m/44'/60'/0'/0/0",
addressFormat: 'ADDRESS_FORMAT_ETHEREUM',
}],
})
// Convert to Actions wallet
const wallet = await actions.wallet.toActionsWallet({
organizationId: turnkeyWallet.activity.organizationId,
signWith: turnkeyWallet.addresses[0],
})
// Wallet can now take action
const receipt = await wallet.lend.openPosition({
amount: 100,
asset: USDC,
...ExampleMarket
})
```
**Configure Smart Wallets**
Optionally, create signers for smart wallets you control:
```typescript theme={null}
import { Turnkey } from '@turnkey/sdk-server'
import { actions } from './config'
const turnkeyClient = new Turnkey({
apiBaseUrl: 'https://api.turnkey.com',
apiPublicKey: process.env.TURNKEY_API_KEY,
apiPrivateKey: process.env.TURNKEY_API_SECRET,
defaultOrganizationId: process.env.TURNKEY_ORGANIZATION_ID,
})
// Create Turnkey wallet
const turnkeyWallet = await turnkeyClient.apiClient().createWallet({
walletName: 'ETH Wallet',
accounts: [{
curve: 'CURVE_SECP256K1',
pathFormat: 'PATH_FORMAT_BIP32',
path: "m/44'/60'/0'/0/0",
addressFormat: 'ADDRESS_FORMAT_ETHEREUM',
}],
})
// Create signer
const signer = await actions.wallet.createSigner({
organizationId: turnkeyWallet.activity.organizationId,
signWith: turnkeyWallet.addresses[0],
})
// Create smart wallet
const { wallet } = await actions.wallet.createSmartWallet({
signer: signer
})
```
## Create your ActionsConfig
Follow the [Configuring Actions](/app-developers/guides/configuring-actions) guide to define which protocols, chains, and assets to support.
## Take Action
Once configured, you can use Actions to perform DeFi operations:
```typescript theme={null}
import { USDC, ETH, USDT } from "@eth-optimism/actions-sdk/assets";
import { ExampleMarket } from "@/actions/markets";
// Enable asset lending in DeFi
const lendReceipt = await wallet.lend.openPosition({
amount: 1,
asset: USDC,
...ExampleMarket,
});
// Manage user market positions
const lendPosition = await wallet.lend.getPosition(market);
// Fetch wallet balance
const balance = await wallet.getBalance();
// ⚠️ COMING SOON
const borrowReceipt = await wallet.borrow.openPosition({
amount: 1,
asset: USDT,
...market,
});
// ⚠️ COMING SOON
const swapReceipt = await wallet.swap.execute({
amountIn: 1,
assetIn: USDC,
assetOut: ETH,
});
// ⚠️ COMING SOON
const sendReceipt = await wallet.send({
amount: 1,
asset: USDC,
to: "vitalik.eth",
});
```
## Next Steps
* [Configure Actions](/app-developers/guides/configuring-actions) to customize protocols, chains, and assets
* Check out the [Actions demo](https://actions.money/earn) for a complete example application
# App developer quickstart
Source: https://docs.optimism.io/app-developers/quickstarts/get-started
Get testnet ETH, deploy your first contract to OP Sepolia, and bridge assets - your first steps on the Superchain.
OP Stack chains are [EVM equivalent](/op-stack/protocol/differences): everything you know from Ethereum works here, and everything you build here works across the OP Stack ecosystem.
This quickstart takes you from zero to a deployed contract on the OP Sepolia testnet, using only free testnet funds.
No real funds are needed at any point — everything below runs on testnets.
## Before you start
You'll need:
* A wallet you control (any EVM wallet works; you'll export or generate a private key for testnet use only).
* A terminal with `curl` available.
Use a fresh, testnet-only private key for tutorials. Never paste a key that holds real funds into a terminal.
Grab free OP Sepolia ETH from the [Superchain Faucet](https://console.optimism.io/faucet).
Other options are listed on the [testnet faucets page](/app-developers/tools-sdks/faucets).
OP Sepolia's public RPC endpoint is `https://sepolia.optimism.io` and its chain ID is `11155420`.
Verify you can reach it:
```bash theme={null}
curl -s -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' \
https://sepolia.optimism.io
```
Expected result: `{"jsonrpc":"2.0","id":1,"result":"0xaa37dc"}` (`0xaa37dc` = 11155420).
For other networks and production-grade endpoints, see [network information](/op-mainnet/network-information/connecting-to-op) and [RPC providers](/app-developers/reference/rpc-providers).
Follow the [Deploy a contract to OP Sepolia](/app-developers/tutorials/deploy-a-contract) tutorial.
It walks you through installing Foundry, deploying a small `Greeter` contract, and reading and writing to it from the command line — the same workflow you'd use on Ethereum.
Apps often need to move assets between Ethereum and an OP Stack chain.
Start with [Bridging basics](/app-developers/guides/bridging/basics) to understand the model, then follow the [Bridging ETH with Viem](/app-developers/tutorials/bridging/cross-dom-bridge-eth) tutorial to do a Sepolia → OP Sepolia deposit programmatically.
## Where to go next
Development workflow, tooling, and what (little) is different from Ethereum.
Test against OP Stack chains locally and in CI.
Build apps that span multiple Superchain chains.
Lend, borrow, and swap with lightweight, type-safe modules (early preview — not production-ready).
**Running your app in production**
A production application depends on infrastructure your team does not run: RPC endpoints that hold up under real traffic (the public endpoints are rate-limited and not built for production), bridges your users rely on, and a chain whose operator keeps sequencing, upgrades, and incident response going around the clock. These docs cover building and testing. If your application is growing toward dedicated blockspace of its own, [OP Enterprise](https://optimism.io/op-enterprise?utm_source=docs\&utm_medium=docs\&utm_campaign=op-enterprise) offers managed and supported paths to running a chain. These docs stay the reference for what you build either way. OP Enterprise is Optimism's managed offering.
## Need help?
* Ask a question or report documentation issues on the [Optimism monorepo issue tracker](https://github.com/ethereum-optimism/optimism/issues).
# Integrating wallets
Source: https://docs.optimism.io/app-developers/reference/actions/integrating-wallets
Understand the wallet options Actions SDK supports, embedded wallet providers, gas sponsorship, and smart wallets.
## Which wallets are right for my use case?
In order to let your users access DeFi, they will need an EVM-compatible wallet. There are plenty of considerations when selecting a wallet schema that works for you:
* Who will maintain custody of funds?
* What permissions must exist for my use case?
* Where in my stack should transaction signatures originate?
* How can my users on and off ramp funds?
Actions SDK supports popular [embedded wallet providers](#embedded-wallet-providers) to address all of these questions while remaining flexible to your use case.
## Embedded Wallet Providers
Embedded wallet providers give your users the ability to sign onchain transactions through your app's existing email authentication and authorization flows.
Actions works with:
## Gas Sponsorship
Signing and sending onchain transactions requires gas, or fee payment, which adds [additional overhead](/app-developers/guides/transactions/estimates) for you and friction for users.
Actions supports gas sponsorship via a combination of smart contract wallets and [paymasters](https://eips.ethereum.org/EIPS/eip-7677). First, configure a paymaster in the chain config by specifying a bundler url. Now, any transactions submitted via an actions SmartWallet on that chain will automatically use your paymaster, therefore eliminating the need for the wallet to pay gas.
## Connect your wallet to Actions
Regardless of where and how transactions are signed, Actions has you covered.
Once you've chosen a wallet provider, follow the guide on [connecting a wallet to Actions SDK](/app-developers/guides/connect-wallet-to-actions) to pass the provider wallet to Actions and start calling Actions functions like Lend, Borrow, Swap, and Pay.
## Smart wallets & signers
In addition to using embedded provider wallets directly, Actions [supports the creation](/app-developers/quickstarts/actions#choose-a-wallet-provider) of custom smart contract wallets. This additional wallet type is separate from, but still controlled by the owner of the embedded wallet.
If you [configure it](/app-developers/guides/configuring-actions), Actions will deploy a [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) compliant [Coinbase Smart Wallets](https://github.com/coinbase/smart-wallet) on the chains you've chosen to support.
Once created, an embedded wallet can be added as a signer on the smart wallet, capable of signing transactions on behalf of the Smart Wallet.
See [Wallet Documentation](/app-developers/reference/actions/wallet-definitions) for additional details.
# Lend Documentation
Source: https://docs.optimism.io/app-developers/reference/actions/lend-documentation
API reference for Actions SDK lending operations, functions, and parameters.
## WalletLendNamespace
Wallet Lend Namespace
### Methods
| Function | Description |
| --------------------------------------------- | -------------------------------------------------------- |
| **[getMarkets()](#getmarkets)** | Get all markets across all configured providers |
| **[getMarket()](#getmarket)** | Get a specific market by routing to the correct provider |
| **[supportedChainIds()](#supportedchainids)** | Get supported chain IDs across all providers |
| **[openPosition()](#openposition)** | Open a lending position |
| **[getPosition()](#getposition)** | Get position information for this wallet |
| **[closePosition()](#closeposition)** | Close a lending position (withdraw from market) |
#### `getMarkets()`
Get all markets across all configured providers
| Parameter | Type | Description |
| --------- | ---------------------- | ----------------------------- |
| `params` | `GetLendMarketsParams` | Optional filtering parameters |
**Returns:** Promise resolving to array of markets from all providers
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/lend/namespaces/BaseLendNamespace.ts#L25)
***
#### `getMarket()`
Get a specific market by routing to the correct provider
| Parameter | Type | Description |
| --------- | --------------------- | ----------------- |
| `params` | `GetLendMarketParams` | Market identifier |
**Returns:** Promise resolving to market information
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/lend/namespaces/BaseLendNamespace.ts#L37)
***
#### `supportedChainIds()`
Get supported chain IDs across all providers
**Returns:** Array of unique chain IDs supported by any provider
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/lend/namespaces/BaseLendNamespace.ts#L46)
***
#### `openPosition()`
Open a lending position
| Parameter | Type | Description |
| ----------------- | ------------------------ | ------------------------------------- |
| `params` | `LendOpenPositionParams` | Lending position parameters |
| `params.marketId` | | Market identifier to open position in |
| `params.amount` | `number` | Amount to lend |
**Returns:** Promise resolving to transaction receipt
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/lend/namespaces/WalletLendNamespace.ts#L34)
***
#### `getPosition()`
Get position information for this wallet
| Parameter | Type | Description |
| ----------------- | ------------------- | -------------------------------- |
| `params` | `GetPositionParams` | Position query parameters |
| `params.marketId` | `LendMarketId` | Market identifier (required) |
| `params.asset` | `Asset` | Asset filter (not yet supported) |
**Returns:** Promise resolving to position information
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/lend/namespaces/WalletLendNamespace.ts#L54)
***
#### `closePosition()`
Close a lending position (withdraw from market)
| Parameter | Type | Description |
| ----------------- | --------------------- | -------------------------------------- |
| `params` | `ClosePositionParams` | Position closing parameters |
| `params.marketId` | `LendMarketId` | Market identifier to close position in |
| `params.amount` | `number` | Amount to withdraw |
**Returns:** Promise resolving to transaction receipt
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/lend/namespaces/WalletLendNamespace.ts#L75)
***
# Swap Documentation
Source: https://docs.optimism.io/app-developers/reference/actions/swap-documentation
API reference for Actions SDK swap operations, functions, and parameters.
## WalletSwapNamespace
Wallet swap namespace with full operations including signing.
Provides getQuote() for pricing and execute() for swapping tokens.
### Methods
| Function | Description |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **[getQuote()](#getquote)** | Get a swap quote with the wallet address as recipient. Ensures calldata is encoded for the real wallet, not a placeholder. |
| **[getQuotes()](#getquotes)** | Get quotes from all providers with the wallet address as recipient. |
| **[getMarket()](#getmarket)** | Get a specific swap market by ID. |
| **[getMarkets()](#getmarkets)** | Get available swap markets across all providers |
| **[supportedChainIds()](#supportedchainids)** | Get all supported chain IDs across all providers |
| **[execute()](#execute)** | Execute a token swap. Accepts either raw params (re-quotes internally) or a pre-built SwapQuote (skips re-quoting). |
#### `getQuote()`
Get a swap quote with the wallet address as recipient.
Ensures calldata is encoded for the real wallet, not a placeholder.
| Parameter | Type | Description |
| --------- | ----------------- | ----------- |
| `params` | `SwapQuoteParams` | |
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/swap/namespaces/WalletSwapNamespace.ts#L32)
***
#### `getQuotes()`
Get quotes from all providers with the wallet address as recipient.
| Parameter | Type | Description |
| --------- | ----------------- | ----------- |
| `params` | `SwapQuoteParams` | |
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/swap/namespaces/WalletSwapNamespace.ts#L42)
***
#### `getMarket()`
Get a specific swap market by ID.
| Parameter | Type | Description |
| ---------- | --------------------- | ----------------------------------------------------------------- |
| `params` | `GetSwapMarketParams` | Market identifier (poolId + chainId) |
| `provider` | `SwapProviderName` | Optional provider name to query directly instead of searching all |
**Returns:** Market information
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/swap/namespaces/BaseSwapNamespace.ts#L145)
***
#### `getMarkets()`
Get available swap markets across all providers
| Parameter | Type | Description |
| --------- | ---------------------- | -------------------------------------- |
| `params` | `GetSwapMarketsParams` | Optional filtering by chainId or asset |
**Returns:** Promise resolving to array of markets from all providers
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/swap/namespaces/BaseSwapNamespace.ts#L174)
***
#### `supportedChainIds()`
Get all supported chain IDs across all providers
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/swap/namespaces/BaseSwapNamespace.ts#L184)
***
#### `execute()`
Execute a token swap.
Accepts either raw params (re-quotes internally) or a pre-built SwapQuote (skips re-quoting).
| Parameter | Type | Description | |
| --------- | ------------------ | ----------- | -------------------------------------------------------- |
| `params` | \`WalletSwapParams | SwapQuote\` | Swap parameters or a pre-built SwapQuote from getQuote() |
**Returns:** Swap receipt with transaction details
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/swap/namespaces/WalletSwapNamespace.ts#L55)
***
# Wallet Documentation
Source: https://docs.optimism.io/app-developers/reference/actions/wallet-definitions
API reference for Actions SDK wallet classes, functions, and parameters.
## WalletNamespace
Wallet namespace that provides unified wallet operations
### Methods
| Function | Description |
| --------------------------------------------------- | -------------------------------------------------------- |
| **[hostedWalletProvider()](#hostedwalletprovider)** | Get direct access to the hosted wallet provider |
| **[smartWalletProvider()](#smartwalletprovider)** | Get direct access to the smart wallet provider |
| **[createSmartWallet()](#createsmartwallet)** | Create a new smart wallet |
| **[createSigner()](#createsigner)** | Create a viem LocalAccount signer from the hosted wallet |
| **[toActionsWallet()](#toactionswallet)** | Convert a hosted wallet to an Actions wallet |
| **[getSmartWallet()](#getsmartwallet)** | Get an existing smart wallet with a provided signer |
#### `hostedWalletProvider()`
Get direct access to the hosted wallet provider
**Returns:** Promise resolving to the configured hosted wallet provider instance
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/namespace/WalletNamespace.ts#L86)
***
#### `smartWalletProvider()`
Get direct access to the smart wallet provider
**Returns:** Promise resolving to the configured smart wallet provider instance
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/namespace/WalletNamespace.ts#L98)
***
#### `createSmartWallet()`
Create a new smart wallet
| Parameter | Type | Description |
| --------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------ |
| `params` | `CreateSmartWalletOptions` | Smart wallet creation parameters |
| `params.signer` | `LocalAccount` | Primary local account used for signing transactions |
| `params.signers` | `Signer[]` | Optional array of additional signers for the smart wallet |
| `params.nonce` | `bigint` | Optional nonce for smart wallet address generation (defaults to 0) |
| `params.deploymentChainIds` | `SupportedChainId[]` | Optional chain IDs to deploy the wallet to. |
| If not provided, the wallet will be deployed to all supported chains. | | |
**Returns:** Promise resolving to deployment result containing:
* `wallet`: The created SmartWallet instance
* `deployments`: Array of deployment results with chainId, receipt, success flag, and error
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/namespace/WalletNamespace.ts#L120)
***
#### `createSigner()`
Create a viem LocalAccount signer from the hosted wallet
| Parameter | Type | Description |
| --------- | ------------------------------------ | ---------------------------- |
| `params` | `TToActionsMap[THostedProviderType]` | Configuration for the signer |
**Returns:** Promise resolving to a viem `LocalAccount` with the hosted wallet as the signer backend
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/namespace/WalletNamespace.ts#L136)
***
#### `toActionsWallet()`
Convert a hosted wallet to an Actions wallet
| Parameter | Type | Description |
| ----------------- | ------------------------------------ | -------------------------------------------------------------- |
| `params` | `TToActionsMap[THostedProviderType]` | Parameters for converting a hosted wallet to an Actions wallet |
| `params.walletId` | | Unique identifier for the hosted wallet |
| `params.address` | | Ethereum address of the hosted wallet |
**Returns:** Promise resolving to the Actions wallet instance
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/namespace/WalletNamespace.ts#L151)
***
#### `getSmartWallet()`
Get an existing smart wallet with a provided signer
| Parameter | Type | Description |
| -------------------------- | ----------------------- | ------------------------------------------------------------------ |
| `params` | `GetSmartWalletOptions` | Wallet retrieval parameters |
| `params.signer` | `LocalAccount` | Local account to use for signing transactions on the smart wallet |
| `params.signers` | `Signer[]` | Optional array of additional signers for the smart wallet |
| `params.deploymentSigners` | `Signer[]` | Optional array of signers used during wallet deployment |
| `params.walletAddress` | `Address` | Optional explicit smart wallet address (skips address calculation) |
| `params.nonce` | `bigint` | Optional nonce used during smart wallet creation |
**Returns:** Promise resolving to the smart wallet instance with the provided signer
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/namespace/WalletNamespace.ts#L173)
***
## Wallet
Base actions wallet class
### Namespaces
| Namespace | Type | Description |
| --------- | --------------------- | ------------------------------------------ |
| `lend` | `WalletLendNamespace` | Lend namespace with all lending operations |
| `swap` | `WalletSwapNamespace` | Swap namespace with all swap operations |
### Properties
| Property | Type | Description |
| --------- | -------------- | -------------------------------------- |
| `address` | `Address` | Get the address of this actions wallet |
| `signer` | `LocalAccount` | Get a signer for this actions wallet |
### Methods
| Function | Description |
| ------------------------------- | ------------------------------------------------------ |
| **[getBalance()](#getbalance)** | Get asset balances across all supported chains |
| **[send()](#send)** | Send a transaction using this actions wallet |
| **[sendBatch()](#sendbatch)** | Send a batch of transactions using this actions wallet |
#### `getBalance()`
Get asset balances across all supported chains
**Returns:** Promise resolving to array of token balances with chain breakdown
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/wallets/abstract/Wallet.ts#L89)
***
#### `send()`
Send a transaction using this actions wallet
| Parameter | Type | Description |
| ----------------- | ------------------ | ------------------------------- |
| `transactionData` | `TransactionData` | The transaction data to execute |
| `chainId` | `SupportedChainId` | Target blockchain chain ID |
**Returns:** Promise resolving to the transaction hash
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/wallets/abstract/Wallet.ts#L150)
***
#### `sendBatch()`
Send a batch of transactions using this actions wallet
| Parameter | Type | Description |
| ----------------- | ------------------- | ------------------------------- |
| `transactionData` | `TransactionData[]` | The transaction data to execute |
| `chainId` | `SupportedChainId` | Target blockchain chain ID |
**Returns:** Promise resolving to the transaction hash
[ Source ↗](https://github.com/ethereum-optimism/actions/blob/@eth-optimism/actions-sdk@0.4.0/packages/sdk/src/wallet/core/wallets/abstract/Wallet.ts#L162)
***
# Interoperability predeploys
Source: https://docs.optimism.io/app-developers/reference/contracts/interop/predeploy
Learn how interoperability predeploys work.
OP Stack interop is in active development. Some features may be experimental.
The following predeploys have been added to enable interoperability.
*Predeployed smart contracts* exist at predetermined addresses, coming from the genesis state.
They're similar to [precompiles](https://www.evm.codes/precompiled) but run directly in the EVM instead of running as native code.
## CrossL2Inbox
The `CrossL2Inbox` is the system predeploy for cross chain messaging.
Anyone can trigger the execution or validation of cross chain messages, on behalf of any user.
* **Address:** `0x4200000000000000000000000000000000000022`
* **Specs:** [`CrossL2Inbox`](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs\&utm_medium=docs#crossl2inbox)
* **Source code:** [`CrossL2Inbox`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/CrossL2Inbox.sol)
## L2ToL2CrossDomainMessenger
The `L2ToL2CrossDomainMessenger` is a higher level abstraction on top of the `CrossL2Inbox` that provides general message passing.
It's utilized for secure ERC20 token transfers between L2 chains.
Messages sent through the `L2ToL2CrossDomainMessenger` on the source chain receive both replay protection and domain binding (the executing transaction can only be valid on a single chain).
* **Address:** `0x4200000000000000000000000000000000000023`
* **Specs:** [`L2ToL2CrossDomainMessenger`](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs\&utm_medium=docs#l2tol2crossdomainmessenger)
* **Source code:** [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol)
## SuperchainETHBridge
The `SuperchainETHBridge` is a predeploy contract that facilitates cross-chain ETH bridging within the OP Stack interop cluster. It serves as an abstraction layer on top of the `L2ToL2CrossDomainMessenger` specifically designed for native ETH transfers between chains. The contract integrates with the `ETHLiquidity` contract to manage native ETH liquidity across chains, ensuring seamless cross-chain transfers of native ETH.
* **Address:** `0x4200000000000000000000000000000000000024`
* **Specs:** [`SuperchainETHBridge`](https://specs.optimism.io/interop/superchain-eth-bridge.html)
* **Source code:** [`SuperchainETHBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainETHBridge.sol)
## ETHLiquidity
The `ETHLiquidity` contract is a predeploy that manages native ETH liquidity for cross-chain transfers within the OP Stack interop set. It works in conjunction with the `SuperchainETHBridge` to facilitate the movement of ETH between chains without requiring modifications to the EVM to generate new ETH.
The contract is initialized with a very large balance (type(uint248).max wei) to ensure it can handle all legitimate minting operations. This design allows the `SuperchainETHBridge` to have a guaranteed source of ETH liquidity on each chain, which is essential for the cross-chain ETH transfer mechanism.
* **Address:** `0x4200000000000000000000000000000000000025`
* **Specs:** [`ETHLiquidity`](https://specs.optimism.io/interop/eth-liquidity.html)
* **Source code:** [`ETHLiquidity`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/ETHLiquidity.sol)
## SuperchainTokenBridge
The `SuperchainTokenBridge` is an abstraction on top of the `L2ToL2CrossDomainMessenger` that facilitates token bridging using interop, as described in the [token bridging spec](https://specs.optimism.io/interop/token-bridging.html).
* **Address:** `0x4200000000000000000000000000000000000028`
* **Specs:** [`SuperchainTokenBridge`](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs\&utm_medium=docs#superchaintokenbridge)
* **Source code:** [`SuperchainTokenBridge`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol)
## Next steps
* Learn [how messages get from one chain to another chain](/app-developers/guides/interoperability/message-passing)
# OP Stack RPC directory
Source: https://docs.optimism.io/app-developers/reference/rpc-providers
Find public RPC endpoints and production RPC providers across all OP Stack networks.
This directory provides developers with a comprehensive collection of RPC endpoints across all OP Stack networks, making it easier to build, deploy, and scale applications on the OP Stack ecosystem.
## Public RPC endpoints
The following table lists public RPC endpoints for all OP Stack networks. These endpoints are **rate-limited and not suitable for production use**, but are perfect for development, testing, and proof-of-concept work.
For production use, please see the [Production RPC Providers](#production-rpc-providers) section below.
| Chain | Public RPC URL | Documentation |
| ---------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| OP Mainnet | [https://mainnet.optimism.io](https://mainnet.optimism.io) | [Docs](https://docs.optimism.io/superchain/networks) |
| Base | [https://mainnet.base.org](https://mainnet.base.org) | [Docs](https://docs.base.org/chain/network-information) |
| Ink | [https://rpc-gel.inkonchain.com](https://rpc-gel.inkonchain.com) | [Docs](https://docs.inkonchain.com/general/network-information) |
| Unichain | [https://mainnet.unichain.org](https://mainnet.unichain.org) | [Docs](https://docs.unichain.org/docs/technical-information/network-information) |
| Soneium | [https://rpc.soneium.org/](https://rpc.soneium.org/) | [Docs](https://docs.soneium.org/docs/builders/overview) |
| Mode | [https://mainnet.mode.network/](https://mainnet.mode.network/) | [Docs](https://docs.mode.network/user-guides/network-details) |
| Zora | [https://rpc.zora.energy](https://rpc.zora.energy) | [Docs](https://docs.zora.co/zora-network/network) |
| Swell | [https://swell-mainnet.alt.technology](https://swell-mainnet.alt.technology) | [Docs](https://build.swellnetwork.io/docs/guides/getting-started) |
| Arena-Z | [https://rpc.arena-z.gg](https://rpc.arena-z.gg) | [Docs](https://raas.gelato.network/rollups/details/public/arena-z) |
| Metal | [https://rpc.metall2.com](https://rpc.metall2.com) | [Docs](https://docs.metall2.com/chain/networks) |
| World | [https://worldchain-mainnet.g.alchemy.com/public](https://worldchain-mainnet.g.alchemy.com/public) | [Docs](https://docs.world.org/world-chain/quick-start/info) |
| Lisk | [https://rpc.api.lisk.com](https://rpc.api.lisk.com) | [Docs](https://docs.lisk.com/network-info/) |
| Polynomial | [https://rpc.polynomial.fi](https://rpc.polynomial.fi) | [Docs](https://docs.polynomial.fi/links) |
| Mint | [https://rpc.mintchain.io](https://rpc.mintchain.io) | [Docs](https://docs.mintchain.io/build/network) |
| Superseed | [https://mainnet.superseed.xyz](https://mainnet.superseed.xyz) | [Docs](https://docs.superseed.xyz/build-on-superseed/network-information) |
| Shape | [https://mainnet.shape.network](https://mainnet.shape.network) | [Docs](https://docs.shape.network/technical-details/network-information) |
| Epic | [https://mainnet.ethernitychain.io](https://mainnet.ethernitychain.io) | [Docs](https://docs.ethernity.io/introduction/network-information) |
| Race | [https://racemainnet.io](https://racemainnet.io) | [Docs](https://raceecosystem.gitbook.io/docs/building-on-race-tm/network-information) |
| BoB | [https://rpc.gobob.xyz/](https://rpc.gobob.xyz/) | [Docs](https://docs.gobob.xyz/learn/user-guides/getting-started/networks) |
| Chain | Public RPC URL | Documentation |
| ------------------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| OP Sepolia | [https://sepolia.optimism.io](https://sepolia.optimism.io) | [Docs](https://docs.optimism.io/superchain/networks) |
| Base Sepolia | [https://sepolia.base.org](https://sepolia.base.org) | [Docs](https://docs.base.org/chain/network-information) |
| Ink Sepolia | [https://rpc-gel-sepolia.inkonchain.com](https://rpc-gel-sepolia.inkonchain.com) | [Docs](https://docs.inkonchain.com/general/network-information) |
| Unichain Sepolia | [https://sepolia.unichain.org](https://sepolia.unichain.org) | [Docs](https://docs.unichain.org/docs/technical-information/network-information) |
| Soneium Sepolia | [https://rpc.minato.soneium.org/](https://rpc.minato.soneium.org/) | [Docs](https://docs.soneium.org/docs/builders/overview) |
| Mode Sepolia | [https://sepolia.mode.network](https://sepolia.mode.network) | [Docs](https://docs.mode.network/user-guides/network-details) |
| Zora Sepolia | [https://sepolia.rpc.zora.energy](https://sepolia.rpc.zora.energy) | [Docs](https://docs.zora.co/zora-network/network) |
| Swell Sepolia | [https://swell-testnet.alt.technology](https://swell-testnet.alt.technology) | [Docs](https://build.swellnetwork.io/docs/guides/getting-started) |
| Arena-Z Testnet | [https://rpc.arena-z.t.raas.gelato.cloud](https://rpc.arena-z.t.raas.gelato.cloud) | [Docs](https://raas.gelato.network/rollups/details/public/arena-z) |
| Metal Testnet | [https://testnet.rpc.metall2.com/](https://testnet.rpc.metall2.com/) | [Docs](https://docs.metall2.com/chain/networks) |
| World Sepolia | [https://worldchain-sepolia.g.alchemy.com/public](https://worldchain-sepolia.g.alchemy.com/public) | [Docs](https://docs.world.org/world-chain/quick-start/info) |
| Lisk Sepolia | [https://rpc.sepolia-api.lisk.com](https://rpc.sepolia-api.lisk.com) | [Docs](https://docs.lisk.com/network-info/) |
| Polynomial Sepolia | [https://rpc.sepolia.polynomial.fi](https://rpc.sepolia.polynomial.fi) | [Docs](https://docs.polynomial.fi/links) |
| Mint Sepolia | [https://sepolia-testnet-rpc.mintchain.io](https://sepolia-testnet-rpc.mintchain.io) | [Docs](https://docs.mintchain.io/build/network) |
| Superseed Sepolia | [https://sepolia.superseed.xyz](https://sepolia.superseed.xyz) | [Docs](https://docs.superseed.xyz/build-on-superseed/network-information) |
| Shape Sepolia | [https://sepolia.shape.network](https://sepolia.shape.network) | [Docs](https://docs.shape.network/technical-details/network-information) |
| Epic Testnet | [https://testnet.ethernitychain.io](https://testnet.ethernitychain.io) | [Docs](https://docs.ethernity.io/introduction/network-information) |
| Race Testnet | [https://racetestnet.io](https://racetestnet.io) | [Docs](https://raceecosystem.gitbook.io/docs/building-on-race-tm/network-information) |
| BoB Sepolia | [https://sepolia.rpc.gobob.xyz/](https://sepolia.rpc.gobob.xyz/) | [Docs](https://docs.gobob.xyz/learn/user-guides/getting-started/networks) |
## Production RPC providers
The following providers offer production-grade RPC access to OP Stack networks. Most providers offer both free tiers with higher rate limits than public RPCs and paid plans for production applications.
### 1RPC
**Description**: [1RPC](https://www.1rpc.io/) offers [free and paid plans](https://www.1rpc.io/#pricing) for the following [networks](https://docs.1rpc.io/using-the-web3-api/networks):
**Supported Testnets**: Mode Sepolia, World Sepolia
**Supported Mainnets**: OP Mainnet, Mode, Base
### Ankr
**Description**: [Ankr](https://www.ankr.com/) offers [free and paid plans](https://www.ankr.com/rpc/pricing/) for the following [networks](https://www.ankr.com/rpc/):
**Supported Testnets**: OP Sepolia, Swell Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Swell, Base
### Alchemy
**Description**: [Alchemy](https://www.alchemy.com/) offers [free and paid plans](https://www.alchemy.com/pricing) for the following [networks](https://docs.alchemy.com/reference/api-overview):
**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Soneium Sepolia, Base Sepolia, World Sepolia, Shape Sepolia
**Supported Mainnets**: OP Mainnet, Ink, Unichain, Soneium, Base, World, Shape
### All That Node
**Description**: [All That Node](https://www.allthatnode.com/) offers [free and paid plans](https://www.allthatnode.com/pricing.dsrv) for the following [networks](https://docs.allthatnode.com/docs/supported-protocols-1):
**Supported Testnets**: OP Sepolia
**Supported Mainnets**: OP Mainnet, Base
### Blast
**Description**: [Blast](https://blastapi.io/) offers [free and paid plans](https://blastapi.io/pricing) for the following [networks](https://blastapi.io/chains):
**Supported Testnets**: OP Sepolia, Mode Sepolia, Base Sepolia, BoB Sepolia
**Supported Mainnets**: OP Mainnet, Mode, Base, BoB
### Blockdaemon
**Description**: [Blockdaemon](https://www.blockdaemon.com/) offers [free and paid plans](https://www.blockdaemon.com/api/pricing) for the following [networks](https://www.blockdaemon.com/protocols):
**Supported Testnets**: OP Sepolia, Unichain Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Unichain, Base
### BlockPI
**Description**: [BlockPI](https://blockpi.io/) offers [free and paid plans](https://blockpi.io/pricing) for the following [networks](https://blockpi.io/chains):
**Supported Testnets**: OP Sepolia, Unichain Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Unichain, Base
### Chainstack
**Description**: [Chainstack](https://chainstack.com/) offers [free and paid plans](https://chainstack.com/pricing/) for the following [networks](https://chainstack.com/protocols/):
**Supported Testnets**: OP Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Base
### dRPC NodeCloud
**Description**: [dRPC](https://drpc.org/nodecloud-multichain-rpc-management) offers [free and paid plans](https://drpc.org/pricing) for the following [networks](https://drpc.org/chainlist):
**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Soneium Sepolia, Mode Sepolia, Zora Sepolia, Swell Sepolia, Metal Sepolia, Base Sepolia, World Sepolia, Lisk Sepolia, Superseed Sepolia, BoB Sepolia
**Supported Mainnets**: OP Mainnet, Ink, Unichain, Soneium, Mode, Zora, Swell, Metal, Base, World, Lisk, Superseed, BoB
### GetBlock
**Description**: [GetBlock](https://getblock.io/) offers [free and paid plans](https://getblock.io/pricing/) for the following [networks](https://docs.getblock.io/api-reference/overview#supported-networks):
**Supported Testnets**: OP Sepolia
**Supported Mainnets**: OP Mainnet
### Grove
**Description**: [Grove](https://grove.city/) offers [free and paid plans](https://www.grove.city/pricing) for the following [networks](https://www.grove.city/services):
**Supported Testnets**: OP Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Base Mainnet, Ink Mainnet
### Infura
**Description**: [Infura](https://infura.io) offers [free and paid plans](https://www.infura.io/pricing) for the following [networks](https://www.infura.io/networks):
**Supported Testnets**: OP Sepolia, Unichain Sepolia, Swell Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Unichain, Swell, Base
### Moralis
**Description**: [Moralis](https://moralis.io) offers [free and paid plans](https://developers.moralis.com/pricing/) for the following [networks](https://developers.moralis.com/chains/):
**Supported Testnets**: OP Sepolia, Base Sepolia, Lisk Sepolia
**Supported Mainnets**: OP Mainnet, Base, Lisk
### Nodies DLB
**Description**: [Nodies DLB](https://www.nodies.app/) offers [free and paid plans](https://www.nodies.app/pricing) for the following [networks](https://docs.nodies.app/overview/supported-blockchains):
**Supported Testnets**: OP Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Ink, Base
### NOWNodes
**Description**: [NOWNodes](https://nownodes.io/) offers [free and paid plans](https://nownodes.io/pricing) for the following [networks](https://nownodes.io/nodes):
**Supported Testnets**: n/a
**Supported Mainnets**: OP Mainnet, Base, Lisk
### OnFinality
**Description**: [OnFinality](https://onfinality.io/) offers [free and paid plans](https://onfinality.io/pricing) for the following [networks](https://onfinality.io/networks):
**Supported Testnets**: OP Sepolia, Unichain Sepolia, Base Sepolia
**Supported Mainnets**: OP Mainnet, Unichain, Base
### QuickNode
**Description**: [QuickNode](https://www.quicknode.com/) offers [free and paid plans](https://www.quicknode.com/pricing) for the following [networks](https://www.quicknode.com/chains):
**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Base Sepolia, World Sepolia, Race Sepolia
**Supported Mainnets**: OP Mainnet, Ink, Unichain, Mode, Zora, Base, World, Lisk, Race
### RockX
**Description**: [RockX](https://www.rockx.com/) offers [free and paid plans](https://access.rockx.com/product/optimism-blockchain-api-for-web3-builders) for the following [networks](https://access.rockx.com/):
**Supported Testnets**: n/a
**Supported Mainnets**: OP Mainnet, Base
### Tenderly
**Description**: [Tenderly](https://tenderly.co/) offers [free and paid plans](https://tenderly.co/pricing) for the following [networks](https://docs.tenderly.co/node/rpc-reference):
**Supported Testnets**: OP Sepolia, Ink Sepolia, Unichain Sepolia, Soneium Sepolia, Mode Sepolia, Swell Sepolia, Base Sepolia, World Sepolia, Lisk Sepolia, Polynomial Sepolia, BoB Sepolia
**Supported Mainnets**: OP Mainnet, Ink, Unichain, Soneium, Mode, Swell, Base, World, Lisk, Polynomial, BoB
### Validation Cloud
**Description**: [Validation Cloud](https://www.validationcloud.io/) offers [free and paid plans](https://www.validationcloud.io/node) for the following [networks](https://docs.validationcloud.io/v1/optimism/overview):
**Supported Testnets**: n/a
**Supported Mainnets**: OP Mainnet, Base
## Directory governance
The OP Stack RPC Directory is maintained by OP Labs with the following policies:
* Providers must submit a docs PR to the [docs](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/) to be added
* To be listed, providers must support at least one network in the [OP Stack ecosystem](/op-stack/protocol/superchain-registry)
* Anyone can submit a PR to remove a provider that does not support a listed network
## Next steps
* Want to run your own node? See the [Node operators overview](/node-operators/overview).
* Looking for other developer tools? See [developer tools overview](/app-developers/tools) to explore more options!
# Bridged token addresses
Source: https://docs.optimism.io/app-developers/reference/tokens/tokenlist
This reference guide lists the correct bridged token addresses for each token.
Various ERC-20 tokens originally deployed to Ethereum also have corresponding "bridged" representations on OP Mainnet.
The [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) exists to help users discover the correct bridged token addresses for each token.
This page is automatically generated from the Superchain Token List.
**Tokens listed on this page are provided for convenience only** and are automatically derived from the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io).
**The presence of a token on this page does not imply any endorsement of the token or its minter.**
### USDC on OP Mainnet
The legacy bridged version of USDC (USDC.e) at address `0x7f5c764cbc14f9669b88837ca1490cca17c31607` is being deprecated on OP Mainnet.
Users and developers should migrate to using the native USDC token issued directly by [Circle](https://www.circle.com/en/), the issuer of [USDC](https://www.circle.com/en/usdc?gad_source=1).
Information about the bridged `USDC.e` token and native USDC token can be found below.
| Symbol | Description | Address |
| -------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `USDC.e` | Bridged USDC from Ethereum | [`0x7f5c764cbc14f9669b88837ca1490cca17c31607`](https://explorer.optimism.io/token/0x7f5c764cbc14f9669b88837ca1490cca17c31607) |
| `USDC` | Native USDC issued by Circle | [`0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85`](https://explorer.optimism.io/token/0x0b2c639c533813f4aa9d7837caf62653d097ff85) |
## OP Mainnet
For a complete and up-to-date list of bridged token addresses for OP Mainnet (Chain ID: 10), please refer to the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) repository.
## OP Sepolia
For a complete and up-to-date list of bridged token addresses for OP Sepolia (Chain ID: 11155420), please refer to the [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io) repository.
# OPChainA (chainID 901)
Source: https://docs.optimism.io/app-developers/reference/tools/supersim/chain-a
Learn network details and contract addresses for OPChainA (chainID 901).
This guide provides network details and contract addresses for OPChainA (chainID 901) when running `supersim` vanilla mode.
## Network details
| **Parameter** | **Value** |
| ------------- | ---------------------------------------------- |
| **Name** | OPChainA |
| **Chain ID** | 901 |
| **RPC URL** | [http://127.0.0.1:9545](http://127.0.0.1:9545) |
## Contract addresses
### L1 contracts
```json theme={null}
{
"AddressManager": "0x78d21C9820A9135215202A9a8D6521483D4b75cD",
"AnchorStateRegistry": "0x21799f09394c50220CCD95E7dAc1cdD774FC871a",
"AnchorStateRegistryProxy": "0xa6F40d5770b3509aB40B2effa5cb544D29743ec7",
"DelayedWETH": "0x49BBFf1629824A1e7993Ab5c17AFa45D24AB28c9",
"DelayedWETHProxy": "0x309DA6B9a8fE16afD7D067528d358E55314bEa6b",
"DisputeGameFactory": "0x20B168142354Cee65a32f6D8cf3033E592299765",
"DisputeGameFactoryProxy": "0x444689B81D485bc58AB81aC02A95a937fAa152D7",
"L1CrossDomainMessenger": "0x094e6508ba9d9bf1ce421fff3dE06aE56e67901b",
"L1CrossDomainMessengerProxy": "0xcd712b03bc6424BF45cE6C29Fc90FFDece228F6E",
"L1ERC721Bridge": "0x5C4F5e749A61a9503c4AAE8a9393e89609a0e804",
"L1ERC721BridgeProxy": "0x018dC24a6617c47cAa00C3fA25097214B2D4F447",
"L1StandardBridge": "0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b",
"L1StandardBridgeProxy": "0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0",
"L2OutputOracle": "0x19652082F846171168Daf378C4fD3ee85a0D4A60",
"L2OutputOracleProxy": "0x6cE0530E823e23be85D8e151FB023605eB4F6d43",
"Mips": "0xB3A0348310a0ff78E5FbDB7f14BB7d3e02d40773",
"OptimismMintableERC20Factory": "0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567",
"OptimismMintableERC20FactoryProxy": "0x15c855966C196Be3a8ca747E8A8Bf40928d4741f",
"OptimismPortal": "0x37a418800d0c812A9dE83Bc80e993A6b76511B57",
"OptimismPortal2": "0xfcbb237388CaF5b08175C9927a37aB6450acd535",
"OptimismPortalProxy": "0xF5fe61a258CeBb54CCe428F76cdeD04Cbc12F53d",
"PreimageOracle": "0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75",
"ProtocolVersions": "0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F",
"ProtocolVersionsProxy": "0x6dA4f6489039d9f4F3144954DDF5bb2F4986e90b",
"ProxyAdmin": "0xe32a4D31ffD5596542DAc8239a1DE3Fff9d63475",
"SafeProxyFactory": "0x4a05c09875DE2DD5B81Bc01dd46eD4699b181bfA",
"SafeSingleton": "0x99A395CE6d6b37CaaCBad64fB42d556b6CA73a48",
"SuperchainConfig": "0x068E44eB31e111028c41598E4535be7468674D0A",
"SuperchainConfigProxy": "0x7E6c6ebCF109fa23277b86bdA39738035C21BB86",
"SystemConfig": "0x6167B477F8d9138aa509f54b2800443857e28c0f",
"SystemConfigProxy": "0xf32919Ed2490b56EaD65E72749894aE4C9523320",
"SystemOwnerSafe": "0xc052b7316C87390E555aF97D42bCd5FB6d5eEFDa"
}
```
### L2 contracts
```json theme={null}
{
// OP Stack predeploys
"L2ToL1MessagePasser": "0x4200000000000000000000000000000000000016",
"L2CrossDomainMessenger": "0x4200000000000000000000000000000000000007",
"L2StandardBridge": "0x4200000000000000000000000000000000000010",
"L2ERC721Bridge": "0x4200000000000000000000000000000000000014",
"SequencerFeeVault": "0x4200000000000000000000000000000000000011",
"OptimismMintableERC20Factory": "0x4200000000000000000000000000000000000012",
"OptimismMintableERC721Factory": "0x4200000000000000000000000000000000000017",
"L1BlockInterop": "0x4200000000000000000000000000000000000015",
"GasPriceOracle": "0x420000000000000000000000000000000000000F",
"ProxyAdmin": "0x4200000000000000000000000000000000000018",
"BaseFeeVault": "0x4200000000000000000000000000000000000019",
"L1FeeVault": "0x420000000000000000000000000000000000001A",
"OperatorFeeVault": "0x420000000000000000000000000000000000001B",
"GovernanceToken": "0x4200000000000000000000000000000000000042",
"SchemaRegistry": "0x4200000000000000000000000000000000000020",
"EAS": "0x4200000000000000000000000000000000000021",
"CrossL2Inbox": "0x4200000000000000000000000000000000000022",
"L2ToL2CrossDomainMessenger": "0x4200000000000000000000000000000000000023",
"SuperchainETHBridge": "0x4200000000000000000000000000000000000024",
"SuperchainTokenBridge": "0x4200000000000000000000000000000000000028",
// Periphery
"L2NativeSuperchainERC20": "0x420beeF000000000000000000000000000000001"
}
```
## Next steps
* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline.
- For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs\&utm_medium=docs).
# OPChainB (chainID 902)
Source: https://docs.optimism.io/app-developers/reference/tools/supersim/chain-b
Learn network details and contract addresses for OPChainB (chainID 902).
This guide provides network details and contract addresses for OPChainB (chainID 902) when running `supersim` vanilla mode.
## Network details
| **Parameter** | **Value** |
| ------------- | ---------------------------------------------- |
| **Name** | OPChainB |
| **Chain ID** | 902 |
| **RPC URL** | [http://127.0.0.1:9546](http://127.0.0.1:9546) |
## Contract addresses
### L1 contracts
```json theme={null}
{
"AddressManager": "0xafB51A0f73C8409AeA1207DF7f39885c927BeA46",
"AnchorStateRegistry": "0x05493149c84A71063f7948127bb931f8377F779C",
"AnchorStateRegistryProxy": "0xfd0269a716A59fF125Bd7eb65Cd3427C8555bab7",
"DelayedWETH": "0x49BBFf1629824A1e7993Ab5c17AFa45D24AB28c9",
"DelayedWETHProxy": "0xA63353128502269b4A4A4c2677fE316cd9ad4397",
"DisputeGameFactory": "0x20B168142354Cee65a32f6D8cf3033E592299765",
"DisputeGameFactoryProxy": "0x5F416fEb15c8B382d338FDBDb7D44967ca2b59BC",
"L1CrossDomainMessenger": "0x094e6508ba9d9bf1ce421fff3dE06aE56e67901b",
"L1CrossDomainMessengerProxy": "0xeCA0f912b4bd255f3851951caE5775CC9400aA3B",
"L1ERC721Bridge": "0x5C4F5e749A61a9503c4AAE8a9393e89609a0e804",
"L1ERC721BridgeProxy": "0xDCE41E6C0901586EE27Eac329EBD4b5fe5A7170d",
"L1StandardBridge": "0xb7900B27Be8f0E0fF65d1C3A4671e1220437dd2b",
"L1StandardBridgeProxy": "0x67B2aB287a32bB9ACe84F6a5A30A62597b10AdE9",
"L2OutputOracle": "0x19652082F846171168Daf378C4fD3ee85a0D4A60",
"L2OutputOracleProxy": "0x006Af3fB62c4BE4fB0393995d364BbFe6b0F3CB2",
"Mips": "0xB3A0348310a0ff78E5FbDB7f14BB7d3e02d40773",
"OptimismMintableERC20Factory": "0x39Aea2Dd53f2d01c15877aCc2791af6BDD7aD567",
"OptimismMintableERC20FactoryProxy": "0x1A2A942d891e525D1Ab192578a378980729fD585",
"OptimismPortal": "0x35e67BC631C327b60C6A39Cff6b03a8adBB19c2D",
"OptimismPortal2": "0xfcbb237388CaF5b08175C9927a37aB6450acd535",
"OptimismPortalProxy": "0xdfC9DEAbEEbDaa7620C71e2E76AEda32919DE5f2",
"PreimageOracle": "0x3bd7E801E51d48c5d94Ea68e8B801DFFC275De75",
"ProtocolVersions": "0xfbfD64a6C0257F613feFCe050Aa30ecC3E3d7C3F",
"ProtocolVersionsProxy": "0xE139cB0CDa5EF722870068ea331d0989776A7aDf",
"ProxyAdmin": "0xff5E6C2Af859f70B875BA59B958BEde60E36bf69",
"SafeProxyFactory": "0xb68f3B057fE3c6CdDF9DB35837Ea769FCc81978a",
"SafeSingleton": "0xeeB44D84d505AbD958d032e90704c56443eB3ED0",
"SuperchainConfig": "0x068E44eB31e111028c41598E4535be7468674D0A",
"SuperchainConfigProxy": "0x2ED4AA34573c36bF3856e597501aEf9d9Dc1687C",
"SystemConfig": "0x6167B477F8d9138aa509f54b2800443857e28c0f",
"SystemConfigProxy": "0x2Db03FE998D7c20E4B65afD1f50f04Ec4BfAb694",
"SystemOwnerSafe": "0xBF3830711B7c559042453B7546dB4736eFB4245e"
}
```
### L2 contracts
```json theme={null}
{
// OP Stack predeploys
"L2ToL1MessagePasser": "0x4200000000000000000000000000000000000016",
"L2CrossDomainMessenger": "0x4200000000000000000000000000000000000007",
"L2StandardBridge": "0x4200000000000000000000000000000000000010",
"L2ERC721Bridge": "0x4200000000000000000000000000000000000014",
"SequencerFeeVault": "0x4200000000000000000000000000000000000011",
"OptimismMintableERC20Factory": "0x4200000000000000000000000000000000000012",
"OptimismMintableERC721Factory": "0x4200000000000000000000000000000000000017",
"L1BlockInterop": "0x4200000000000000000000000000000000000015",
"GasPriceOracle": "0x420000000000000000000000000000000000000F",
"ProxyAdmin": "0x4200000000000000000000000000000000000018",
"BaseFeeVault": "0x4200000000000000000000000000000000000019",
"L1FeeVault": "0x420000000000000000000000000000000000001A",
"OperatorFeeVault": "0x420000000000000000000000000000000000001B",
"GovernanceToken": "0x4200000000000000000000000000000000000042",
"SchemaRegistry": "0x4200000000000000000000000000000000000020",
"EAS": "0x4200000000000000000000000000000000000021",
"CrossL2Inbox": "0x4200000000000000000000000000000000000022",
"L2ToL2CrossDomainMessenger": "0x4200000000000000000000000000000000000023",
"SuperchainETHBridge": "0x4200000000000000000000000000000000000024",
"SuperchainTokenBridge": "0x4200000000000000000000000000000000000028",
// Periphery
"L2NativeSuperchainERC20": "0x420beeF000000000000000000000000000000001"
}
```
## Next steps
* Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline.
- For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs\&utm_medium=docs).
# Fork mode
Source: https://docs.optimism.io/app-developers/reference/tools/supersim/fork
Learn how to fork Supersim.
Supersim fork mode to simulate and interact with the state of the chain without needing to re-deploy or modify the contracts. This is possible if you're relying on contracts already deployed on testnet / mainnet chains.
```sh theme={null}
supersim fork
```
## How it works
The `supersim` fork command simplifies the process of forking multiple chains in the OP Stack ecosystem simultaneously. It determines the appropriate block heights for each chain and launches both the L1 and L2 chains based on these values.
This allows you to locally fork any chain in a superchain network of the [superchain registry](https://github.com/ethereum-optimism/superchain-registry), default `mainnet` versions.
### Example startup logs
```
Available Accounts
-----------------------
(0): 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
--- truncated for brevity ---
Private Keys
-----------------------
(0): 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
--- truncated for brevity ---
Orchestrator Config:
L1:
Name: mainnet Chain ID: 1 RPC: http://127.0.0.1:8545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-1-1521250718
L2:
Name: op Chain ID: 10 RPC: http://127.0.0.1:9545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-10
Name: base Chain ID: 8453 RPC: http://127.0.0.1:9546 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-8453
Name: zora Chain ID: 7777777 RPC: http://127.0.0.1:9547 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-7777777
```
## Configuration
```
NAME:
supersim fork - Locally fork a network in the superchain registry
USAGE:
supersim fork [command options]
OPTIONS:
--l1.fork.height value (default: 0) ($SUPERSIM_L1_FORK_HEIGHT)
L1 height to fork the superchain (bounds L2 time). `0` for latest
--chains value ($SUPERSIM_CHAINS)
chains to fork in the superchain, mainnet options: [arena-z, automata, base,
bob, cyber, ethernity, funki, hashkeychain, ink, lisk, lyra, metal, mint, mode,
op, orderly, polynomial, race, redstone, shape, soneium, sseed, swan, swell, tbn,
unichain, worldchain, xterio-eth, zora]. In order to replace the public rpc endpoint
for a chain, specify the ($SUPERSIM_RPC_URL_) env variable. i.e
SUPERSIM_RPC_URL_OP=http://optimism-mainnet.infura.io/v3/
--network value (default: "mainnet") ($SUPERSIM_NETWORK)
superchain network. options: mainnet, sepolia, sepolia-dev-0. In order to
replace the public rpc endpoint for the network, specify the
($SUPERSIM_RPC_URL_) env variable. i.e
SUPERSIM_RPC_URL_MAINNET=http://mainnet.infura.io/v3/
--interop.enabled (default: true) ($SUPERSIM_INTEROP_ENABLED)
enable interop predeploy and functionality
--admin.port value (default: 8420) ($SUPERSIM_ADMIN_PORT)
Listening port for the admin server
--interop.l2tol2cdm.override value ($SUPERSIM_INTEROP_L2TO2CDM_OVERRIDE)
Path to the L2ToL2CrossDomainMessenger build artifact that overrides
the default implementation
--l1.port value (default: 8545) ($SUPERSIM_L1_PORT)
Listening port for the L1 instance. `0` binds to any available port
--l2.count value (default: 2) ($SUPERSIM_L2_COUNT)
Number of L2s. Max of 5
--l2.starting.port value (default: 9545) ($SUPERSIM_L2_STARTING_PORT)
Starting port to increment from for L2 chains. `0` binds each chain to any
available port
--interop.autorelay (default: false) ($SUPERSIM_INTEROP_AUTORELAY)
Automatically relay messages sent to the L2ToL2CrossDomainMessenger using
account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
--interop.delay value (default: 0) ($SUPERSIM_INTEROP_DELAY)
Delay before relaying messages sent to the L2ToL2CrossDomainMessenger
--logs.directory value ($SUPERSIM_LOGS_DIRECTORY)
Directory to store logs
--l1.host value (default: "127.0.0.1") ($SUPERSIM_L1_HOST)
Host address for the L1 instance
--l2.host value (default: "127.0.0.1") ($SUPERSIM_L2_HOST)
Host address for L2 instances
--odyssey.enabled (default: false) ($SUPERSIM_ODYSSEY_ENABLED)
Enable odyssey experimental features
--dependency.set value ($SUPERSIM_DEPENDENCY_SET)
Override local chain IDs in the dependency set.(format: '[901,902]' or '[]')
--log.level value (default: INFO) ($SUPERSIM_LOG_LEVEL)
The lowest log level that will be output
--log.format value (default: text) ($SUPERSIM_LOG_FORMAT)
Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json',
'json-pretty',
--log.color (default: false) ($SUPERSIM_LOG_COLOR)
Color the log output if in terminal mode
--log.pid (default: false) ($SUPERSIM_LOG_PID)
Show pid in the log
--help, -h (default: false)
show help
```
## Notes
### Fork height
The fork height is determined by L1 block height (default `latest`). This is then used to derive the corresponding L2 block to start from.
### Interoperability contracts
By default, interop contracts are not deployed on forked networks. To include them, run `supersim` with the `--interop.enabled` flag.
```sh theme={null}
supersim fork --chains=op,base,zora --interop.enabled
```
## Next steps
* Explore the Supersim [included contracts](/app-developers/reference/tools/supersim/included-contracts) being used to help replicate the OP Stack environment.
- Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline.
# Included contracts
Source: https://docs.optimism.io/app-developers/reference/tools/supersim/included-contracts
Learn about the Supersim included contracts.
The `supersim` chain environment includes contracts already deployed to help replicate the OP Stack interop environment. See [OP Chain A](./chain-a) for contract address examples for a L2 system.
## OP Stack system contracts (L1)
These are the L1 contracts that are required for a rollup as part of the OP Stack protocol. Examples are the [OptimismPortal](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal2.sol), [L1StandardBridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/L1StandardBridge.sol), and [L1CrossDomainMessenger](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol).
For more details, see [example contracts](/op-mainnet/network-information/op-addresses#ethereum-mainnet) or the [source code](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1).
## OP Stack L2 contracts (L2)
The OP Stack system contracts on the L2 are included at the standard addresses by default.
* [Standard OP Stack predeploys (L2)](https://specs.optimism.io/protocol/predeploys.html)
* [Interoperability predeploys (*experimental*) (L2)](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs\&utm_medium=docs)
* [OP Stack preinstalls (L2)](https://specs.optimism.io/protocol/preinstalls.html?utm_source=op-docs\&utm_medium=docs)
## Periphery contracts (L2)
L2 chains running on `supersim` also includes some useful contracts for testing purposes that are not part of the OP Stack by default.
### L2NativeSuperchainERC20
A simple ERC20 included in Supersim for testing cross-chain token transfers. It includes permissionless minting for easy testing.
Source: [L2NativeSuperchainERC20.sol](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/L2NativeSuperchainERC20.sol)
Deployed address: `0x420beeF000000000000000000000000000000001`
#### Minting new tokens
```bash theme={null}
cast send 0x420beeF000000000000000000000000000000001 "mint(address _to, uint256 _amount)" $RECIPIENT_ADDRESS 1ether --rpc-url $L2_RPC_URL
```
## Next steps
* Get network details about the two OP Stack systems spun up in vanilla mode: [OPChainA (chainID 901)](/app-developers/reference/tools/supersim/chain-a) and [OPChainB (chainID 902)](/app-developers/reference/tools/supersim/chain-b).
- Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline.
# Vanilla mode
Source: https://docs.optimism.io/app-developers/reference/tools/supersim/vanilla
Learn how to use Supersim in vanilla mode (non-forked).
This guide explains how to start `supersim` in vanilla (non-forked) mode. By default, two OP Stack systems will be spun up in vanilla mode:
* OPChainA (chainID 901)
* OPChainB (chainID 902)
Both "roll up" into a single L1 chain (chainID 900).
## How it works
```sh theme={null}
supersim
```
Vanilla mode will start 3 chains, with the OP Stack contracts & periphery contracts already deployed.
* (1) L1 Chain
* Chain 900
* (2) L2 Chains
* Chain 901
* Chain 902
### Example startup logs
```
Available Accounts
-----------------------
(0): 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
(1): 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
(2): 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC
(3): 0x90F79bf6EB2c4f870365E785982E1f101E93b906
(4): 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
(5): 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
(6): 0x976EA74026E726554dB657fA54763abd0C3a0aa9
(7): 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955
(8): 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f
(9): 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
Private Keys
-----------------------
(0): 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1): 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
(2): 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
(3): 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
(4): 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
(5): 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba
(6): 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
(7): 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
(8): 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
(9): 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
Orchestrator Config:
L1:
Name: L1 Chain ID: 900 RPC: http://127.0.0.1:8545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-900
L2:
Name: OPChainA Chain ID: 901 RPC: http://127.0.0.1:9545 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-901
Name: OPChainB Chain ID: 902 RPC: http://127.0.0.1:9546 LogPath: /var/folders/0w/ethers-phoenix/T/anvil-chain-902
```
## Configuration
```
NAME:
supersim - Superchain Multi-L2 Simulator
USAGE:
supersim [global options] command [command options]
VERSION:
untagged
DESCRIPTION:
Local multichain optimism development environment
COMMANDS:
fork Locally fork a network in the superchain registry
docs Display available docs links
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--admin.port value (default: 8420) ($SUPERSIM_ADMIN_PORT)
Listening port for the admin server
--interop.l2tol2cdm.override value ($SUPERSIM_INTEROP_L2TO2CDM_OVERRIDE)
Path to the L2ToL2CrossDomainMessenger build artifact that overrides
the default implementation
--l1.port value (default: 8545) ($SUPERSIM_L1_PORT)
Listening port for the L1 instance. `0` binds to any available port
--l2.count value (default: 2) ($SUPERSIM_L2_COUNT)
Number of L2s. Max of 5
--l2.starting.port value (default: 9545) ($SUPERSIM_L2_STARTING_PORT)
Starting port to increment from for L2 chains. `0` binds each chain to any
available port
--interop.autorelay (default: false) ($SUPERSIM_INTEROP_AUTORELAY)
Automatically relay messages sent to the L2ToL2CrossDomainMessenger using
account 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720
--interop.delay value (default: 0) ($SUPERSIM_INTEROP_DELAY)
Delay before relaying messages sent to the L2ToL2CrossDomainMessenger
--logs.directory value ($SUPERSIM_LOGS_DIRECTORY)
Directory to store logs
--l1.host value (default: "127.0.0.1") ($SUPERSIM_L1_HOST)
Host address for the L1 instance
--l2.host value (default: "127.0.0.1") ($SUPERSIM_L2_HOST)
Host address for L2 instances
--odyssey.enabled (default: false) ($SUPERSIM_ODYSSEY_ENABLED)
Enable odyssey experimental features
--dependency.set value ($SUPERSIM_DEPENDENCY_SET)
Override local chain IDs in the dependency set.(format: '[901,902]' or '[]')
--log.color (default: false) ($SUPERSIM_LOG_COLOR)
Color the log output if in terminal mode
--log.format value (default: text) ($SUPERSIM_LOG_FORMAT)
Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json',
'json-pretty',
--log.level value (default: INFO) ($SUPERSIM_LOG_LEVEL)
The lowest log level that will be output
--log.pid (default: false) ($SUPERSIM_LOG_PID)
Show pid in the log
MISC
--help, -h (default: false)
show help
--version, -v (default: false)
print the version
```
## Next steps
* Explore the Supersim [included contracts](/app-developers/reference/tools/supersim/included-contracts) being used to help replicate the OP Stack interop environment.
* Get network details about the two OP Stack systems spun up in vanilla mode: [OPChainA (chainID 901)](/app-developers/reference/tools/supersim/chain-a) and [OPChainB (chainID 902)](/app-developers/reference/tools/supersim/chain-b).
- Learn how to [deposit transactions](/app-developers/tutorials/bridging/deposit-transactions) with Supersim, using a much simpler approach that bypasses the derivation pipeline.
# Account abstraction
Source: https://docs.optimism.io/app-developers/tools-sdks/account-abstraction
This guide explains how to use account abstraction to remove friction from your app experience
This page includes providers that meet specific [inclusion criteria](#inclusion-criteria), as outlined below. Please visit the [community account abstractions page](https://github.com/ethereum-optimism/developers/blob/main/community/tools/account-abstraction.md) for an additional listing of third-party account abstraction tools.
[ERC-4337](https://www.erc4337.io/docs/paymasters/introduction), also known as Account Abstraction, enables more opportunities for apps and wallet developers to innovate on user experiences, including the ability to:
* Batch transactions together (e.g. approve and execute a swap in one go)
* Offer wallets with easy recovery and no seed phrase
* Sponsor the gas fees for transactions
* Enable users to pay gas in the token(s) of their choice
## Bundlers
The OP Stack includes support for the `eth_sendRawTransactionConditional` RPC method to assist bundlers on shared 4337 mempools. See the [specification](/op-stack/features/send-raw-transaction-conditional) for how this method is implemented in op-geth.
If used by the chain operator, also see the supplemental [op-txproxy](/chain-operators/tools/op-txproxy) service which may apply additional restrictions prior to reaching the block builder.
As of today, this endpoint is not enabled by default in the stack. The operator must explicitly configure this.
## Account abstraction tools
Ready to enable account abstraction experiences in your app? Here's some helpful information on account abstraction infrastructure like ERC-4337 bundler and gas manager APIs that are available on OP Mainnet:
* [Alchemy](https://www.alchemy.com/account-abstraction):
Account Kit is a complete solution for account abstraction. Using Account Kit, you can create a smart contract wallet for every user that leverages account abstraction to simplify every step of your app's onboarding experience. It also offers Gas Manager and Bundler APIs for sponsoring gas and batching transactions.
* [Biconomy](https://docs.biconomy.io/): is an Account Abstraction toolkit that enables you to provide the simplest UX for your app or wallet. It offers modular smart accounts, as well as paymasters and bundlers as a service for sponsoring gas and executing transactions at scale.
* [GroupOS](https://docs.groupos.xyz/introduction/group-os): provides Smart Wallets that are ERC-4337 compliant smart wallets, offering full flexibility, programmability and extensibility as well as out-of-the-box toolkit groups need to gaslessly onboard and activate wallets to games, applications, and/or protocols.
* [Openfort](https://openfort.io/docs/?utm_source=optimism\&utm_medium=docs\&utm_campaign=backlinks): an open-source wallet infrastructure solution. The core offerings include embedded wallets, global wallets and AA infrastructure (Paymaster and Bundler). It enables rapid integration of wallet functionality, intuitive onboarding, and stablecoin flows.
* [Pimlico](https://docs.pimlico.io/): provides an infrastructure platform that makes building smart accounts simpler. If you are developing an ERC-4337 smart account, they provide bundlers, verifying paymasters, ERC-20 paymasters, and much more.
* [Reown](https://reown.com/?utm_source=optimism\&utm_medium=docs\&utm_campaign=backlinks) gives developers the tools to build user experiences that make digital ownership effortless, intuitive, and secure. Using Reown's AppKit SDK, you can enable your users to create a smart wallet using their social logins, configure a paymaster to sponsor gas fees, enable chain abstraction and a lot more.
* [Safe](https://docs.safe.global/home/what-is-safe): provides modular smart account infrastructure and account abstraction stack via their Safe\{Core} Account Abstraction SDK, API, and Protocol.
* [Stackup](https://docs.stackup.sh/docs): provides smart account tooling for building account abstraction within your apps. They offer Paymaster and Bundler APIs for sponsoring gas and sending account abstraction transactions.
* [thirdweb](https://portal.thirdweb.com/react/v5/account-abstraction/get-started?utm_source=opdocs\&utm_medium=docs):
offers the complete tool-kit to leverage account abstraction technology to enable seamless user experiences for your users. This includes Account Factory contracts that lets your users spin up Smart Accounts, Bundler for UserOps support, and Paymaster to enable gas sponsorships.
## Helpful tips
* [EIP-1271 Signature Validation](https://eip1271.io/)
* [Making smart accounts work with WalletConnect v2](https://safe-global.notion.site/WalletConnect-v2-update-Issues-and-solutions-for-smart-wallets-3fc32fad6af4485fa5823eaebd486819)
# Block explorers
Source: https://docs.optimism.io/app-developers/tools-sdks/block-explorers
Learn about different block explorers you can use to interact with contracts and view transaction history for OP Mainnet and OP Sepolia.
## Blockscout
We have a Blockscout explorer for [OP Mainnet](https://optimism.blockscout.com) and [OP Sepolia](https://optimism-sepolia.blockscout.com/). It includes:
* [Verified testnet contract source code, along with the ability to interact with it](https://optimism.blockscout.com/verified-contracts)
* [Detailed testnet transaction information](https://optimism.blockscout.com/tx/0xa1b04233084d4067ec0bb3e09301012900f0e209f14a3d406f3d6dc696eea138)
Blockscout also has some OP-Mainnet-specific features:
* [An interactive list of deposits (L1-L2)](https://optimism.blockscout.com/l2-deposits)
* [An interactive list of withdrawals (L2-L1)](https://optimism.blockscout.com/l2-withdrawals)
* [Transaction batches](https://optimism.blockscout.com/l2-txn-batches)
* [App marketplace](https://optimism.blockscout.com/apps)
* And much more!
## Etherscan
We have Etherscan explorers for the [OP Mainnet](https://explorer.optimism.io) and the [OP Sepolia](https://testnet-explorer.optimism.io/).
Etherscan has lots of tools to help you debug transactions.
Optimistic Etherscan has all the tools you expect from Etherscan, such as:
* [Verified contract source code, along with the ability to interact with it](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#code)
* [Detailed transaction information](https://explorer.optimism.io/tx/0x292423266d6da24126dc4e0e81890c22a67295cc8b1a987e71ad84748511452f)
* And everything else you might find on Etherscan!
It's also got some OP-Mainnet-specific features:
* [A list of L1-to-L2 transactions](https://explorer.optimism.io/txsEnqueued)
* [A list of L2-to-L1 transactions](https://explorer.optimism.io/txsExit)
* [A tool for finalizing L2-to-L1 transactions](https://explorer.optimism.io/messagerelayer)
* And more! Just check it out and click around to find all of the available features.
## Superscan by Routescan
[Superscan](https://superscan.network) is the dev-focused OP Stack explorer unified at the ecosystem level, powered by [Routescan](https://routescan.io). On the Superscan, developers can quickly glance at transactions, blocks, addresses, deployed contracts and more across OP Stack chains in unified pages.
The Superscan currently includes:
* Mainnet - OP Mainnet, Base, Zora, Mode, Cyber, Orderly, Fraxtal, Public Goods Network
* Testnet - Zora, Mode, Orderly, Fraxtal
## Tenderly
Tenderly's [Developer Explorer](https://docs.tenderly.co/developer-explorer?mtm_campaign=ext-docs\&mtm_kwd=optimism) for OP Mainnet and OP Sepolia allows you to monitor and inspect transactions, providing a high level of detail and additional tools:
Tenderly Developer Explorer lets you:
* Keep track of specific [contracts](https://docs.tenderly.co/developer-explorer/contracts?mtm_campaign=ext-docs\&mtm_kwd=optimism) and their transactions
* Inspect [transaction execution](https://docs.tenderly.co/developer-explorer/inspect-transaction?mtm_campaign=ext-docs\&mtm_kwd=optimism) with fully decoded transaction trace
* [Debug](https://docs.tenderly.co/debugger?mtm_campaign=ext-docs\&mtm_kwd=optimism) failing and [simulate](https://docs.tenderly.co/simulator-ui/using-simulation-ui?mtm_campaign=ext-docs\&mtm_kwd=optimism) correct transactions before sending them on-chain
* Evaluate [function-level gas usage](https://docs.tenderly.co/debugger/gas-profiler?mtm_campaign=ext-docs\&mtm_kwd=optimism) for any transaction
* Set up [Alerts](https://docs.tenderly.co/alerts/tutorials-and-quickstarts/alerting-quickstart-guide?mtm_campaign=ext-docs\&mtm_kwd=optimism) to monitor interactions, access control, asset transfers, and contracts' state changes
* Create a [Virtual TestNet](https://docs.tenderly.co/virtual-testnets?mtm_campaign=ext-docs\&mtm_kwd=optimism) from a specific OP Mainnet or OP Chain transaction for systematic research
## Access to pre-regenesis history
Because of our final regenesis on 11 November 2021, older transactions are not part of the current blockchain and do not appear on [Etherscan](https://explorer.optimism.io/?utm_source=op-docs\&utm_medium=docs).
However, you **can** access transaction history between 23 June 2021 and the final regenesis using a number of different tools. For detailed instructions, see [Regenesis History](/op-mainnet/pre-bedrock-history/regenesis-history).
## Next Steps
* Looking for other developer tools? See the [building apps overview](/app-developers/guides/building-apps) to explore more options!
# Analytics tools
Source: https://docs.optimism.io/app-developers/tools-sdks/data/analytics-tools
Learn about platforms you can use to gather analytics and set up customizations about OP Mainnet.
The following guide lists platforms you can use to gather analytics and set up customizations about OP Mainnet.
## Blocknative
[Blocknative](https://www.blocknative.com/) lets you [decode](http://docs.blocknative.com/ethernow/batch-decoder-api) and [analyze](https://docs.blocknative.com/blocknative-data-archive/blob-archive) OP Stack Batches submitted to the Ethereum L1. You can inspect, analyze, decode, and download the data of any batch – confirmed on-chain or not – via public APIs or visually through the [Ethernow Explorer](http://ethernow.xyz). Below you can find links to the different resources:
* [Batch Decoding API](http://docs.blocknative.com/ethernow/batch-decoder-api): decode OP Stack Batch transactions into their human-readable JSON format.
* [Blob Archive API](https://docs.blocknative.com/blocknative-data-archive/blob-archive): take any versioned hash of an OP Stack Blob and receive the blob data (even beyond the 4096 epoch window of storage).
* [Mempool Archive](https://docs.blocknative.com/blocknative-data-archive/mempool-archive): Analyze any OP Stack transaction that was in the Ethereum mempool to see detection time, time pending, gas, etc.
* [Ethernow Explorer](http://ethernow.xyz): Visually see transactions and batches enter the mempool and get organized into blocks. Use this filter to see OP Stack Batches enter the mempool and land on-chain.
## Tenderly
[Tenderly](https://tenderly.co/?mtm_campaign=ext-docs\&mtm_kwd=optimism) provides comprehensive monitoring and security solutions for OP-powered Chains, allowing you to stay informed and respond proactively to potential issues in real time.
* Configure [Tenderly Alerts](https://docs.tenderly.co/alerts/intro-to-alerts?mtm_campaign=ext-docs\&mtm_kwd=optimism) for monitoring wallets and setting up real-time notifications on transactions and contract events. Notifications trigger external webhooks, PagerDuty, or chat apps like Telegram and Slack.
* Rely on [Developer Explorer](https://docs.tenderly.co/developer-explorer?mtm_campaign=ext-docs\&mtm_kwd=optimism) to monitor and analyze transaction execution with a high level of detail.
* Use [Web3 Actions](https://docs.tenderly.co/web3-actions/intro-to-web3-actions?mtm_campaign=ext-docs\&mtm_kwd=optimism) to automate predefined responses, improving security and user experience.
* Integrate [Simulation RPC](https://docs.tenderly.co/simulations/single-simulations?mtm_campaign=ext-docs\&mtm_kwd=optimism#simulate-via-rpc) to predict transaction outcomes such as the expected asset changes, precise gas usage, and emitted events.
## Dune Analytics
[Dune Analytics](https://dune.com) allows anyone to create dashboards that present information about OP Chains (OP Mainnet, Base, and Zora are available). See [Dune Docs](https://dune.com/docs/) for more info.
You can find a list of community created dashboards for OP Chains [here](https://dune.com/browse/dashboards?q=tags%3Aop%2Coptimism%2Csuperchain\&order=trending\&time_range=24h), or [create your own](https://docs.dune.com/#queries) dashboard.
For developers building apps on OP chains, Dune's developer platform, [Sim](https://sim.dune.com/), provides real-time onchain data access via [Sim APIs](https://docs.sim.dune.com/) and custom indexing with [Sim IDX](https://docs.sim.dune.com/idx).
Here are some of our favorite dashboards so far:
* [OP Chains / Superchain - L2 Activity, Chain Economics](https://dune.com/oplabspbc/op-stack-chains-l1-activity)
* [OP Chains / Superchain - Popular Apps & Project Usage Trends](https://dune.com/oplabspbc/superchain-op-chains-apps-and-project-usage-trends)
* [By OP Chain - L2/L1 Chain Economics](https://dune.com/oplabspbc/optimism-l2-l1-economics)
* [OP Token House Delegates](https://dune.com/optimismfnd/optimism-op-token-house)
* [Superchain NFTs](https://dune.com/oplabspbc/superchain-nfts)
## Additional tools and resources
Here are some additional tools and resources for OP Mainnet analytics and development:
* L2 Usage and Comparison: [growthepie](https://www.growthepie.xyz/)
* OP Analytics (Incentive Tracking, Helper Functions, Public Analysis): [OP Analytics on GitHub](https://github.com/ethereum-optimism/op-analytics)
* Contribute to NumbaNERDs: [Issues on GitHub](https://github.com/ethereum-optimism/op-analytics/issues)
# Data and Dashboards
Source: https://docs.optimism.io/app-developers/tools-sdks/data/data-and-dashboards
Learn about various dashboards and terms to explore various OP Stack metrics.
The following resources are available to help you explore various OP Stack metrics:
* [Data Glossary](/app-developers/tools-sdks/data/data-glossary): Definitions, methodology, and calculation notes for key metrics
* [Superchain Health Dashboard](https://docs.google.com/spreadsheets/d/1f-uIW_PzlGQ_XFAmsf9FYiUf0N9l_nePwDVrw0D5MXY/edit?gid=192497306#gid=192497306): High-level metrics across OP Stack chains
* [Superchain Strategic Focus Dashboard](https://app.hex.tech/61bffa12-d60b-484c-80b9-14265e268538/app/d28726b2-ff11-4f94-8a9f-6bb0a86f4b46/latest): In-depth metrics with chain-level splits and industry benchmarks
* [Optimism Superchain Raw Onchain Data](https://console.cloud.google.com/bigquery/analytics-hub/exchanges/projects/523274563936/locations/us/dataExchanges/optimism_192d403716e/listings/optimism_superchain_raw_onchain_data_192fdc72e35): Raw Blocks, Logs, Transactions, Traces data for OP Stack chains, updated daily.
* [Optimism Superchain 4337 Account Abstraction Data](https://console.cloud.google.com/bigquery/analytics-hub/exchanges/projects/523274563936/locations/us/dataExchanges/optimism_192d403716e/listings/optimism_superchain_4337_account_abstraction_data_1954d8919e1): Decoded account abstraction UserOps across OP Stack chains.
# Data Glossary
Source: https://docs.optimism.io/app-developers/tools-sdks/data/data-glossary
This glossary explains various data terms.
This glossary is a living document and will be updated over time as new metrics emerge or definitions evolve.
| Metric | What It Measures | Why It Matters |
| ------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Real Economic Value (REV) | Fees paid by users to transact (txn fees + out-of-protocol tips) | Captures users' willingness to pay for onchain activity |
| Collective Revenue | ETH earned by the Optimism Collective | Revenue can be directed by governance to support ecosystem growth |
| Total Value Locked (TVL) | Tokens locked in DeFi protocols and other apps | Supply side of the DeFi ecosystem |
| Gas Used per Second | Average compute consumed onchain | Measures throughput and execution load |
| Median Transaction Fee | Typical cost for a user to transact | Lower fees reduce friction and may unlock broader usage |
| Market Share | OP Stack ecosystem's share of activity vs. the broader crypto industry | Tracks relative performance against L2s or the broader market |
## Measure Demand
### Transaction Fees Paid
**Metric:** Real Economic Value (REV)
**Definition:** The total fees paid to execute a transaction onchain. This includes both the traditional gas fees required for inclusion onchain and additional fees paid to transaction execution services (e.g., Jito, Flashbots, Timeboost).
**Calculation:** Gas Fees + Out-of-Protocol Tips (e.g., Jito, Flashbots, Timeboost)
* Out-of-Protocol Tips can be sourced from Defillama's MEV category
**Why it matters:**
* REV is a topline metric that "measures the monetary demand to transact onchain" (Blockworks).
* It's used as a proxy for users' willingness to pay, capturing all transaction fees to better reflect real demand (excludes app-level fees like DEX swap costs).
### Revenue
**Metric:** Estimated Optimism Collective Revenue (Collective Revenue)
**Definition:** The amount of ETH expected to be earned by the Optimism Collective from revenue sharing.
**Calculation:** For each chain, take the greater of (a) 2.5% of Chain Revenue or (b) 15% of Net Onchain Profit. OP Mainnet contributes 100% of Net Onchain Profit.
**Key Components:**
* **Net Onchain Profit:** Chain Revenue - L1 Gas Fees
* **Chain Revenue:** Sum of the L1 Data Fee + L2 Base Fee + L2 Priority Fee + L2 Operator Fee (Also includes any additional fee types added in the future.)
* *L1 Gas Fees:* Total gas fees spent by the chain on L1 in transaction batches (including blob costs) and state output submissions or dispute games.
* **Transaction Batches:** All transactions where the transaction from address is the `batcherHash` address as defined in the chains' `SystemConfigProxy` contract, and the transaction to address is the chain's `batch_inbox_address` as defined in the rollup config.
* **State Output Submissions or Dispute Games:** All transactions where the transaction from address is the `Proposer` and the transaction to address is either the `outputOracleProxy` or the `disputeGameFactoryProxy` as defined in the chains' `SystemConfigProxy` contract.
* Each chain's `SystemConfigProxy` contract can be found in the superchain-registry.
* **Resolving Dispute Games:** All transactions sent to dispute game contracts created by the `disputeGameFactoryProxy`, where the transaction's method id (function call) is either `Resolve`, `ResolveClaim`, or `ClaimCredit`.
**Why it matters:**
* This is what the Optimism Collective earns by operating OP Stack chains, which can be directed by governance to support ecosystem growth.
* See How (and why) the OP Stack drives fees to the Optimism Collective (Optimism blog, Aug 2024)
## Onchain Signals
### Value Onchain
**Metric:** Total Value Locked (TVL)
**Definition:** "Value of all coins held in smart contracts of the protocol" (Defillama).
**Calculation:** The sum of all USD value of assets locked in applications, as reported by DefiLlama.
* TVL can be priced in USD or a crypto asset like ETH, but both are subject to price volatility. USD is often used because it's easier to interpret and consistent across the broader crypto ecosystem.
**Why it Matters:** TVL represents the supply side of onchain economic activity for use in protocols such as Decentralized Exchanges (DEXs) and lending markets. Strong TVL in the right places may enable greater onchain demand.
#### How to Measure Growth: Net TVL Flows
Because TVL is influenced by market fluctuations, it can be misleading when trying to measure true growth or user behavior. Net TVL Flows can adjust for this by tracking the net change in token balances, valued at current prices.
**Calculation:** ( `# of Tokens on Day N` - `# of Tokens on Day 0` ) \* `Price of Tokens on Day N`
**Example:** If an app has 100,000 ETH locked on Day 0 when ETH/USD is $2,000, and 90,000 ETH locked at $3,000 on Day N:
* Net TVL Flows = −10,000 ETH × $3,000 = $30 million in net outflows
* Naive TVL change would suggest growth: $200 million → $270 million
## Network Usage & Infrastructure
**Metric:** Gas Used per Second (gas/s)
**Definition:** "Gas refers to the unit that measures the amount of computational effort required to execute specific operations on Ethereum" (ethereum.org). Gas Used is tracked as an average rate per second for simplicity.
**Why it Matters:** Gas, sometimes referred to as blockspace, is the limited resource that blockchains provide. Gas used shows how much of that resource is actually being consumed.
**Caution:** Gas is only comparable across chains that use Ethereum-equivalent gas units.
### User Experience (UX)
**Metric:** Median Transaction Fee (USD)
**Definition:** The median gas fee paid to submit a transaction, expressed in USD for simplicity and easier comparison across ecosystems.
**Calculation:** Median of all transaction fees over a period of time, marked at the USD price at the time of the transaction.
**Why it Matters:** This metric serves as a proxy for the cost to transact. Lower median fees enable broader usage by reducing friction, lowering breakeven costs, and unlocking use cases that would otherwise be cost-prohibitive.
## Market Share
**Definition:** The OP Stack ecosystem's share of a broader market segment for any measure (e.g., L2s, total crypto).
**Calculation:** OP Stack Metric Value / Total Market Metric Value
**Why it Matters:** Market share helps isolate whether growth is driven by the OP Stack ecosystem itself, or is simply part of a broader market trend. A rising share signals outperformance, while a declining share suggests that other ecosystems are growing faster.
# Oracles
Source: https://docs.optimism.io/app-developers/tools-sdks/data/oracles
Learn about different oracles and how you can use them to access offchain data onchain as well as random number generation.
This page includes providers that meet specific [inclusion criteria](#inclusion-criteria), as outlined below. Please visit the [community oracles page](https://github.com/ethereum-optimism/developers/blob/main/community/tools/oracles.md) for an additional listing of third-party Oracles.
This reference guide lists different Oracles you can use when building on Optimism. [Oracles](https://ethereum.org/en/developers/docs/oracles/) provide offchain data onchain. This allows code running on a blockchain to access a wide variety of information.
For example, a [stablecoin](https://ethereum.org/en/stablecoins/) that accepts ETH as collateral needs to know the ETH/USD exchange rate:
* How many stablecoins can we give a user for a given amount of ETH?
* Do we need to liquidate any deposits because they are under collateralized?
## Security and decentralization
Different oracles have different security assumptions and different levels of decentralization.
Usually they are either run by the organization that produces the information, or have a mechanism to reward entities that provide accurate information and penalize those that provide incorrect information.
## Types of oracles
There are two types of oracles:
1. **Push oracles** are updated continuously and always have up to date information available onchain.
2. **Pull oracles** are only updated when information is requested by a contract.
Pull oracles are themselves divided into two types:
1. Double-transaction oracles, which require two transactions.
The first transaction is the request for information, which usually causes the oracle to emit an event that triggers some offchain mechanism to provide the answer (through its own transaction).
The second transaction actually reads onchain the result from the oracle and uses it.
2. Single-transaction oracles, which only require one transaction. The way this works is that the transaction that requests the information includes a callback (address and the call data to provide it).
When the oracle is updated (which also happens through a transaction, but one that is not sent by the user), the oracle uses the callback to inform a contract of the result.
## Random number generation (RNG)
Random number generation in blockchain applications ensures that smart contracts can access unbiased random values. This is essential for certain use cases like generative NFTs, gaming, commit & reveal schemes and more. Various approaches include using a trusted third party, blockhash-based methods, Verifiable Random Functions (VRF), quantum random numbers to name a few. Each method has trade-offs between simplicity, security, and trust assumptions, allowing developers to select the most suitable option for their use case.
## List of oracles
### Gas oracle
OP Mainnet provides a [Gas Price Oracle](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol) that provides information about [gas prices and related parameters](/op-stack/transactions/fees).
It can also calculate the total cost of a transaction for you before you send it.
This contract is a predeploy at address `0x420000000000000000000000000000000000000F`:
* [On OP Mainnet](https://explorer.optimism.io/address/0x420000000000000000000000000000000000000F#readContract)
* [On OP Sepolia](https://testnet-explorer.optimism.io/address/0x420000000000000000000000000000000000000F)
This is a push Oracle.
OP Mainnet (and the testnets) updates the gas price parameters onchain whenever those parameters change.
The L1 gas price, which can be volatile, is only pushed once every 5 minutes, and each time can change only by up to 20%.
* [Blocknative](https://docs.blocknative.com/gas-prediction) provides real-time gas estimation powered by predictive modeling to forecast gas price distribution for select OP Stack chains, including OP Mainnet and Base. The [API](https://docs.blocknative.com/gas-prediction) is also available on Ethereum Mainnet to estimate base fee costs.
### API3
The [API3 Market](https://market.api3.org/optimism) provides access to 200+ price feeds on [Optimism Mainnet](https://market.api3.org/optimism) and [Testnet](https://market.api3.org/optimism-sepolia-testnet). The price feeds operate as a native push oracle and can be activated instantly via the Market UI.
The price feeds are delivered by an aggregate of [first-party oracles](https://docs.api3.org/oev-searchers/glossary.html#first-party-oracles) using signed data and support [OEV recapture](https://docs.api3.org/dapps/integration/security-considerations.html#oracle-extractable-value-oev).
Unlike traditional data feeds, reading [API3 price feeds](https://docs.api3.org/oev-searchers/in-depth/#dapps-catalog) enables dApps to auction off the right to update the price feeds to searcher bots which facilitates more efficient liquidation processes for users and LPs of DeFi money markets. The OEV recaptured is returned to the dApp.
API3's QRNG provides dApps with truly random numbers based on quantum mechanics at no charge. More details are available on the [API3 website](https://api3.org/).
### Chainlink
[Chainlink](https://chain.link/) is the industry-standard decentralized computing platform powering the verifiable web.
Chainlink powers verifiable applications and high-integrity markets for banking, DeFi, global trade, gaming, and other major sectors.
Chainlink provides a number of [price feeds](https://docs.chain.link/docs/optimism-price-feeds/).
Those feeds are available on the production network @ [OP Mainnet](https://docs.chain.link/data-feeds/price-feeds/addresses?network=optimism\&page=1#optimism-mainnet).
* Data Feeds: Chainlink Data Feeds provide a secure, reliable, and decentralized source of off-chain data to power unique smart contract use cases for DeFi and beyond.
* Automation: Chainlink Automation is an ultra-reliable and performant smart contract automation solution enabling developers to quickly scale their operations in a verifiable, decentralized, and cost-efficient manner, to build next-generation apps.
* CCIP: Chainlink CCIP provides a secure interoperability protocol for powering token transfers and sending arbitrary messages cross-chain.
This is a push Oracle. See the [Using Data Feeds guide](https://docs.chain.link/docs/get-the-latest-price/) to learn how to use the Chainlink feeds.
* Chainlink VRF provides cryptographically secure randomness for blockchain-based applications. More details [here](https://chain.link/vrf)
### Chronicle
The first Oracle on Ethereum, Chronicle's decentralized Oracle network was originally built within MakerDAO for the development of DAI and is now available to builders on OP Mainnet and OP Stack chains.
* **Data Feeds**: Builders can choose from 65+ data feeds, including crypto assets, yield rates, and RWAs. Chronicle's data is sourced via custom-built data models, only utilizing Tier 1 Primary Sources, such as the markets where tokens are actively traded, including Coinbase, Binance, Uniswap, and Curve.
* **Transparency & Integrity**: Chronicle's Oracle network is fully transparent and verifiable. Via [The Chronicle](https://chroniclelabs.org/dashboard/oracle/DAI/USD?blockchain=OPT\&txn=0x53e60e6e79eb938e5ca3ca6c56b0795e003dd6b3a17cfd810ca5042b3d33b680\&contract=0x104916d38828DA8B83a88A1775Aa058e1F0B1647), the data supply chain for any Oracle can be viewed in real-time and historically, including data sources and the identity of all Validators/Signers. Users can cryptographically challenge the integrity of every Oracle update using the 'verify' feature. Data is independently sourced by a [community of Validators](https://chroniclelabs.org/validators), including Gitcoin, Etherscan, Infura, DeFi Saver, and MakerDAO.
* **Gas Efficiency:** Pioneering the Schnorr-based Oracle architecture, Chronicle's Oracles use 60-80% less gas per update than other Oracle providers. This lowest cost per update allows Push Oracle updates to be made more regularly, ensuring more accurate and granular data reporting.
Every Oracle implementation is customized to fit your needs. Implement one of our existing data models or contact Chronicle to develop custom Oracle data feeds via [Discord](https://discord.gg/CjgvJ9EspJ) or [Email](mailto:gm@chroniclelabs.org). Developers can dive deeper into Chronicle Protocol's architecture and unique design choices [via the docs](https://docs.chroniclelabs.org/).
### Gelato
[Gelato VRF](https://www.gelato.network/) enables smart contracts on Optimism to access verifiable randomness. Gelato VRF offers real randomness for blockchain applications by leveraging Drand, a trusted decentralized source for random numbers.
Gelato VRF (Verifiable Random Function) provides trustable randomness on EVM-compatible blockchains. Here's a brief overview:
* Contract Deployment: Use GelatoVRFConsumerBase.sol as an interface for requesting random numbers.
* Requesting Randomness: Emit the RequestedRandomness event to signal the need for a random number.
* Processing: Gelato VRF fetches the random number from Drand.
* Delivery: The fulfillRandomness function delivers the random number to the requesting contract.
Ready to integrate? Head over to the [Gelato VRF Quick Start Guide](https://docs.gelato.network/web3-services/vrf/quick-start).
### Pyth Network
The Pyth Network is a financial oracle network which delivers over 400 low-latency, high-fidelity price feeds across cryptocurrencies, FX pairs, equities, ETFs, and commodities.
* Pyth's price data is sourced from over [95 first-party sources](https://pyth.network/publishers) including exchanges, market makers, and financial services providers.
* Pyth [Price Feeds](https://www.pyth.network/developers/price-feed-ids) offer both the real-time spot price of the asset as well as an accompanying confidence interval band around that price
* The Pyth [TradingView](https://docs.pyth.network/guides/how-to-create-tradingview-charts) integration allows users to view and display Pyth prices on their own website and UI.
You can explore the full catalog of Pyth Price Feed IDs for [OP Mainnet and Sepolia (EVM Stable)](https://www.pyth.network/developers/price-feed-ids).
* Pyth Entropy allows developers to quickly and easily generate secure random numbers on the blockchain. More details [here](https://pyth.network/blog/secure-random-numbers-for-blockchains)
### RedStone
[RedStone](https://redstone.finance/) offers flexible Data Feeds for Lending Markets, Perpetuals, Options, Stablecoins, Yield Aggregators and other types of novel DeFi protocols. The infrastructure is well battle-tested and secures hundreds of millions of USD across mainnet.
Builders can choose how they want to consume the data among 3 dedicated models:
* [RedStone Core](https://docs.redstone.finance/docs/dapps/redstone-pull/) (pull oracle) - less than 10s update time, broad spectrum of feeds, best for most use cases. All [Core Price Feeds](https://app.redstone.finance/#/app/tokens) are available on OP Mainnet & OP Sepolia.
* [RedStone Classic](https://docs.redstone.finance/docs/dapps/redstone-push/) (push oracle) - for protocols designed for the traditional oracle interface, customizable heartbeat and deviation threshold.
* [Hybrid (push + push) ERC7412](https://docs.redstone.finance/docs/dapps/redstone-erc7412/) - specifically for Perps and Options, highest update frequency and front-running protection.
Interested in integration? [Get in contact](https://discord.com/invite/PVxBZKFr46) with the RedStone team!
### Stork
[Stork](https://stork.network) delivers price feeds with ultra-low latency, high uptime, and broad asset coverage for DeFi applications on OP Mainnet and OP Stack chains.
* **Ultra-Low Latency**: Stork provides price data at ultra-low latency, enabling protocols to maintain functionality during high-volatility periods and support time-sensitive trading operations.
* **High Reliability**: With best-in-class uptime and reliability, Stork supports perpetuals markets, lending protocols, and DeFi ecosystems that require consistent data availability.
* **Day 1 Asset Support**: Price feeds for new digital assets are available from Day 1, allowing protocols to launch new markets immediately after token generation events.
* **Broad Coverage**: Stork provides comprehensive asset coverage across crypto assets, equities, ETFs and commodities, supporting a wide range of DeFi use cases from derivatives trading to collateralized lending.
Visit the [Stork documentation](https://docs.stork.network) to get started.
### DIA
[DIA](https://www.diadata.org/) is a cross-chain, trustless oracle network delivering verifiable price feeds for Optimism. DIA sources raw trade data directly from primary markets and computes it onchain, ensuring complete transparency and data integrity.
* Complete verifiability from source to destination smart contract.
* Direct data sourcing from 100+ primary markets eliminating intermediary risk.
* Support for 20,000+ assets across all major asset classes.
* Custom oracle configuration with tailored sources and methodologies.
To get started:
* [Explore the Optimism Oracle Integration Guide](https://www.diadata.org/docs/guides/chain-specific-guide/optimism)
* [Request a Custom Oracle](https://www.diadata.org/docs/guides/how-to-guides/request-a-custom-oracle)
## Next steps
* Looking for other developer tools? See the [building apps overview](/app-developers/guides/building-apps) to explore more options!
# Testnet faucets
Source: https://docs.optimism.io/app-developers/tools-sdks/faucets
Learn how to get testnet ETH on test networks like Sepolia and OP Sepolia for development and testing purposes.
Faucets are developer tools that allow you to get free ETH (and other tokens) on test networks like Sepolia and OP Sepolia so that you can send transactions and create smart contracts.
Here you'll find a list of active faucets that you can try out.
Different faucets use different authentication methods, so you may have to try a few before you find one that works for you.
Faucets can occasionally also run out of ETH, so if you're having trouble getting ETH from a faucet, try another one.
Tokens on test networks like Sepolia or OP Sepolia have no value and are only meant for testing.
Optimists only take what they need so that others can use faucets too!
## Optimism's faucet
The [Optimism's faucet](https://console.optimism.io/faucet?utm_source=op-docs\&utm_medium=docs) is a developer tool hosted by Optimism that allows developers to get free testnet ETH to test apps on testnet OP Chains like Base Sepolia, OP Sepolia, PGN Sepolia, Zora Sepolia, and other OP Stack chains.
Optimism's faucet is a great place to start if you're looking for testnet ETH.
## Additional faucets
| Faucet Name | Supported Networks |
| ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| [Alchemy Faucet](https://sepoliafaucet.com) | Sepolia |
| [Chain Platform Faucet](https://faucet.chainplatform.co/faucets/ethereum-sepolia/) | Sepolia |
| [Ethereum Ecosystem Faucets](https://www.ethereum-ecosystem.com/faucets) | Sepolia, OP Sepolia, Base Sepolia |
| [ETHGlobal Testnet Faucet](https://ethglobal.com/faucet) | Sepolia, OP Sepolia, Base Sepolia, Zora Sepolia, Holesky |
| [Farcaster Frame Faucet by LearnWeb3](https://warpcast.com/haardikkk/0x28f4237d) | Sepolia, OP Sepolia |
| [Infura Faucet](https://www.infura.io/faucet/sepolia) | Sepolia |
| [Native USDC Faucet](https://faucet.circle.com/) | Sepolia, OP Sepolia |
| [QuickNode Faucet](https://faucet.quicknode.com/optimism/) | Sepolia, OP Sepolia |
| [Tenderly Unlimited Faucet](https://docs.tenderly.co/virtual-testnets/unlimited-faucet?mtm_campaign=ext-docs\&mtm_kwd=optimism) | OP Sepolia, OP Mainnet, and [85+ other networks](https://docs.tenderly.co/supported-networks?mtm_campaign=ext-docs\&mtm_kwd=optimism) |
| [thirdweb OP Sepolia Faucet](https://thirdweb.com/op-sepolia-testnet?utm_source=opdocs\&utm_medium=docs) | OP Sepolia |
| [thirdweb Sepolia Faucet](https://thirdweb.com/sepolia?utm_source=opdocs\&utm_medium=docs) | Sepolia |
| [ethfaucet.com](https://ethfaucet.com?utm_source=opdocs\&utm_medium=docs) | Sepolia, OP Sepolia, Base Sepolia, Zora Sepolia, Unichain Sepolia, Ink Sepolia, Mode Sepolia, Shape Sepolia, Worldchain Sepolia, BOB Sepolia |
## Bridge from Sepolia
If you have testnet ETH on Sepolia, you can bridge it to OP Sepolia (and vice versa) using the [Superchain Bridges UI](https://app.optimism.io/bridge/?utm_source=op-docs\&utm_medium=docs) or this collection of [Superchain Testnet Tools](https://www.superchain.tools/).
## Next steps
* If you're new to onchain development, check out [Optimism Unleashed](https://cryptozombies.io/en/optimism) by CryptoZombies and [Superchain Builder NFT](https://web.archive.org/web/20231218203510/https://blog.thirdweb.com/guides/optimism-superchain-faucet-nft/) by ThirdWeb.
* If you're familiar with onchain development, check out the [Optimism Ecosystem's Contributions Dashboard](https://github.com/ethereum-optimism/ecosystem-contributions) for project ideas that Optimism is looking for.
* Looking for other developer tools? See the [building apps overview](/app-developers/guides/building-apps) to explore more options!
# Tools & SDKs Listing Criteria
Source: https://docs.optimism.io/app-developers/tools-sdks/listing-criteria
What a tool or SDK must meet to be listed on the support matrix, the removal rule, and the maintenance sweep that keeps listings accurate.
This page governs the [Tools & SDKs support matrix](/app-developers/tools-sdks/support-matrix).
It exists so that adding or removing a listing is the application of written
policy, not a per-pull-request argument. The approach follows the
[ethereum.org product-listing policy](https://ethereum.org/en/contributing/adding-products/),
which uses published criteria plus a standing removal rule for the same reason.
Listings must also satisfy the site-wide
[content guide](/op-stack/contribute/content-guide): the matrix is a routing
page (clause 3 of the three-clause test), so every listing links one canonical
documentation home and restates nothing.
## Listing Criteria
A tool or SDK is eligible for the matrix only if **all** of the following hold:
1. **Open source, public repository.** The source is publicly available and
the repository is linkable from the matrix.
2. **Works with the OP Stack as documented.** A developer can follow the
tool's own quickstart against an OP Stack chain and succeed. Listings for
tools that only incidentally support the OP Stack must link the
OP-Stack-relevant entry point, not a generic homepage.
3. **Actively maintained.** The project ships releases and responds to
issues. An archived or visibly abandoned repository is disqualifying.
4. **Has one canonical documentation home.** There is a single place we can
link as the source of truth (per the content guide's dual-sourcing ban we
will not maintain a copy of its documentation here).
5. **Honest support status.** The listing's support status must match what
the owner declares in its own repository or docs. Experimental or preview
software is listed as such, never as production.
6. **Third-party listings are marked.** Any listing not owned by the Optimism
Collective passes through the `` component, per the
[content guide](/op-stack/contribute/content-guide).
## Proposing a Listing
Open a pull request against the
[matrix page](/app-developers/tools-sdks/support-matrix) that adds one row and,
in the PR description, states how the tool meets each criterion above — with
links as evidence (repository, docs home, release page, the owner's own status
declaration). Reviewers apply this page; if a criterion is unclear, the fix is
a PR to this page, not an exception.
## Removal Rule
A listing is removed — not left stale — when it no longer meets the criteria.
Typical triggers:
* The repository is archived, or releases and issue activity have stopped.
* The documented quickstart no longer works against an OP Stack chain.
* The canonical docs home is gone or no longer maintained.
* The owner's declared status changed and the listing was not updated
(in that case, update the row instead if the tool still qualifies).
Removal is an ordinary pull request that cites the failed criterion. Rows are
never soft-deprecated in place: the ethereum.org experience this policy is
modeled on shows that stale curated tables are worse than absent ones.
## Maintenance Sweep
Curated listings rot silently, so accuracy is maintained by a scheduled sweep
rather than by hoping readers report drift:
* **What is checked.** Every sweep re-verifies all four cells of every row —
purpose, owner, canonical docs link, and support status — against the
upstream repository and docs, and re-checks each criterion above.
* **What is recorded.** The sweep PR (or its "no changes" note) lists what
was checked and the upstream sources consulted, so the next sweep starts
from evidence.
* **Who runs it.** The docs maintainers own the sweep as part of the
standing docs review rotation; anyone may run one ahead of schedule by
opening the same kind of PR.
* **Outcome.** Each row is updated, confirmed, or removed under the
removal rule. A sweep that cannot verify a row treats it as failing.
## Next Steps
* Browse the [Tools & SDKs support matrix](/app-developers/tools-sdks/support-matrix).
* Read the [content guide](/op-stack/contribute/content-guide) for the
site-wide rules this policy inherits.
# Consensus
Source: https://docs.optimism.io/app-developers/tools-sdks/op-alloy/building/consensus
The `op-alloy-consensus` crate provides an Optimism consensus interface.
It contains constants, types, and functions for implementing Optimism EL
consensus and communication. This includes an extended `OpTxEnvelope` type
with [deposit transactions](https://specs.optimism.io/protocol/deposits.html), and receipts containing OP Stack
specific fields (`deposit_nonce` + `deposit_receipt_version`).
In general a type belongs in this crate if it exists in the
`alloy-consensus` crate, but was modified from the base Ethereum protocol
in the OP Stack. For consensus types that are not modified by the OP Stack,
the `alloy-consensus` types should be used instead.
## Block
[`op-alloy-consensus`](https://crates.io/crates/op-alloy-consensus) exports an Optimism block type, [`OpBlock`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/type.OpBlock.html).
This type simply re-uses the `alloy-consensus` block type, with `OpTxEnvelope`
as the type of transactions in the block.
## Transactions
Optimism extends the Ethereum [EIP-2718](https://eips.ethereum.org/EIPS/eip-2718) transaction envelope to include a
deposit variant.
### [`OpTxEnvelope`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/transaction/enum.OpTxEnvelope.html)
The [`OpTxEnvelope`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/transaction/enum.OpTxEnvelope.html) type is based on [Alloy](https://github.com/alloy-rs/alloy)'s
`TxEnvelope` type.
Optimism modifies the `TxEnvelope` to the following.
* Legacy
* EIP-2930
* EIP-1559
* EIP-7702
* Deposit
Deposit is a custom transaction type that is either an L1 attributes
deposit transaction or a user-submitted deposit transaction. Read more
about deposit transactions in [the specs](https://specs.optimism.io).
### Transaction Types ([`OpTxType`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/transaction/enum.OpTxType.html))
The [`OpTxType`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/transaction/enum.OpTxType.html) enumerates the transaction types using their byte identifier,
represents as a `u8` in rust.
## Receipt Types
Just like [`op-alloy-consensus`](https://crates.io/crates/op-alloy-consensus) defines transaction types,
it also defines associated receipt types.
[`OpReceiptEnvelope`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/enum.OpReceiptEnvelope.html) defines an [Eip-2718](https://eips.ethereum.org/EIPS/eip-2718) receipt envelope type
modified for the OP Stack. It contains the following variants - mapping
directly to the `OpTxEnvelope` variants defined above.
* Legacy
* EIP-2930
* EIP-1559
* EIP-7702
* Deposit
There is also an [`OpDepositReceipt`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/struct.OpDepositReceipt.html) type, extending the alloy receipt
type with a deposit nonce and deposit receipt version.
# RPC Engine Types
Source: https://docs.optimism.io/app-developers/tools-sdks/op-alloy/building/engine
The [`op-alloy-rpc-types-engine`](https://docs.rs/op-alloy-rpc-types-engine/latest/op_alloy_rpc_types_engine/) crate provides Optimism types for interfacing
with the Engine API in the OP Stack.
Optimism defines a custom payload attributes type called [`OpPayloadAttributes`](https://docs.rs/op-alloy-rpc-types-engine/latest/op_alloy_rpc_types_engine/struct.OpPayloadAttributes.html).
`OpPayloadAttributes` extends alloy's [`PayloadAttributes`](https://docs.rs/alloy-rpc-types-engine/latest/alloy_rpc_types_engine/payload/struct.PayloadAttributes.html) with a few fields: transactions,
a flag for enabling the tx pool, the gas limit, EIP 1559 parameters, and a
minimum base fee (used after the Jovian hardfork).
Optimism also returns a custom type for the `engine_getPayload` request for both V3 and
V4 payload envelopes. These are the [`OpExecutionPayloadEnvelopeV3`](https://docs.rs/op-alloy-rpc-types-engine/latest/op_alloy_rpc_types_engine/payload/v3/struct.OpExecutionPayloadEnvelopeV3.html) and
[`OpExecutionPayloadEnvelopeV4`](https://docs.rs/op-alloy-rpc-types-engine/latest/op_alloy_rpc_types_engine/payload/v4/struct.OpExecutionPayloadEnvelopeV4.html) types, which both wrap payload envelope types
from [`alloy-rpc-types-engine`](https://crates.io/crates/alloy-rpc-types-engine).
# Building
Source: https://docs.optimism.io/app-developers/tools-sdks/op-alloy/building/index
This section offers in-depth documentation into the various `op-alloy` crates.
Some of the primary crates and their types are listed below.
* [`op-alloy-consensus`](https://crates.io/crates/op-alloy-consensus) provides [`OpBlock`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/type.OpBlock.html),
[`OpTxEnvelope`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/transaction/enum.OpTxEnvelope.html), [`OpReceiptEnvelope`](https://docs.rs/op-alloy-consensus/latest/op_alloy_consensus/enum.OpReceiptEnvelope.html),
and more.
* [`op-alloy-rpc-types-engine`](https://crates.io/crates/op-alloy-rpc-types-engine) provides the
[`OpPayloadAttributes`](https://docs.rs/op-alloy-rpc-types-engine/latest/op_alloy_rpc_types_engine/struct.OpPayloadAttributes.html).
# op-alloy
Source: https://docs.optimism.io/app-developers/tools-sdks/op-alloy/intro
Welcome to the hands-on guide for getting started with `op-alloy`!
`op-alloy` connects applications to the OP Stack, leveraging high
performance types, traits, and middleware from [Alloy](https://github.com/alloy-rs/alloy).
**Development Status**
`op-alloy` is in active development, and is not yet ready for use in production.
During development, this documentation will evolve quickly and may contain inaccuracies.
Please [open an issue](https://github.com/ethereum-optimism/optimism/issues/new) if you find any errors or have any suggestions for
improvements, and also feel free to [contribute](https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md) to the project!
## Sections
### [Getting Started](/app-developers/tools-sdks/op-alloy/starting)
To get started with op-alloy, add its crates as a dependency and take your first steps.
### [Building with op-alloy](/app-developers/tools-sdks/op-alloy/building/index)
Walk through types and functionality available in different `op-alloy` crates.
### [Contributing](https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md)
Contributors are welcome! It is built and maintained by Alloy contributors,
members of [OP Labs](https://github.com/ethereum-optimism), and the broader open source community.
`op-alloy` follows and expands the OP Stack standards set in the [specs](https://specs.optimism.io).
### Licensing
`op-alloy` is licensed under the combined Apache 2.0 and MIT License, along
with a SNAPPY license for snappy encoding use.
# Add op-alloy to your project
Source: https://docs.optimism.io/app-developers/tools-sdks/op-alloy/starting
Add the op-alloy crates to a Rust project with Cargo and pick the feature flags you need.
[op-alloy](https://github.com/ethereum-optimism/optimism/tree/develop/rust/op-alloy) consists of a number of crates that provide a range of functionality
essential for interfacing with any OP Stack chain.
The most succinct way to work with `op-alloy` is to add the [`op-alloy`](https://crates.io/crates/op-alloy) crate
with the `full` feature flag from the command-line using Cargo.
```txt theme={null}
cargo add op-alloy --features full
```
Alternatively, you can add the following to your `Cargo.toml` file.
```txt theme={null}
op-alloy = { version = "2.0", features = ["full"] }
```
For more fine-grained control over the features you wish to include, you can add the individual
crates to your `Cargo.toml` file, or use the `op-alloy` crate with the features you need.
After `op-alloy` is added as a dependency, crates re-exported by `op-alloy` are now available.
```rust theme={null}
use op_alloy::{
consensus::OpBlock,
network::Optimism,
rpc_types::OpTransactionReceipt,
rpc_types_engine::OpPayloadAttributes,
};
```
## Features
The [`op-alloy`](https://crates.io/crates/op-alloy) defines many [feature flags](https://docs.rs/crate/op-alloy/latest/features) including the following.
Default
* `std`
* `k256`
* `serde`
Full enables the most commonly used crates: `consensus`, `network`, `rpc-types`,
`rpc-types-engine`, and `rpc-jsonrpsee`. The `provider` crate is not part of `full`
and is enabled with its own `provider` feature.
* `full`
The `k256` feature flag enables the `k256` feature on the `op-alloy-consensus` crate.
* `k256`
Arbitrary enables arbitrary features on crates, deriving the `Arbitrary` trait on types.
* `arbitrary`
Serde derives serde's Serialize and Deserialize traits on types.
* `serde`
Additionally, individual crates can be enabled using their shorthand names.
For example, the `consensus` feature flag provides the `op-alloy-consensus` re-export
so `op-alloy-consensus` types can be used from `op-alloy` through `op_alloy::consensus::InsertTypeHere`.
## Crates
* [`op-alloy-network`](https://crates.io/crates/op-alloy-network)
* [`op-alloy-provider`](https://crates.io/crates/op-alloy-provider)
* [`op-alloy-consensus`](https://crates.io/crates/op-alloy-consensus) (supports `no_std`)
* [`op-alloy-rpc-jsonrpsee`](https://crates.io/crates/op-alloy-rpc-jsonrpsee)
* [`op-alloy-rpc-types`](https://crates.io/crates/op-alloy-rpc-types) (supports `no_std`)
* [`op-alloy-rpc-types-engine`](https://crates.io/crates/op-alloy-rpc-types-engine) (supports `no_std`)
## `no_std`
As noted above, the following crates are `no_std` compatible.
* [`op-alloy-consensus`](https://crates.io/crates/op-alloy-consensus)
* [`op-alloy-rpc-types-engine`](https://crates.io/crates/op-alloy-rpc-types-engine)
* [`op-alloy-rpc-types`](https://crates.io/crates/op-alloy-rpc-types)
To add `no_std` support to a crate, ensure the [check\_no\_std](https://github.com/ethereum-optimism/optimism/blob/develop/rust/op-alloy/scripts/check_no_std.sh)
script is updated to include this crate once `no_std` compatible.
# op-revm
Source: https://docs.optimism.io/app-developers/tools-sdks/op-revm
op-revm is the Optimism variant of revm, implementing OP Stack modifications to the EVM.
`op-revm` is the Optimism variant of [revm](https://github.com/bluealloy/revm) —
the OP Stack's modifications to the Ethereum Virtual Machine, packaged as a
custom EVM built on top of the upstream `revm` framework.
## Features
`op-revm` extends `revm` with everything the OP Stack needs on top of vanilla
Ethereum execution:
* **Deposit transactions** — the L1-to-L2 deposit transaction type and its
execution semantics.
* **L1 cost accounting** — `L1BlockInfo` and per-transaction L1 fee / blob fee
calculation.
* **Operator fees** — operator fee handling introduced in Isthmus and refined
in Jovian.
* **OP-specific precompiles** — including accelerated BN254 pairing.
* **OP halt reasons & transaction errors** — OP Stack–specific execution
failure modes.
* **Hardfork-aware spec selection** — `OpSpecId` selects the right behavior for
each OP Stack hardfork (Bedrock, Regolith, Canyon, Ecotone, Fjord, Granite,
Holocene, Isthmus, Jovian, …).
## Provenance
`op-revm` is vendored from upstream
[`bluealloy/revm`'s `crates/op-revm`](https://github.com/bluealloy/revm/tree/main/crates/op-revm)
and imported into the monorepo so it can evolve in lock-step with the rest of
the OP Stack Rust code. The upstream release history lives in the
[revm releases](https://github.com/bluealloy/revm/releases).
## Crate features
The crate exposes the usual `revm` feature knobs, forwarded through to the
underlying `revm` crate:
* `default = ["std", "c-kzg", "secp256k1", "portable", "blst"]`
* `std` — enables `std`-dependent code paths in `revm`, `alloy-primitives`,
`serde_json`, etc.
* `serde` — derives `serde` impls and forwards to `revm/serde` and
`alloy-primitives/serde`.
* `portable`, `c-kzg`, `secp256k1`, `blst`, `bn` — pass-through feature gates
for the cryptographic backends.
* `dev`, `memory_limit`, `optional_balance_check`, `optional_block_gas_limit`,
`optional_eip3541`, `optional_eip3607`, `optional_no_base_fee`,
`optional_fee_charge` — debugging and testing knobs forwarded to `revm`.
`op-revm` supports `no_std` builds: disabling default features (or building
with `--no-default-features`) produces a crate suitable for fault-proof and
zkVM targets such as `riscv32imac-unknown-none-elf`.
## Building & testing
From the `rust/` workspace root:
```bash theme={null}
# Build
cargo build -p op-revm
# Run tests with all features
cargo nextest run -p op-revm --all-features
# no_std check (mirrors upstream's riscv32imac CI)
cargo build -p op-revm \
--target riscv32imac-unknown-none-elf \
--no-default-features
```
The `no_std` build is also exercised by `just check-no-std` and runs in CI on
every PR that touches `rust/**`.
## Using op-revm
`op-revm` is the EVM used by [op-reth](/op-stack/components/op-reth) for OP Stack block
execution, and by [Kona](https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona) inside the fault-proof
program and rollup node. Most users will consume it transitively through one
of those components rather than depending on it directly; depend on `op-revm`
directly only when building a custom OP Stack execution environment (for
example, an alternate client or a zkVM proof backend).
## License
`op-revm` is licensed under the MIT License — see
[`rust/op-revm/LICENSE`](https://github.com/ethereum-optimism/optimism/blob/develop/rust/op-revm/LICENSE).
# Supersim Multichain Development Environment
Source: https://docs.optimism.io/app-developers/tools-sdks/supersim
Learn how to use the Supersim local dev environment tool designed to simulate the OP Stack multi-chain environment.
Interop is currently in active development and not yet ready for production use. The information provided here may change. Check back regularly for the most up-to-date information.
[Supersim](https://github.com/ethereum-optimism/Supersim) is a local development environment tool designed to simulate the OP Stack for developers building multi-chain applications. It provides a simplified way to test and develop applications that interact with multiple chains within the OP Stack ecosystem.
## Supersim workflow
```mermaid theme={null}
graph LR
A[Write Smart Contracts] --> B[Deploy on Supersim]
B --> C[Test Cross-Chain Interactions]
C --> D[Debug and Refine]
D --> B
C --> E[Ready for Production]
```
This diagram illustrates the typical workflow for developers using Supersim, from writing smart contracts to testing and refining cross-chain interactions.
## Features and benefits
* Simulates multiple OP Stack chains locally (e.g., chain 901, 902)
* Supports testing of cross-chain messaging and interactions
* Includes pre-deployed interoperability contracts
* Offers a CLI interface for starting and managing Supersim instances
* Provides local JSON-RPC endpoints for each simulated chain
* Allows for custom configuration of chain parameters
* Facilitates testing of Superchain-specific features like cross-chain token transfers
* Easy to use with common Ethereum development tools
* Supports chain forking
## Supersim CLI interaction
```mermaid theme={null}
graph TD
A[Developer] --> B[Supersim CLI]
B --> C[Chain 901]
B --> D[Chain 902]
B --> E[...]
C --> F[JSON-RPC Endpoint]
D --> G[JSON-RPC Endpoint]
E --> H[JSON-RPC Endpoint]
```
This diagram illustrates how developers interact with Supersim through the CLI, which simulates OP Stack-specific features (specifically interop) on locally run chains, each with its own JSON-RPC endpoint and pre-deployed interoperability contracts.
## Next steps
* View more [Supersim tutorials](/app-developers/tutorials/development/supersim/first-steps)
# Tools & SDKs Support Matrix
Source: https://docs.optimism.io/app-developers/tools-sdks/support-matrix
One page that differentiates the OP Stack SDK surfaces and developer tools — what each is for, who owns it, where its canonical docs live, and its support status.
Items on this page refer to third-party projects or products that are not
maintained by Optimism. They are provided for convenience; refer to each
project's own documentation as the source of truth.
This page is the canonical hub for the tools and SDKs that surround the OP Stack.
Following the [content guide](/op-stack/contribute/content-guide), each listing
links exactly one canonical documentation home — this page routes, it does not
restate. What gets listed here (and what gets removed) is governed by the
[listing criteria](/app-developers/tools-sdks/listing-criteria).
## Support Status Levels
| Status | Meaning |
| --------------------- | ------------------------------------------------------------------------------------------ |
| **Production** | Stable and recommended for production use by its owner. |
| **Developer preview** | Usable today, but its owner has declared it not yet ready for production; APIs may change. |
| **Experimental** | A staging ground. Features may change, move, or be upstreamed elsewhere. |
## The Matrix
| Tool | Purpose | Owner | Canonical docs | Support status |
| ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| [`viem/op-stack`](https://viem.sh/op-stack) | General-purpose TypeScript client for OP Stack chains: deposits, withdrawals, L1 gas estimation, and all standard chain interaction, built into viem | [wevm](https://github.com/wevm/viem) (third party) | [viem.sh/op-stack](https://viem.sh/op-stack) | Production |
| [`@eth-optimism/viem`](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/viem) | viem extension for OP Stack features that have not yet been upstreamed into viem — Superchain interop actions and utilities; used by this site's bridging and interop tutorials | Optimism ([`ecosystem`](https://github.com/ethereum-optimism/ecosystem) repo) | [Package README & SDK reference](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/viem#readme) | Experimental |
| [Actions SDK](https://github.com/ethereum-optimism/actions) (`@eth-optimism/actions-sdk`) | High-level abstractions for building onchain apps — wallet, lend, and swap namespaces over Superchain protocols | Optimism ([`actions`](https://github.com/ethereum-optimism/actions) repo) | [Actions quickstart](/app-developers/quickstarts/actions) and [reference](/app-developers/reference/actions/integrating-wallets) on this site | Developer preview |
| [wagmi](https://wagmi.sh) | React hooks for Ethereum apps; works with OP Stack chains as with any EVM chain | [wevm](https://github.com/wevm/wagmi) (third party) | [wagmi.sh](https://wagmi.sh) | Production |
| [supersim](/app-developers/tools-sdks/supersim) | Local Superchain simulator: one L1 plus multiple OP Stack L2s for testing L1↔L2 and L2↔L2 message passing without deploying to live networks | Optimism ([`supersim`](https://github.com/ethereum-optimism/supersim) repo) | [Supersim docs](https://supersim.pages.dev) | Developer preview (all releases are alpha pre-releases; interop features in active development) |
| [super-cli](https://github.com/ethereum-optimism/super-cli) (`sup`) | Foundry companion CLI for multichain workflows: deploy and verify contracts on multiple chains at once, bridge funds, use connected wallets | Optimism ([`super-cli`](https://github.com/ethereum-optimism/super-cli) repo) | [Repository README](https://github.com/ethereum-optimism/super-cli#readme) | Experimental |
| [superchain-registry](https://github.com/ethereum-optimism/superchain-registry) tooling | Not an SDK — the source-of-truth library of chain configs that OP Stack software (such as `op-node` and `op-geth`) consumes, plus the `just` tasks and validation checks used to add and verify a chain | Optimism ([`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry) repo) | [Registry operations docs](https://github.com/ethereum-optimism/superchain-registry/tree/main/docs) | Production |
| [op-alloy](/app-developers/tools-sdks/op-alloy/intro) | Rust crates for interfacing with OP Stack chains: consensus, RPC, engine, and network types built on the [Alloy](https://github.com/alloy-rs/alloy) ecosystem; the type layer consumed by kona and op-reth | Optimism ([`rust/op-alloy`](https://github.com/ethereum-optimism/optimism/tree/develop/rust/op-alloy) in the monorepo) | [op-alloy docs](/app-developers/tools-sdks/op-alloy/intro) on this site | Developer preview (owner-declared not yet production-ready; APIs may change) |
| [op-revm](/app-developers/tools-sdks/op-revm) | Rust crate implementing the OP Stack's EVM: a revm variant with deposit transactions, L1 and operator fee accounting, and OP-specific precompiles; the EVM inside op-reth and kona | Optimism ([`rust/op-revm`](https://github.com/ethereum-optimism/optimism/tree/develop/rust/op-revm) in the monorepo, vendored from upstream [`bluealloy/revm`](https://github.com/bluealloy/revm)) | [op-revm docs](/app-developers/tools-sdks/op-revm) on this site | Production (consumed transitively through op-reth or kona by most users) |
## Choose an SDK Surface
Three of the rows above are TypeScript SDK surfaces that are easy to confuse.
The distinction:
* **Start with [`viem/op-stack`](https://viem.sh/op-stack).** viem provides
first-class OP Stack support in the core library: extend any viem client
with `publicActionsL2()` (and friends) imported from `viem/op-stack` to get
deposits, withdrawals, and L1 gas estimation. If a feature is available
here, this is its production home. Super Root withdrawal proving requires
`viem` `2.51.0` or later.
* **Reach for [`@eth-optimism/viem`](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/viem)
when you need what viem does not have yet.** The package's stated goal is
to upstream as much as possible into viem itself; until then it is the
home of Superchain interop actions and other pre-production features. The
[interop tutorials](/app-developers/tutorials/interoperability/manual-relay)
and [bridging tutorials](/app-developers/tutorials/bridging/cross-dom-bridge-erc20)
on this site use it.
* **Use the [Actions SDK](/app-developers/quickstarts/actions) for app-level
building blocks, not chain plumbing.** Where the two viem surfaces expose
protocol operations, the Actions SDK offers wallet, lend, and swap
namespaces for building end-user apps. It is a developer preview and not
yet ready for production use.
Building in Rust rather than TypeScript? Start from
[op-alloy](/app-developers/tools-sdks/op-alloy/intro) for OP Stack types and RPC
surfaces and [op-revm](/app-developers/tools-sdks/op-revm) for execution, and see
the [Stack Components index](/op-stack/components/index) for the Rust clients
themselves (op-reth, kona-node, kona-client).
## Using wagmi With the OP Stack
If you build with React, you likely start from wagmi — and it works with OP
Stack chains out of the box. OP Stack chains are standard EVM chains, and
`wagmi/chains` ships their definitions (`optimism`, `base`, `zora`, and every
other chain from `viem/chains`). Nothing OP-specific is required for reads,
writes, or wallet connections.
For OP Stack–specific operations — deposits, withdrawals, interop — use the
fact that wagmi is a wrapper over viem:
1. Get a viem client from wagmi with the
[`useClient`](https://wagmi.sh/react/api/hooks/useClient) or
[`useConnectorClient`](https://wagmi.sh/react/api/hooks/useConnectorClient)
hooks, following wagmi's own
[viem guide](https://wagmi.sh/react/guides/viem).
2. Extend that client with the [`viem/op-stack`](https://viem.sh/op-stack)
actions and call them directly.
For Superchain interop specifically, the experimental
[`@eth-optimism/wagmi`](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/wagmi)
package provides React hooks (such as `useSendL2ToL2Message`) over
`@eth-optimism/viem`. Treat it like its underlying package: experimental.
## Next Steps
* Read the [listing criteria and maintenance-sweep policy](/app-developers/tools-sdks/listing-criteria)
that govern this page.
* Adding a chain rather than building an app? See
[Join the Superchain Registry](/chain-operators/guides/join-superchain-registry).
* For where content belongs in general, see the
[content guide](/op-stack/contribute/content-guide).
# Transferring ETH
Source: https://docs.optimism.io/app-developers/tutorials/bridging/bridge-crosschain-eth
Learn how to transfer ETH across the OP Stack interop cluster
OP Stack interop is in active development. Some features may be experimental.
This tutorial provides step-by-step instructions for how to send ETH from one chain in the OP Stack interop cluster to another.
For a conceptual overview,
see the [interoperable ETH explainer](/op-stack/interop/superchain-eth-bridge).
## Overview
Crosschain ETH transfers across OP Stack chains are facilitated through the [SuperchainETHBridge](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/SuperchainETHBridge.sol) contract.
This tutorial walks through how to send ETH from one chain to another.
You can do this on [Supersim](/app-developers/tools-sdks/supersim) or production once it is released.
### What you'll build
* A TypeScript application to transfer ETH between chains
### What you'll learn
* How to send ETH on the blockchain and between blockchains
* How to relay messages between chains
## Prerequisites
Before starting this tutorial, ensure your development environment meets the following requirements:
### Technical knowledge
* Intermediate TypeScript knowledge
* Understanding of smart contract development
* Familiarity with blockchain concepts
### Development environment
* Unix-like operating system (Linux, macOS, or WSL for Windows)
* Node.js version 16 or higher
* Git for version control
### Required tools
The tutorial uses these primary tools:
* Foundry: For smart contract development
* Supersim: For local blockchain simulation
* TypeScript: For implementation
* Viem: For blockchain interaction
1. Install [Foundry](https://book.getfoundry.sh/getting-started/installation).
2. Install [Node](https://nodejs.org/en).
3. Install [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
The exact mechanism to do this depends on your operating system; most come with it preinstalled.
You can run this tutorial either with [Supersim](/app-developers/tools-sdks/supersim) running locally, or using the [Interop devnet](/app-developers/guides/building-apps).
Select the correct tab and follow the directions.
1. Follow [Install Supersim](/app-developers/tutorials/development/supersim/installation) to set up Supersim for running blockchains with Interop.
2. Start Supersim.
```sh theme={null}
./supersim --interop.autorelay
```
3. Supersim uses Foundry's `anvil` blockchains, which start with ten prefunded accounts.
Set these environment variables to access one of those accounts on the L2 blockchains.
```sh theme={null}
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
4. Specify the URLs to the chains.
```sh theme={null}
SRC_URL=http://localhost:9545
DST_URL=http://localhost:9546
```
Get the ETH balances for your address on both the source and destination chains.
```sh theme={null}
cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $SRC_URL
cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $DST_URL
```
1. Set `PRIVATE_KEY` to the private key of an address that has [Sepolia ETH](https://cloud.google.com/application/web3/faucet/ethereum/sepolia).
```sh theme={null}
export PRIVATE_KEY=0x
```
2. Send ETH to the two L2 blockchains via their OptimismPortal contracts on Sepolia.
```sh theme={null}
cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x7385d89d38ab79984e7c84fab9ce5e6f4815468a
cast send --rpc-url https://endpoints.omniatech.io/v1/eth/sepolia/public --private-key $PRIVATE_KEY --value 0.02ether 0x55f5c4653dbcde7d1254f9c690a5d761b315500c
```
3. Wait a few minutes until you can see the ETH [on the block explorer](https://sid.testnet.routescan.io/) for your address.
4. Specify the URLs to the chains.
```sh theme={null}
SRC_URL=https://interop-alpha-0.optimism.io
DST_URL=https://interop-alpha-1.optimism.io
```
Get the ETH balances for your address on both the source and destination chains.
```sh theme={null}
cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $SRC_URL
cast balance --ether `cast wallet address $PRIVATE_KEY` --rpc-url $DST_URL
```
Run these commands:
```sh theme={null}
DST_CHAINID=`cast chain-id --rpc-url $DST_URL`
MY_ADDRESS=`cast wallet address $PRIVATE_KEY`
SUPERCHAIN_ETH_BRIDGE=0x4200000000000000000000000000000000000024
BEFORE=`cast balance $MY_ADDRESS --rpc-url $DST_URL | cast from-wei`
cast send --rpc-url $SRC_URL --private-key $PRIVATE_KEY $SUPERCHAIN_ETH_BRIDGE "sendETH(address,uint256)" $MY_ADDRESS $DST_CHAINID --value 0.001ether
sleep 10
AFTER=`cast balance $MY_ADDRESS --rpc-url $DST_URL | cast from-wei`
echo -e Balance before transfer\\t$BEFORE
echo -e Balance after transfer\\t$AFTER
```
Messages are relayed automatically in the interop devnet.
```sh theme={null}
mkdir transfer-eth
cd transfer-eth
npm init -y
npm install --save-dev -y viem tsx @types/node @eth-optimism/viem typescript
mkdir src
```
```sh theme={null}
curl https://raw.githubusercontent.com/ethereum-optimism/optimism/refs/heads/develop/packages/contracts-bedrock/snapshots/abi/SuperchainETHBridge.json > src/SuperchainETHBridge.abi.json
```
```typescript theme={null}
import {
createWalletClient,
http,
publicActions,
getContract,
Address,
formatEther,
parseEther,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import {
supersimL2A,
supersimL2B,
interopAlpha0,
interopAlpha1
} from '@eth-optimism/viem/chains'
import {
walletActionsL2,
publicActionsL2,
contracts as optimismContracts
} from '@eth-optimism/viem'
import superchainEthBridgeAbi from './SuperchainETHBridge.abi.json'
const supersimAddress = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const sourceChain = account.address === supersimAddress ? supersimL2A : interopAlpha0
const destinationChain = account.address === supersimAddress ? supersimL2B : interopAlpha1
const sourceWallet = createWalletClient({
chain: sourceChain,
transport: http(),
account,
}).extend(publicActions)
.extend(publicActionsL2())
.extend(walletActionsL2())
const destinationWallet = createWalletClient({
chain: destinationChain,
transport: http(),
account,
}).extend(publicActions)
.extend(publicActionsL2())
.extend(walletActionsL2())
const ethBridgeOnSource = await getContract({
address: optimismContracts.superchainETHBridge.address,
abi: superchainEthBridgeAbi,
client: sourceWallet,
})
const reportBalance = async (address: string): Promise => {
const sourceBalance = await sourceWallet.getBalance({ address })
const destinationBalance = await destinationWallet.getBalance({ address })
console.log(`
Address: ${address}
Balance on source chain: ${formatEther(sourceBalance)}
Balance on destination chain: ${formatEther(destinationBalance)}
`)
}
console.log('Before transfer')
await reportBalance(account.address)
const sourceHash = await ethBridgeOnSource.write.sendETH({
value: parseEther('0.001'),
args: [account.address, destinationChain.id],
})
const sourceReceipt = await sourceWallet.waitForTransactionReceipt({
hash: sourceHash,
})
console.log('After transfer on source chain')
await reportBalance(account.address)
const sentMessages = await sourceWallet.interop.getCrossDomainMessages({
logs: sourceReceipt.logs,
})
const sentMessage = sentMessages[0]
const relayMessageParams = await sourceWallet.interop.buildExecutingMessage({
log: sentMessage.log,
})
const relayMsgTxnHash = await destinationWallet.interop.relayCrossDomainMessage(relayMessageParams)
await destinationWallet.waitForTransactionReceipt({ hash: relayMsgTxnHash })
console.log('After relaying message to destination chain')
await reportBalance(account.address)
```
```typescript theme={null}
import {
supersimL2A,
supersimL2B,
interopAlpha0,
interopAlpha1
} from '@eth-optimism/viem/chains'
```
Import all chain definitions from `@eth-optimism/viem`.
```typescript theme={null}
const supersimAddress="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const sourceChain = account.address == supersimAddress ? supersimL2A : interopAlpha0
const destinationChain = account.address == supersimAddress ? supersimL2B : interopAlpha1
```
If the address we use is `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266`, one of the prefunded addresses on `anvil`, assume we're using Supersim.
Otherwise, use Interop devnet.
```typescript theme={null}
const sourceHash = await ethBridgeOnSource.write.sendETH({
value: parseEther('0.001'),
args: [account.address, destinationChain.id]
})
const sourceReceipt = await sourceWallet.waitForTransactionReceipt({
hash: sourceHash
})
```
To relay a message we need the information in the receipt.
Also, we need to wait until the transaction with the relayed message is actually part of a block.
```typescript theme={null}
const sentMessages = await sourceWallet.interop.getCrossDomainMessages({
logs: sourceReceipt.logs,
})
const sentMessage = sentMessages[0]
```
A single transaction can send multiple messages.
But here we know we sent just one, so we look for the first one in the list.
```typescript theme={null}
const relayMessageParams = await sourceWallet.interop.buildExecutingMessage({
log: sentMessage.log,
})
const relayMsgTxnHash = await destinationWallet.interop.relayCrossDomainMessage(relayMessageParams)
```
This is how you use `@eth-optimism/viem` to create an executing message.
1. Run the example.
```sh theme={null}
npx tsx src/transfer-eth.mts
```
2. Read the results.
```
Before transfer
Address: 0x7ED53BfaA58B79Dd655B2f229258C093b6C09A8C
Balance on source chain: 0.020999799151902245
Balance on destination chain: 0.026999459226731331
```
The initial state. Note that the address depends on your private key; it should be different from mine.
```
After transfer on source chain
Address: 0x7ED53BfaA58B79Dd655B2f229258C093b6C09A8C
Balance on source chain: 0.019999732176717961
Balance on destination chain: 0.026999459226731331
```
After the initiating message the balance on the source chain is immediately reduced.
Notice that even though we are sending 0.001 ETH, the balance on the source chain is reduced by a bit more (here, approximately 67 gwei).
This is the cost of the initiating transaction on the source chain.
Of course, as there has been no transaction on the destination chain, that balance is unchanged.
```
After relaying message to destination chain
Address: 0x7ED53BfaA58B79Dd655B2f229258C093b6C09A8C
Balance on source chain: 0.019999732176717961
Balance on destination chain: 0.027999278943880868
```
Now the balance on the destination chain increases, by slightly less than 0.001 ETH.
The executing message also has a transaction cost (in this case, about 180gwei).
## Next steps
* Check out the [SuperchainETHBridge guide](/op-stack/interop/superchain-eth-bridge) for more information.
* Review the [OP Stack interop explainer](/op-stack/interop/explainer) for answers to common questions about interoperability.
# Bridging ERC-20 tokens to OP Mainnet
Source: https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-erc20
Learn how to use @eth-optimism/viem and viem packages to transfer ERC-20 tokens between Layer 1 (Ethereum or Sepolia) and Layer 2 (OP Mainnet or OP Sepolia).
This tutorial explains how you can use [@eth-optimism/viem](https://www.npmjs.com/package/@eth-optimism/viem) and [viem](https://viem.sh/op-stack) to bridge ERC-20 tokens between L1 (Ethereum or Sepolia) and L2 (OP Mainnet or OP Sepolia).
The `@eth-optimism/viem` and `viem` packages are an easy way to add bridging functionality to your javascript-based application.
They also provide some safety rails to prevent common mistakes that could cause tokens to be made inaccessible.
Behind the scenes, `@eth-optimism/viem` package uses the [Standard Bridge](/app-developers/guides/bridging/standard-bridge) contracts to transfer tokens.
Make sure to check out the [Standard Bridge guide](/app-developers/guides/bridging/standard-bridge) if you want to learn more about how the bridge works under the hood.
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 they can cause bridge accounting errors.
## Supported networks
Viem supports any of the [OP Stack networks](https://viem.sh/op-stack/chains).
If you want to use a network that isn't included by default, you can add it to Viem's chain [configurations](https://viem.sh/op-stack/chains#configuration).
## Dependencies
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
## Create a demo project
You're going to use the library for this tutorial.
Since is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.
```bash theme={null}
mkdir bridging-erc20-tokens
cd bridging-erc20-tokens
```
```bash theme={null}
pnpm init
```
```bash theme={null}
pnpm add @eth-optimism/viem viem
```
## Get ETH on Sepolia and OP Sepolia
This tutorial explains how to bridge tokens from Sepolia to OP Sepolia.
You will need to get some ETH on both of these testnets.
## Add a private key to your environment
You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
Make sure this private key corresponds to an address that has ETH on .
Want to create a new wallet for this tutorial?
If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
```bash theme={null}
export TUTORIAL_PRIVATE_KEY=0x...
```
## Start the Node REPL
You're going to use the Node REPL to interact with .
To start the Node REPL, run the following command in your terminal:
```bash theme={null}
node
```
This will bring up a Node REPL prompt that allows you to run JavaScript code.
## Import dependencies
You need to import some dependencies into your Node REPL session.
The `@eth-optimism/viem` package uses ESM modules, and to use in the Node.js REPL, you need to use dynamic imports with await.
Here's how to do it:
```js theme={null}
const viem = await import('viem');
const { createPublicClient, createWalletClient, http, formatEther, parseEther} = viem;
const accounts = await import('viem/accounts');
const { privateKeyToAccount } = accounts;
const viemChains = await import('viem/chains');
const { optimismSepolia, sepolia } = viemChains;
const opActions = await import('@eth-optimism/viem/actions');
const { depositERC20, withdrawOptimismERC20 } = opActions;
```
## Set session variables
You'll need a few variables throughout this tutorial.
Let's set those up now.
This step retrieves your private key from the environment variable you set earlier and converts it into an account object that Viem can use for transaction signing.
The private key is essential for authorizing transactions on both L1 and L2 networks.
For security reasons, we access it from an environment variable rather than hardcoding it.
```js theme={null}
const PRIVATE_KEY = process.env.TUTORIAL_PRIVATE_KEY;
const account = privateKeyToAccount(PRIVATE_KEY);
```
Here we establish the connections to both networks by creating four different clients:
1. L1 Public Client: For reading data from the Sepolia network
2. L1 Wallet Client: For signing and sending transactions on Sepolia
3. L2 Public Client: For reading data from OP Sepolia
4. L2 Wallet Client: For signing and sending transactions on OP Sepolia
Each client is configured with the appropriate chain information and RPC endpoint.
This dual-network setup allows us to seamlessly interact with both layers using the same account.
Replace `` with your API key from a RPC provider.
```js theme={null}
const L1_RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com';
const L2_RPC_URL = 'https://sepolia.optimism.io';
const publicClientL1 = createPublicClient({
chain: sepolia,
transport: http(L1_RPC_URL),
});
const walletClientL1 = createWalletClient({
account,
chain: sepolia,
transport: http(L1_RPC_URL),
});
const publicClientL2 = createPublicClient({
chain: optimismSepolia,
transport: http(L2_RPC_URL),
});
const walletClientL2 = createWalletClient({
account,
chain: optimismSepolia,
transport: http(L2_RPC_URL),
});
```
We define the addresses of the ERC-20 tokens on both networks.
These are specially deployed test tokens with corresponding implementations on both L1 (Sepolia) and L2 (OP Sepolia).
The L2 token is configured to recognize deposits from its L1 counterpart.
We also define a constant `oneToken` representing the full unit (10^18 wei) to simplify our deposit and withdrawal operations.
```js theme={null}
const l1Token = "0x5589BB8228C07c4e15558875fAf2B859f678d129";
const l2Token = "0xD08a2917653d4E460893203471f0000826fb4034";
```
If you're coming from the [Create an L2 token for the Standard
Bridge](./standard-bridge-standard-token) tutorial, you can use the addresses
of your own ERC-20 tokens here instead.
## Get L1 tokens
You're going to need some tokens on L1 that you can bridge to L2.
The L1 testing token located at [`0x5589BB8228C07c4e15558875fAf2B859f678d129`](https://sepolia.etherscan.io/address/0x5589BB8228C07c4e15558875fAf2B859f678d129) has a `faucet` function that makes it easy to get tokens.
The Application Binary Interface (ABI) defines how to interact with the smart contract functions. This ERC-20 ABI includes several critical functions:
* `balanceOf`: Allows us to check token balances for any address
* `faucet`: A special function in this test token that mints new tokens to the caller
* `approve`: Required to grant the bridge permission to transfer tokens on our behalf
* `allowance`: To check how many tokens we've approved for the bridge
* `decimals` and `symbol`: Provide token metadata
This comprehensive ABI gives us everything we need to manage our tokens across both L1 and L2.
```js theme={null}
const erc20ABI = [
{
inputs: [
{
internalType: "address",
name: "account",
type: "address",
},
],
name: "balanceOf",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "faucet",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "spender",
type: "address"
},
{
internalType: "uint256",
name: "value",
type: "uint256"
}
],
name: "approve",
outputs: [
{
internalType: "bool",
name: "",
type: "bool"
}
],
stateMutability: "nonpayable",
type: "function"
},
];
```
Now we'll call the `faucet` function on the L1 test token contract to receive free tokens for testing.
This transaction will mint new tokens directly to our wallet address.
The function doesn't require any parameters - it simply credits a predetermined amount to whoever calls it.
We store the transaction hash for later reference and wait for the transaction to be confirmed.
```js theme={null}
console.log('Getting tokens from faucet...');
const tx = await walletClientL1.writeContract({
address: l1Token,
abi: erc20ABI,
functionName: 'faucet',
account,
});
console.log('Faucet transaction:', tx);
```
After using the faucet, we verify our token balance by calling the `balanceOf` function on the L1 token contract.
This step confirms that we've successfully received tokens before proceeding with the bridging process.
The balance is returned in the smallest unit (wei), but we format it into a more readable form using the `formatEther` utility function from `viem`, since this token uses 18 decimal places.
```js theme={null}
const l1Balance = await publicClientL1.readContract({
address: l1Token,
abi: erc20ABI,
functionName: 'balanceOf',
args: [account.address]
});
console.log(`L1 Balance after receiving faucet: ${formatEther(l1Balance)}`);
```
## Deposit tokens
Now that you have some tokens on L1, you can deposit those tokens into the `L1StandardBridge` contract.
You'll then receive the same number of tokens on L2 in return.
We define a variable `oneToken` that represents 1 full token in its base units (wei).
ERC-20 tokens typically use 18 decimal places, so 1 token equals 10^18 wei.
This constant helps us work with precise token amounts in our transactions, avoiding rounding errors and ensuring exact value transfers.
We'll use this value for both deposits and withdrawals
```js theme={null}
const oneToken = parseEther('1')
```
ERC-20 tokens require a two-step process for transferring tokens on behalf of a user.
First, we must grant permission to the bridge contract to spend our tokens by calling the `approve` function on the token contract.
We specify the bridge address from the chain configuration and the exact amount we want to bridge.
This approval transaction must be confirmed before the bridge can move our tokens.
```js theme={null}
const bridgeAddress = optimismSepolia.contracts.l1StandardBridge[sepolia.id].address;
const approveTx = await walletClientL1.writeContract({
address: l1Token,
abi: erc20ABI,
functionName: 'approve',
args: [bridgeAddress, oneToken],
});
console.log('Approval transaction:', approveTx);
```
After submitting the approval transaction, we need to wait for it to be confirmed on L1.
We use the `waitForTransactionReceipt` function to monitor the transaction until it's included in a block.
The receipt provides confirmation details, including which block includes our transaction.
This step ensures our approval is finalized before attempting to bridge tokens.
```js theme={null}
await publicClientL1.waitForTransactionReceipt({ hash: approveTx });
```
Now we can execute the actual bridging operation using the `depositERC20` function from the `@eth-optimism/viem` package.
This function handles all the complex interactions with the `L1StandardBridge` contract for us.
We provide:
* The addresses of both the L1 and L2 tokens
* The amount to bridge
* The target chain (OP Sepolia)
* Our wallet address as the recipient on L2
* A minimum gas limit for the L2 transaction
This streamlined process ensures our tokens are safely transferred to L2.
```js theme={null}
console.log('Depositing tokens to L2...');
const depositTx = await depositERC20(walletClientL1, {
tokenAddress: l1Token,
remoteTokenAddress: l2Token,
amount: oneToken,
targetChain: optimismSepolia,
to: account.address,
minGasLimit: 200000,
});
console.log(`Deposit transaction hash: ${depositTx}`);
```
Using a smart contract wallet? As a safety measure, `depositERC20` will fail
if you try to deposit ETH from a smart contract wallet without specifying a
`recipient`. Add the `recipient` option to the `depositERC20` call to fix
this. Check out the [@eth-optimism/viem
docs](https://github.com/ethereum-optimism/ecosystem/tree/main/packages/viem) for
more info on the options you can pass to `depositERC20`.
After initiating the deposit, we need to wait for the L1 transaction to be confirmed.
This function tracks the transaction until it's included in an L1 block.
Note that while this confirms the deposit was accepted on L1, there will still be a short delay (typically a few minutes) before the tokens appear on L2, as the transaction needs to be processed by the Optimism sequencer.
```js theme={null}
const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositTx });
console.log(`Deposit confirmed in block ${depositReceipt.blockNumber}`);
```
After the deposit transaction is confirmed, we check our token balance on L1 again to verify that the tokens have been deducted.
This balance should be lower by the amount we bridged, as those tokens are now escrowed in the `L1StandardBridge` contract.
This step helps confirm that the first part of the bridging process completed successfully:
```js theme={null}
const l1BalanceAfterDeposit = await publicClientL1.readContract({
address: l1Token,
abi: erc20ABI,
functionName: 'balanceOf',
args: [account.address]
});
console.log(`L1 Balance after deposit: ${formatEther(l1BalanceAfterDeposit)}`);
```
After allowing some time for the L2 transaction to be processed, we check our token balance on L2 to verify that we've received the bridged tokens.
The newly minted L2 tokens should appear in our wallet at the same address we used on L1.
This step confirms the complete success of the bridge operation from L1 to L2.
```js theme={null}
const l2Balance = await publicClientL2.readContract({
address: l2Token,
abi: erc20ABI,
functionName: 'balanceOf',
args: [account.address]
});
console.log(`L2 Balance after withdrawal: ${formatEther(l2Balance)}`);
```
## Withdraw tokens
You just bridged some tokens from L1 to L2.
Nice!
Now you're going to repeat the process in reverse to bridge some tokens from L2 to L1.
To move tokens back to L1, we use the `withdrawOptimismERC20` function from the `@eth-optimism/viem` package.
This function interacts with the `L2StandardBridge` contract to initialize the withdrawal process.
We specify:
* The L2 token address
* The amount to withdraw (we're using half of a token in this tutorial)
* Our address as the recipient on L1
* A minimum gas limit for the transaction
Unlike deposits, withdrawals from L2 to L1 are not immediate and require a multi-step process including a 7-day challenge period for security reasons.
```js theme={null}
console.log('Withdrawing tokens back to L1...');
const withdrawTx = await withdrawOptimismERC20(walletClientL2, {
tokenAddress: l2Token,
amount: oneToken / 2n,
to: account.address,
minGasLimit: 200000,
});
console.log(`Withdrawal transaction hash: ${withdrawTx}`);
```
Similar to deposits, we wait for the withdrawal transaction to be confirmed on L2.
This receipt provides confirmation that the withdrawal has been initiated.
The transaction logs contain critical information that will be used later in the withdrawal verification process.
This is only the first step in the withdrawal - the tokens are now locked on L2, but not yet available on L1.
```js theme={null}
const withdrawReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawTx });
console.log(`Withdrawal initiated in L2 block ${withdrawReceipt.blockNumber}`);
```
This step can take a few minutes. Feel free to take a quick break while you
wait.
After the withdrawal transaction is confirmed, we check our token balance on L2 again to verify that the tokens have been deducted.
Our L2 balance should now be lower by the amount we initiated for withdrawal.
At this point, the withdrawal process has begun, but the tokens are not yet available on L1 - please refer to [Withdraw ETH](./cross-dom-bridge-eth#withdraw-eth) to continue with the “prove” and “finalize” withdrawal steps.
```js theme={null}
const l2Balance = await publicClientL2.readContract({
address: l2Token,
abi: erc20ABI,
functionName: 'balanceOf',
args: [account.address]
});
console.log(`L2 Balance after withdrawal initiation: ${formatEther(l2Balance)}`);
```
## Next steps
Congrats!
You've just deposited and withdrawn tokens using `@eth-optimism/viem` package.
You should now be able to write applications that use the `@eth-optimism/viem` package to transfer ERC-20 tokens between L1 and L2.
Although this tutorial used Sepolia and OP Sepolia, the same process works for Ethereum and OP Mainnet.
# Submitting Transactions from L1
Source: https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-bridge-eth
Learn how to process deposit transactions and withdrawals with Viem.
**Learn the OP Stack — stop 10 of 13.**
You've read how deposits and withdrawals work. In this project you walk
both directions end to end from code, using Viem. When you're done,
continue to
[Fault proofs explainer](/op-stack/fault-proofs/explainer).
This tutorial explains how to use [Viem](https://viem.sh/op-stack) to process cross-domain transactions:
* [Deposited transactions](https://specs.optimism.io/protocol/deposits.html): Also known as deposits, these are transactions initiated on L1 and executed on L2. They can be used to submit arbitrary L2 transactions from L1.
* [Withdrawals](https://specs.optimism.io/protocol/withdrawals.html): These are cross-domain transactions initiated on L2 and finalized by a transaction executed on L1. They can be used to send arbitrary messages on L1 from L2 via the `OptimismPortal`.
Both deposit transactions and withdrawals can transfer ETH and data.
## Supported networks
Viem supports any of the [OP Stack networks](https://viem.sh/op-stack/chains).
The OP Stack networks are included in Viem by default.
If you want to use a network that isn't included by default, you can add it to Viem's chain [configurations](https://viem.sh/op-stack/chains#configuration).
## Dependencies
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
## Create a demo project
You're going to use the library for this tutorial.
Since is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.
```bash theme={null}
mkdir bridge-eth
cd bridge-eth
```
```bash theme={null}
pnpm init
```
```bash theme={null}
pnpm add viem@^2.51.0
```
## Get ETH on Sepolia
This tutorial explains how to bridge ETH from Sepolia to OP Sepolia.
You will need to get some ETH on Sepolia to follow along.
## Add a private key to your environment
You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
Make sure this private key corresponds to an address that has ETH on .
Want to create a new wallet for this tutorial?
If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
```bash theme={null}
export TUTORIAL_PRIVATE_KEY=0x...
```
## Start the Node REPL
You're going to use the Node REPL to interact with .
To start the Node REPL, run the following command in your terminal:
```bash theme={null}
node
```
This will bring up a Node REPL prompt that allows you to run JavaScript code.
## Import dependencies
You need to import some dependencies into your Node REPL session.
```js theme={null}
const { createPublicClient, http, createWalletClient, parseEther, formatEther } = require('viem');
const { sepolia, optimismSepolia } = require('viem/chains');
const { privateKeyToAccount } = require('viem/accounts');
const { getL2TransactionHashes, publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2, isSuperGameType } = require('viem/op-stack');
```
```js theme={null}
const PRIVATE_KEY = process.env.TUTORIAL_PRIVATE_KEY;
const account = privateKeyToAccount(PRIVATE_KEY);
```
```js theme={null}
const publicClientL1 = createPublicClient({
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
}).extend(publicActionsL1())
```
```js theme={null}
const walletClientL1 = createWalletClient({
account,
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
}).extend(walletActionsL1());
```
```js theme={null}
const publicClientL2 = createPublicClient({
chain: optimismSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(publicActionsL2());
```
```js theme={null}
const walletClientL2 = createWalletClient({
account,
chain: optimismSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(walletActionsL2());
```
## Get ETH on Sepolia
You're going to need some ETH on L1 that you can bridge to L2.
You can get some Sepolia ETH from [this faucet](https://sepoliafaucet.com).
## Deposit ETH
Now that you have some ETH on L1, in addition to using the method described in [Bridging ETH](/app-developers/guides/bridging/standard-bridge#bridging-eth), you can also deposit ETH using the approach shown in the example below.
If you are using a contract account, you should pay attention to [Address Aliasing](https://specs.optimism.io/protocol/deposits.html#address-aliasing).
See how much ETH you have on L1 so you can confirm that the deposit worked later on.
```js theme={null}
const l1Balance = await publicClientL1.getBalance({ address: account.address });
console.log(`L1 Balance: ${formatEther(l1Balance)} ETH`);
```
We used `formatEther` method from `viem` to format the balance to ether.
Use [buildDepositTransaction](https://viem.sh/op-stack/actions/buildDepositTransaction) to build the deposit transaction parameters on L2.
Be sure to understand the meanings of the optional parameters `mint` and `value`. You can also use someone else’s address as the `to` value if desired.
```js theme={null}
const depositArgs = await publicClientL2.buildDepositTransaction({
mint: parseEther("0.01"),
to: account.address,
});
```
Send the deposit transaction on L1 and log the L1 transaction hash.
```js theme={null}
const depositHash = await walletClientL1.depositTransaction(depositArgs);
console.log(`Deposit transaction hash on L1: ${depositHash}`);
```
Wait for the L1 transaction to be processed and log the receipt.
```js theme={null}
const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositHash });
console.log('L1 transaction confirmed:', depositReceipt);
```
Extracts the corresponding L2 transaction hash from the L1 receipt, and logs it.
This hash represents the deposit transaction on L2.
```js theme={null}
const [l2Hash] = getL2TransactionHashes(depositReceipt);
console.log(`Corresponding L2 transaction hash: ${l2Hash}`);
```
Wait for the L2 transaction to be processed and confirmed and logs the L2 receipt to verify completion.
```js theme={null}
const l2Receipt = await publicClientL2.waitForTransactionReceipt({
hash: l2Hash,
});
console.log('L2 transaction confirmed:', l2Receipt);
console.log('Deposit completed successfully!');
```
```js theme={null}
const { createPublicClient, http, createWalletClient, parseEther, formatEther } = require('viem');
const { sepolia, optimismSepolia } = require('viem/chains');
const { privateKeyToAccount } = require('viem/accounts');
const { getL2TransactionHashes, publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2, isSuperGameType } = require('viem/op-stack');
const PRIVATE_KEY = process.env.TUTORIAL_PRIVATE_KEY;
const account = privateKeyToAccount(PRIVATE_KEY);
const publicClientL1 = createPublicClient({
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
}).extend(publicActionsL1())
const walletClientL1 = createWalletClient({
account,
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
}).extend(walletActionsL1());
const publicClientL2 = createPublicClient({
chain: optimismSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(publicActionsL2());
const walletClientL2 = createWalletClient({
account,
chain: optimismSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(walletActionsL2());
const l1Balance = await publicClientL1.getBalance({ address: account.address });
console.log(`L1 Balance: ${formatEther(l1Balance)} ETH`);
async function depositETH() {
const depositArgs = await publicClientL2.buildDepositTransaction({
mint: parseEther("0.01"),
to: account.address,
});
const depositHash = await walletClientL1.depositTransaction(depositArgs);
console.log(`Deposit transaction hash on L1: ${depositHash}`);
const depositReceipt = await publicClientL1.waitForTransactionReceipt({ hash: depositHash });
console.log('L1 transaction confirmed:', depositReceipt);
const [l2Hash] = getL2TransactionHashes(depositReceipt);
console.log(`Corresponding L2 transaction hash: ${l2Hash}`);
const l2Receipt = await publicClientL2.waitForTransactionReceipt({
hash: l2Hash,
});
console.log('L2 transaction confirmed:', l2Receipt);
console.log('Deposit completed successfully!');
}
```
## Withdraw ETH
You just bridged some ETH from L1 to L2.
Nice!
Now you’re going to repeat the process in reverse to bridge some ETH from L2 to L1.
In addition to the method described in [Bridging ETH](/app-developers/guides/bridging/standard-bridge#bridging-eth), you can also withdraw ETH using the example approach shown below.
Uses `buildInitiateWithdrawal` to create the withdrawal parameters.
Converts the withdrawal amount to `wei` and specifies the recipient on L1.
```js theme={null}
//Add the same imports used in DepositETH function
const withdrawalArgs = await publicClientL1.buildInitiateWithdrawal({
value: parseEther('0.005'),
to: account.address,
});
```
This sends the withdrawal transaction on L2, which initiates the withdrawal process on L2 and logs a transaction hash for tracking the withdrawal.
```js theme={null}
const withdrawalHash = await walletClientL2.initiateWithdrawal(withdrawalArgs);
console.log(`Withdrawal transaction hash on L2: ${withdrawalHash}`);
```
Wait one hour (max) for the L2 Output containing the transaction to be proposed, and log the receipt, which contains important details like the block number etc.
```js theme={null}
const withdrawalReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawalHash });
const withdrawalBlock = await publicClientL2.getBlock({
blockNumber: withdrawalReceipt.blockNumber
});
const respectedGameType = await publicClientL1.readContract({
abi: [{
inputs: [],
name: 'respectedGameType',
outputs: [{ type: 'uint32' }],
stateMutability: 'view',
type: 'function'
}],
address: publicClientL2.chain.contracts.portal[publicClientL1.chain.id].address,
functionName: 'respectedGameType'
});
const proveContext = isSuperGameType(Number(respectedGameType))
? { l2Timestamp: withdrawalBlock.timestamp }
: {};
console.log('L2 transaction confirmed:', withdrawalReceipt);
```
Next, is to prove to the bridge on L1 that the withdrawal happened on L2. To achieve that, you first need to wait until the withdrawal is ready to prove.
```js theme={null}
const { game, withdrawal } = await publicClientL1.waitToProve({
...proveContext,
receipt: withdrawalReceipt,
targetChain: walletClientL2.chain
});
```
Build parameters to prove the withdrawal on the L2.
```js theme={null}
const proveArgs = await publicClientL2.buildProveWithdrawal({
game,
withdrawal,
});
```
Once the withdrawal is ready to be proven, you'll send an L1 transaction to prove that the withdrawal happened on L2.
```js theme={null}
const proveHash = await walletClientL1.proveWithdrawal(proveArgs);
const proveReceipt = await publicClientL1.waitForTransactionReceipt({ hash: proveHash });
```
Before a withdrawal transaction can be finalized, you will need to wait for the finalization period.
This can only happen after the fault proof period has elapsed. On OP Mainnet, this takes 7 days.
```js theme={null}
const awaitWithdrawal = await publicClientL1.waitToFinalize({
targetChain: walletClientL2.chain,
withdrawalHash: withdrawal.withdrawalHash,
});
```
We're currently testing fault proofs on OP Sepolia, so withdrawal times
reflect Mainnet times.
```js theme={null}
const finalizeHash = await walletClientL1.finalizeWithdrawal({
targetChain: walletClientL2.chain,
withdrawal,
});
```
```js theme={null}
const finalizeReceipt = await publicClientL1.waitForTransactionReceipt({
hash: finalizeHash
});
```
```js theme={null}
//Add the same imports used in DepositETH function
const withdrawalArgs = await publicClientL1.buildInitiateWithdrawal({
value: parseEther('0.005'),
to: account.address,
});
const withdrawalHash = await walletClientL2.initiateWithdrawal(withdrawalArgs);
console.log(`Withdrawal transaction hash on L2: ${withdrawalHash}`);
const withdrawalReceipt = await publicClientL2.waitForTransactionReceipt({ hash: withdrawalHash });
const withdrawalBlock = await publicClientL2.getBlock({
blockNumber: withdrawalReceipt.blockNumber
});
const respectedGameType = await publicClientL1.readContract({
abi: [{
inputs: [],
name: 'respectedGameType',
outputs: [{ type: 'uint32' }],
stateMutability: 'view',
type: 'function'
}],
address: publicClientL2.chain.contracts.portal[publicClientL1.chain.id].address,
functionName: 'respectedGameType'
});
const proveContext = isSuperGameType(Number(respectedGameType))
? { l2Timestamp: withdrawalBlock.timestamp }
: {};
console.log('L2 transaction confirmed:', withdrawalReceipt);
const { game, withdrawal } = await publicClientL1.waitToProve({
...proveContext,
receipt: withdrawalReceipt,
targetChain: walletClientL2.chain
});
const proveArgs = await publicClientL2.buildProveWithdrawal({
game,
withdrawal,
});
const proveHash = await walletClientL1.proveWithdrawal(proveArgs);
const proveReceipt = await publicClientL1.waitForTransactionReceipt({ hash: proveHash });
const awaitWithdrawal = await publicClientL1.waitToFinalize({
targetChain: walletClientL2.chain,
withdrawalHash: withdrawal.withdrawalHash,
});
const finalizeHash = await walletClientL1.finalizeWithdrawal({
targetChain: walletClientL2.chain,
withdrawal,
});
const finalizeReceipt = await publicClientL1.waitForTransactionReceipt({
hash: finalizeHash
});
```
Recommend checking with [getWithdrawalStatus](https://viem.sh/op-stack/actions/getWithdrawalStatus) before the `waitToProve` and `waitToFinalize` actions.
```js theme={null}
const status = await publicClientL1.getWithdrawalStatus({
...proveContext,
receipt: withdrawalReceipt,
targetChain: walletClientL2.chain
})
console.log(`Withdrawal status: ${status}`)
```
## Submitting Arbitrary L2 Transactions from L1
EOAs can submit any transaction on L1 that needs to be executed on L2. This also makes it possible for users to interact with contracts on L2 even when the [Sequencer is down](/op-stack/transactions/forced-transaction).
If the caller is a contract on L1, you need to pay attention to [Address Aliasing](https://specs.optimism.io/protocol/deposits.html#address-aliasing).
If you have just completed the [Bridging ERC-20 tokens to OP Mainnet](/app-developers/tutorials/bridging/cross-dom-bridge-erc20) tutorial, you can try initiating an ERC-20 transfer transaction on L1 that will be executed on L2.
`encodeFunctionData` and `erc20Abi` can be imported from Viem.
```js theme={null}
const oneToken = parseEther('1')
// L2 faucet token contract
const to = "0xD08a2917653d4E460893203471f0000826fb4034"
const data = encodeFunctionData({
abi: erc20Abi,
functionName: "transfer",
args: [
"0x000000000000000000000000000000000000dEaD", // recipient
oneToken / 2n,
],
});
const args = await publicClientL2.buildDepositTransaction({
account,
data,
to,
});
```
## Use `OptimismPortal` to Send Arbitrary Messages on L1 from L2
The `L2ToL1MessagePasser` contract’s `initiateWithdrawal` function accepts a `_target` address and `_data` bytes. These are passed to a CALL opcode on L1 when `finalizeWithdrawalTransaction` is executed after the challenge period.
This means that, by design, the `OptimismPortal` contract can be used to send arbitrary transactions on L1, with the `OptimismPortal` acting as the `msg.sender`.
## Important Considerations
* The purpose of this tutorial is to introduce deposited transactions and withdrawals. You should first consider whether the [standard bridge](/app-developers/guides/bridging/standard-bridge) and the [messenger](/app-developers/guides/bridging/messaging) meet your use case requirements.
* When working with deposited transactions, consider the implications of [Address Aliasing](https://specs.optimism.io/protocol/deposits.html#address-aliasing).
* When working with withdrawals, consider that [OptimismPortal can send arbitrary messages on L1](https://specs.optimism.io/protocol/withdrawals.html#optimismportal-can-send-arbitrary-messages-on-l1).
* Challenge period: The 7-day withdrawal challenge period is crucial for security.
* Gas costs: Withdrawals involve transactions on both L2 and L1, each incurring gas fees.
* Private key handling: Use secure key management practices in real applications.
* RPC endpoint security: Keep your API key (or any RPC endpoint) secure.
# Communicating between OP Stack and Ethereum in Solidity
Source: https://docs.optimism.io/app-developers/tutorials/bridging/cross-dom-solidity
Learn how to write Solidity contracts on OP Stack and Ethereum that can talk to each other.
This tutorial explains how to write Solidity contracts on OP Stack and Ethereum that can talk to each other.
Here you'll use a contract on OP Stack that can set a "greeting" variable on a contract on Ethereum, and vice-versa.
This is a simple example, but the same technique can be used to send any kind of message between the two chains.
You won't actually be deploying any smart contracts as part of this tutorial.
Instead, you'll reuse existing contracts that have already been deployed to OP Stack and Ethereum.
Later in the tutorial you'll learn exactly how these contracts work so you can follow the same pattern to deploy your own contracts.
Just looking to bridge tokens between OP Stack and Ethereum?
Check out the tutorial on [Bridging ERC-20 Tokens to OP Stack With viem](./cross-dom-bridge-erc20).
## Message passing basics
OP Stack uses a smart contract called the `CrossDomainMessenger` to pass messages between OP Stack and Ethereum.
Both chains have a version of this contract (the `L1CrossDomainMessenger` and the `L2CrossDomainMessenger`).
Messages sent from Ethereum to OP Stack are automatically relayed behind the scenes.
Messages sent from OP Stack to Ethereum must be explicitly relayed with a second transaction on Ethereum.
Read more about message passing in the guide to [Sending Data Between L1 and L2](/app-developers/guides/bridging/messaging).
## Dependencies
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
## Get ETH on Sepolia and OP Sepolia
This tutorial explains how to send messages from Sepolia to OP Sepolia.
You will need to get some ETH on both of these testnets.
## Review the contracts
You're about to use two contracts that have already been deployed to Sepolia and OP Sepolia, the `Greeter` contracts.
You can review the source code for the L1 `Greeter` contract [here on Etherscan](https://sepolia.etherscan.io/address/0x31A6Dd971306bb72f2ffF771bF30b1B98dB8B2c5#code).
You can review the source code for the L2 `Greeter` contract [here on Etherscan](https://testnet-explorer.optimism.io/address/0x5DE8a2957eddb140567fF90ba5d57bc9769f3055#code).
Both contracts have exactly the same source code.
Feel free to review the source code for these two contracts now if you'd like.
This tutorial will explain how these contracts work in detail later on in the [How It Works](#how-it-works) section below.
## Interact with the L1 Greeter
You're first going to use the L1 `Greeter` contract to set the greeting on the L2 `Greeter` contract.
You'll send a transaction directly to the L1 `Greeter` contract which will ask the `L1CrossDomainMessenger` to send a message to the L2 `Greeter` contract.
After just a few minutes, you'll see the corresponding greeting set on the L2 `Greeter` contract.
Sending a message to the L2 `Greeter` contract via the L1 `Greeter` contract requires that you call the `sendGreeting` function.
For simplicity, you'll interact with the contract directly on Etherscan.
Open up the [L1 `Greeter` contract on Sepolia Etherscan](https://sepolia.etherscan.io/address/0x31A6Dd971306bb72f2ffF771bF30b1B98dB8B2c5#writeContract) and click the "Connect to Web3" button.
Put a greeting into the field next to the "sendGreeting" function and click the "Write" button.
You can use any greeting you'd like.
It will take a few minutes for your message to reach L2.
Feel free to take a quick break while you wait.
You can use viem to programmatically check the status of any message between L1 and L2.
Later on in this tutorial you'll learn how to use viem and the `waitToProve` function to wait for various message statuses.
This same function can be used to wait for a message to be relayed from L1 to L2.
After a few minutes, you should see the greeting on the L2 `Greeter` contract change to the greeting you set.
Open up the [L2 `Greeter` contract on OP Sepolia Etherscan](https://testnet-explorer.optimism.io/address/0x5DE8a2957eddb140567fF90ba5d57bc9769f3055#readContract) and click the "Read Contract" button.
Paste your address into the field next to the "greeting" function and click the "Query" button.
You should see the message you sent from L1.
Haven't seen your message yet?
You might need to wait a little longer.
L2 transactions triggered on L1 are typically processed within one minute but can occasionally be slightly delayed.
## Interact with the L2 Greeter
Now you're going to use the L2 `Greeter` contract to set the greeting on the L1 `Greeter` contract.
You'll send a transaction directly to the L2 `Greeter` contract which will ask the `L2CrossDomainMessenger` to send a message to the L1 `Greeter` contract.
Unlike the previous step, you'll need to relay the message from L2 to L1 yourself.
You'll do this by sending two transactions on Sepolia, one proving transaction and one relaying transaction.
Just like before, sending a message to the L1 `Greeter` contract via the L2 `Greeter` contract requires that you call the `sendGreeting` function.
Open up the [L2 `Greeter` contract on OP Sepolia Etherscan](https://testnet-explorer.optimism.io/address/0x5DE8a2957eddb140567fF90ba5d57bc9769f3055#writeContract) and click the "Connect to Web3" button.
Put a greeting into the field next to the "sendGreeting" function and click the "Write" button.
You can use any greeting you'd like.
Copy the transaction hash from the transaction you just sent.
You'll need this for the next few steps.
Feel free to keep this tab open so you can easily copy the transaction hash later.
You're going to use viem to prove and relay your message to L1.
```bash theme={null}
mkdir cross-dom
cd cross-dom
pnpm init
pnpm add viem@^2.51.0
```
Set your private key and transaction hash as environment variables.
```bash theme={null}
export TUTORIAL_PRIVATE_KEY=0x...
export TUTORIAL_TRANSACTION_HASH=0x...
```
Start a Node.js REPL with `node` and paste the following script to monitor, prove, and finalize the cross-domain message:
```js theme={null}
const { createPublicClient, http, createWalletClient } = require('viem');
const { optimismSepolia, sepolia } = require('viem/chains');
const { publicActionsL1, publicActionsL2, walletActionsL1, walletActionsL2, getWithdrawals, isSuperGameType } = require('viem/op-stack');
const { privateKeyToAccount } = require('viem/accounts');
const l1Provider = createPublicClient({
chain: sepolia,
transport: http('https://eth-sepolia.g.alchemy.com/v2/your-key')
}).extend(publicActionsL1());
const l2Provider = createPublicClient({
chain: optimismSepolia,
transport: http('https://opt-sepolia.g.alchemy.com/v2/your-key')
}).extend(publicActionsL2());
const account = privateKeyToAccount(process.env.TUTORIAL_PRIVATE_KEY);
const l1Wallet = createWalletClient({
account,
chain: sepolia,
transport: http('https://eth-sepolia.g.alchemy.com/v2/your-key')
}).extend(walletActionsL1());
const l2Wallet = createWalletClient({
account,
chain: optimismSepolia,
transport: http('https://opt-sepolia.g.alchemy.com/v2/your-key')
}).extend(walletActionsL2());
const receipt = await l2Provider.getTransactionReceipt({
hash: process.env.TUTORIAL_TRANSACTION_HASH
});
const withdrawalBlock = await l2Provider.getBlock({
blockNumber: receipt.blockNumber
});
const respectedGameType = await l1Provider.readContract({
abi: [{
inputs: [],
name: 'respectedGameType',
outputs: [{ type: 'uint32' }],
stateMutability: 'view',
type: 'function'
}],
address: l2Provider.chain.contracts.portal[l1Provider.chain.id].address,
functionName: 'respectedGameType'
});
const proveContext = isSuperGameType(Number(respectedGameType))
? { l2Timestamp: withdrawalBlock.timestamp }
: {};
console.log('Waiting for message to be provable...');
await l1Provider.getWithdrawalStatus({
...proveContext,
receipt,
targetChain: l2Provider.chain
});
console.log('Proving message...');
const { game, withdrawal } = await l1Provider.waitToProve({
...proveContext,
receipt,
targetChain: l2Provider.chain
});
const proveArgs = await l2Provider.buildProveWithdrawal({
account,
game,
withdrawal
});
await l1Wallet.proveWithdrawal(proveArgs);
console.log('Waiting for message to be relayable...');
await l1Provider.waitToFinalize({
targetChain: l2Provider.chain,
withdrawalHash: withdrawal.withdrawalHash
});
console.log('Relaying message...');
await l1Wallet.finalizeWithdrawal({
targetChain: l2Wallet.chain,
withdrawal
});
console.log('Message relayed!');
```
After finalization, open the [L1 `Greeter` contract on Sepolia Etherscan](https://sepolia.etherscan.io/address/0x31A6Dd971306bb72f2ffF771bF30b1B98dB8B2c5#readContract).
Confirm that the greeting has been updated to the message you sent from L2.
## How it works
Congratulations! You've successfully sent a message from L1 to L2 and from L2 to L1.
This section explains how the `Greeter` contracts work so you can follow the same pattern to deploy your own contracts.
Luckily, both `Greeter` contracts are exactly the same so it's easy to see how everything comes together.
### The Messenger variable
The `Greeter` contract has a `MESSENGER` variable that keeps track of the `CrossDomainMessenger` contract on the current chain.
Check out the [Contract Addresses page](/op-mainnet/network-information/op-addresses) to see the addresses of the `CrossDomainMessenger` contracts on whatever network you'll be using.
```solidity theme={null}
address public immutable MESSENGER;
```
### The other Greeter variable
The `Greeter` contract also has an `OTHER_GREETER` variable that keeps track of the `Greeter` contract on the other chain.
On L1, this variable is set to the address of the L2 `Greeter` contract, and vice-versa.
```solidity theme={null}
address public immutable OTHER_GREETER;
```
### The Greetings mapping
The `Greeter` contract keeps track of the different greetings that users have sent inside a `greetings` mapping.
By using a mapping, this contract can keep track of greetings from different users at the same time.
```solidity theme={null}
mapping(address => string) public greetings;
```
### The Constructor
The `Greeter` has a simple constructor that sets the `MESSENGER` and `OTHER_GREETER` variables.
```solidity theme={null}
constructor(address messenger, address otherGreeter) {
MESSENGER = messenger;
OTHER_GREETER = otherGreeter;
}
```
### The sendGreeting function
The `sendGreeting` function is the most important function in the `Greeter` contract.
This is what you called earlier to send messages in both directions.
All this function does is use the `sendMessage` function found within the `CrossDomainMessenger` contract to send a message to the `Greeter` contract on the other chain.
```solidity theme={null}
function sendGreeting(string calldata newGreeting) external payable {
ICrossDomainMessenger(MESSENGER).sendMessage(
OTHER_GREETER,
abi.encodeWithSelector(Greeter.setGreeting.selector, msg.sender, newGreeting),
100_000
);
}
```
### The setGreeting function
The `setGreeting` function is the function that actually sets the greeting.
This function is called by the `CrossDomainMessenger` contract on the other chain.
It checks explicitly that the function can only be called by the `CrossDomainMessenger` contract.
It also checks that the `CrossChainMessenger` says that the message came from the `Greeter` contract on the other chain.
Finally, it sets the greeting in the `greetings` mapping.
```solidity theme={null}
function setGreeting(address sender, string calldata newGreeting) external {
require(msg.sender == MESSENGER, "Only messenger");
require(
ICrossDomainMessenger(MESSENGER).xDomainMessageSender() == OTHER_GREETER,
"Only remote greeter"
);
greetings[sender] = newGreeting;
}
```
The two `require` statements in this function are important.
Without them, anyone could call this function and set the greeting to whatever they want.
You can follow a similar pattern in your own smart contracts.
## Conclusion
You just learned how you can write Solidity contracts on Sepolia and OP Sepolia that can talk to each other.
You can follow the same pattern to write contracts that can talk to each other on Ethereum and OP Stack.
```solidity theme={null}
interface ICrossDomainMessenger {
function sendMessage(address target, bytes calldata message, uint32 gasLimit) external;
function xDomainMessageSender() external view returns (address);
}
```
This sort of cross-chain communication is useful for a variety of reasons.
For example, the [Standard Bridge](/app-developers/guides/bridging/standard-bridge) contracts use this same system to bridge ETH and ERC-20 tokens between Ethereum and OP Stack.
One cool way to take advantage of cross-chain communication is to do most of your heavy lifting on OP Stack and then send a message to Ethereum only when you have important results to share.
This way you can take advantage of the low gas costs on OP Stack while still being able to use Ethereum when you need it.
# Deposit transactions
Source: https://docs.optimism.io/app-developers/tutorials/bridging/deposit-transactions
Learn about using deposit transactions with `supersim`.
Supersim supports [deposit transactions](/op-stack/bridging/deposit-flow). It uses a very lightweight solution without the `op-node` derivation pipeline by listening directly to the `TransactionDeposited` events on the `OptimismPortal` contract and simply forwarding the transaction to the applicable L2.
The execution engine used with Supersim must support the Optimism [deposit transaction type](https://specs.optimism.io/protocol/deposits.html#the-deposited-transaction-type).
## `OptimismPortal`
When starting Supersim, the L1 contracts for each L2 chain are emitted as output to the console. The `L1CrossDomainMessenger`, `L1StandardBridge`, and `OptimismPortal` can be used to initiate deposits in the same manner as one would on a production network like OP Mainnet or Base.
```bash theme={null}
Chain Configuration
-----------------------
L1: Name: Local ChainID: 900 RPC: http://127.0.0.1:8545 LogPath: ...
L2: Predeploy Contracts Spec ( https://specs.optimism.io/protocol/predeploys.html )
* Name: OPChainA ChainID: 901 RPC: http://127.0.0.1:9545 LogPath: ...
L1 Contracts:
- OptimismPortal: 0x37a418800d0c812A9dE83Bc80e993A6b76511B57
- L1CrossDomainMessenger: 0xcd712b03bc6424BF45cE6C29Fc90FFDece228F6E
- L1StandardBridge: 0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0
...
```
If running Supersim in fork mode, the production contracts will be used for each of the forked networks.
```bash theme={null}
Chain Configuration
-----------------------
L1: Name: mainnet ChainID: 1 RPC: http://127.0.0.1:8545 LogPath: ...
L2: Predeploy Contracts Spec ( https://specs.optimism.io/protocol/predeploys.html )
* Name: op ChainID: 10 RPC: http://127.0.0.1:9545 LogPath: ...
L1 Contracts:
- OptimismPortal: 0xbEb5Fc579115071764c7423A4f12eDde41f106Ed
- L1CrossDomainMessenger: 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1
- L1StandardBridge: 0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1
* Name: mode ChainID: 34443 RPC: http://127.0.0.1:9546 LogPath: ...
L1 Contracts:
- OptimismPortal: 0x8B34b14c7c7123459Cf3076b8Cb929BE097d0C07
- L1CrossDomainMessenger: 0x95bDCA6c8EdEB69C98Bd5bd17660BaCef1298A6f
- L1StandardBridge: 0x735aDBbE72226BD52e818E7181953f42E3b0FF21
...
```
## Sample Deposit Flow
We'll run through a sample deposit directly with the `OptimismPortal` using cast.
```bash theme={null}
supersim
```
```bash theme={null}
...
* Name: OPChainA ChainID: 901 ...
L1 Contracts:
- OptimismPortal: 0x37a418800d0c812A9dE83Bc80e993A6b76511B57
...
```
We'll be using the first pre-funded account to send this deposit of 1 ether
```bash theme={null}
cast send 0x37a418800d0c812A9dE83Bc80e993A6b76511B57 --value 1ether --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
```bash theme={null}
INFO [11-28|13:56:06.756] OptimismPortal#depositTransaction chain.id=901 l2TxHash=0x592d6e13016751332115df1fce59904176bfe447854196ed1b97ee00f14be469
```
## Next steps
* See the [transaction guides](/app-developers/guides/transactions/estimates) for more detailed information.
* Questions about Interop? Check out collection of [interop guides](/op-stack/interop/explainer) or check out this [OP Stack interop design video walk-thru](https://www.youtube.com/watch?v=FKc5RgjtGes).
* For more info about how OP Stack interoperability works under the hood, [check out the specs](https://specs.optimism.io/interop/overview.html?utm_source=op-docs\&utm_medium=docs).
# Replaying a failed deposit
Source: https://docs.optimism.io/app-developers/tutorials/bridging/replay-failed-deposit
Learn how deposit replays work by deliberately failing a deposit against a test contract on OP Sepolia and then replaying it with more gas, end to end.
When a deposit transaction fails on L2 — usually because it ran out of gas or the L2 state didn't allow it to succeed — the message isn't lost. `L2CrossDomainMessenger` records it as a failed message, and you can replay it later, optionally with more gas.
In this tutorial, you'll make a deposit fail on purpose against a test contract, then replay it successfully, so you can see the full failure-and-replay cycle end to end. For the concepts behind deposits and why replays are possible, see [Deposit flow](/op-stack/bridging/deposit-flow).
## Before you begin
* [Foundry](https://book.getfoundry.sh/getting-started/installation) installed (this tutorial uses `cast`).
* A test account private key, funded with a small amount of test ETH on both **Ethereum Sepolia** (L1) and **OP Sepolia** (L2).
* An L1 (Ethereum Sepolia) RPC URL — e.g. a free [Infura](https://infura.io) key or another provider.
**L1 vs L2 network clarification**
This tutorial involves **two different networks**:
* **L1**: Ethereum Sepolia testnet (`https://sepolia.infura.io/v3/YOUR_KEY`)
* **L2**: OP Sepolia testnet (`https://sepolia.optimism.io`)
You'll send transactions on L1 that trigger actions on L2. Make sure you're using the correct RPC URLs for each step.
## Trigger and replay a failed deposit
To see how replays work, you can use [this contract on OP Sepolia](https://testnet-explorer.optimism.io/address/0xEF60cF6C6D0C1c755be104843bb72CDa3D778630#code).
1. Call `stopChanges`, using this Foundry command:
```sh theme={null}
PRIV_KEY=
export ETH_RPC_URL=https://sepolia.optimism.io
GREETER=0xEF60cF6C6D0C1c755be104843bb72CDa3D778630
cast send --private-key $PRIV_KEY $GREETER "stopChanges()"
```
2. Verify that `getStatus()` returns false, meaning changes are not allowed, and see the value of `greet()` using Foundry.
Note that Foundry returns false as zero.
```sh theme={null}
cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()"
```
3. Get the calldata.
You can use this Foundry command:
```sh theme={null}
cast calldata "setGreeting(string)" "testing"
```
Or just use this value:
```
0xa41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000774657374696e6700000000000000000000000000000000000000000000000000
```
4. Send a greeting change as a deposit from L1 (Ethereum Sepolia) to L2 (OP Sepolia).
Use these commands:
```sh theme={null}
# L1 = Ethereum Sepolia
# Get a free Infura key at https://infura.io or use the public RPC below
L1_RPC=https://sepolia.infura.io/v3/YOUR_INFURA_KEY
L1XDM_ADDRESS=0x5086d1eef304eb5284a0f6720f79403b4e9be294
FUNC="sendMessage(address,bytes,uint32)"
CALLDATA=`cast calldata "setGreeting(string)" "testing"`
cast send --rpc-url $L1_RPC --private-key $PRIV_KEY $L1XDM_ADDRESS $FUNC $GREETER $CALLDATA 10000000
```
The transaction will be successful on **L1 (Ethereum Sepolia)**, but then emit a fail event on **L2 (OP Sepolia)**.
5. The next step is to find the hash of the failed relay. There are several ways to do this:
**Method A: Using Etherscan Internal Transactions**
Look in [the internal transactions of the destination contract](https://testnet-explorer.optimism.io/address/0xEF60cF6C6D0C1c755be104843bb72CDa3D778630#internaltx), and select the latest one that appears as a failure. It should be a call to `L2CrossDomainMessenger` at address `0x420...007`.
**Method B: Using Contract Events (if internal transactions aren't visible)**
If you can't see internal transactions on Etherscan, check the [L2CrossDomainMessenger contract events](https://testnet-explorer.optimism.io/address/0x4200000000000000000000000000000000000007#events) and look for `FailedRelayedMessage` events with your contract address.
**Method C: Using cast to query failed messages**
```sh theme={null}
# First, you need the message hash. You can derive it from the L1 transaction, or check events
L2XDM_ADDRESS=0x4200000000000000000000000000000000000007
# Replace MSG_HASH with the actual message hash from the FailedRelayedMessage event
cast call $L2XDM_ADDRESS "failedMessages(bytes32)" $MSG_HASH
```
If the latest internal transaction is a success, it probably means your transaction hasn't relayed yet. Wait until it is, that may take a few minutes.
6. Get the transaction information using Foundry.
**Wait for the failed relay transaction**
Make sure you wait for the deposit to be processed on L2 and fail before proceeding. This can take 2-5 minutes. You should see a failed transaction in one of the methods from step 5.
```sh theme={null}
TX_HASH=
L2XDM_ADDRESS=0x4200000000000000000000000000000000000007
REPLAY_DATA=`cast tx $TX_HASH input`
```
7. Call `startChanges()` to allow changes using this Foundry command:
```sh theme={null}
cast send --private-key $PRIV_KEY $GREETER "startChanges()"
```
Don't do this prematurely
If you call `startChanges()` too early, it will happen when the message is relayed to L2, and then the initial deposit will be successful and there will be no need to replay it.
8. Verify that `getStatus()` returns true, meaning changes are not allowed, and see the value of `greet()`.
Foundry returns true as one.
```sh theme={null}
cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()"
```
9. Now send the replay transaction.
```sh theme={null}
cast send --private-key $PRIV_KEY --gas-limit 10000000 $L2XDM_ADDRESS $REPLAY_DATA
```
Why do we need to specify the gas limit?
The gas estimation mechanism tries to find the minimum gas limit at which the transaction would be successful.
However, `L2CrossDomainMessenger` does not revert when a replay fails due to low gas limit, it just emits a failure message.
The gas estimation mechanism considers that a success.
To get a gas estimate, you can use this command:
```sh theme={null}
cast estimate --from 0x0000000000000000000000000000000000000001 $L2XDM_ADDRESS $REPLAY_DATA
```
That address is a special case in which the contract does revert.
10. Verify the greeting has changed:
```sh theme={null}
cast call $GREETER "greet()" | cast --to-ascii ; cast call $GREETER "getStatus()"
```
## Debugging
To debug deposit transactions, you can ask the L2 cross domain messenger for the state of the transaction.
1. Look on Etherscan to see the `FailedRelayedMessage` event. Set `MSG_HASH` to that value.
2. To check if the message is listed as failed, run this:
```sh theme={null}
cast call $L2XDM_ADDRESS "failedMessages(bytes32)" $MSG_HASH
```
To check if it is listed as successful, run this:
```sh theme={null}
cast call $L2XDM_ADDRESS "successfulMessages(bytes32)" $MSG_HASH
```
## Next steps
* Read [Deposit flow](/op-stack/bridging/deposit-flow) to understand how deposits are processed across L1 and L2 under the hood.
* Learn about [sending data between L1 and L2](/app-developers/guides/bridging/messaging) from your contracts.
# Create an L2 token for the Standard Bridge
Source: https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token
Learn how to create a standard or custom L2 representation of an L1 ERC-20 token that works with the Standard Bridge on an OP Stack chain.
In this tutorial, you'll create an L2 representation of an L1 ERC-20 token that works with the Standard Bridge system on an OP Stack chain.
This tutorial is meant for developers who already have an existing ERC-20 token on Ethereum and want to create a bridged representation of that token on layer 2.
By the end, you'll have an L2 token contract deployed on OP Sepolia that the Standard Bridge can mint and burn.
This tutorial does not cover the bridging itself: once your L2 token exists, follow [Bridging ERC-20 tokens with viem](./cross-dom-bridge-erc20) to move tokens between L1 and L2.
The Standard Bridge supports two kinds of L2 tokens, and this tutorial covers both:
* A **standard token** deployed through the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol).
Tokens created by this factory contract are compatible with the Standard Bridge system and include basic logic for deposits, transfers, and withdrawals.
Choose this path if you don't need any specialized logic in your L2 token.
* A **custom token** that implements the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20.sol) interface itself.
A custom token allows you to do things like trigger extra logic whenever a token is deposited.
Choose this path if you need specialized behavior like that.
Both paths share the same setup and fork at the [Create an L2 ERC-20 token](#create-an-l2-erc-20-token) section, where each path has its own tab.
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 they can cause bridge accounting errors.
## About OptimismMintableERC20s
The Standard Bridge system requires that L2 representations of L1 tokens implement the [`IOptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/universal/IOptimismMintableERC20.sol) interface.
This interface is a superset of the standard ERC-20 interface and includes functions that allow the bridge to properly verify deposits/withdrawals and mint/burn tokens as needed.
Your L2 token contract must implement this interface in order to be bridged using the Standard Bridge system.
This tutorial will show you how to deploy a basic standardized ERC-20 token through the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol), or how to write a custom token that implements the interface itself.
## Dependencies
* [cast](https://book.getfoundry.sh/getting-started/installation), used on the standard-token path to deploy through the factory
* A web browser with a wallet extension, used on the custom-token path to deploy with [Remix](https://remix.ethereum.org)
## Get ETH on Sepolia and OP Sepolia
This tutorial explains how to create a bridged ERC-20 token on OP Sepolia.
You will need to get some ETH on both of these testnets.
## Get an L1 ERC-20 token address
You will need an L1 ERC-20 token for this tutorial.
If you already have an L1 ERC-20 token deployed on Sepolia, you can skip this step.
Otherwise, you can use the testing token located at [`0x5589BB8228C07c4e15558875fAf2B859f678d129`](https://sepolia.etherscan.io/address/0x5589BB8228C07c4e15558875fAf2B859f678d129) that includes a `faucet()` function that can be used to mint tokens.
## Create an L2 ERC-20 token
Once you have an L1 ERC-20 token, you can create a corresponding L2 ERC-20 token on OP Sepolia.
Pick the tab that matches the kind of token you want to create.
You can deploy your L2 ERC-20 token using the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol).
All tokens created by the factory implement the `IOptimismMintableERC20` interface and are compatible with the Standard Bridge system.
You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
Make sure this private key corresponds to an address that has ETH on .
Want to create a new wallet for this tutorial?
If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
```bash theme={null}
export TUTORIAL_PRIVATE_KEY=0x...
```
You'll need an RPC URL in order to connect to OP Sepolia.
Set your RPC URL as an environment variable with the `export` command.
```bash theme={null}
export TUTORIAL_RPC_URL=https://sepolia.optimism.io
```
You'll need to know the address of your L1 ERC-20 token in order to create a bridged representation of it on OP Sepolia.
Set your L1 ERC-20 token address as an environment variable with the `export` command.
```bash theme={null}
# Replace this with your L1 ERC-20 token if not using the testing token!
export TUTORIAL_L1_ERC20_ADDRESS=0x5589BB8228C07c4e15558875fAf2B859f678d129
```
You can now deploy your L2 ERC-20 token using the [`OptimismMintableERC20Factory`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20Factory.sol).
Use the `cast` command to trigger the deployment function on the factory contract.
This example command creates a token with the name "My Standard Demo Token" and the symbol "L2TKN".
The resulting L2 ERC-20 token address is printed to the console.
```bash theme={null}
cast send 0x4200000000000000000000000000000000000012 "createOptimismMintableERC20(address,string,string)" $TUTORIAL_L1_ERC20_ADDRESS "My Standard Demo Token" "L2TKN" --private-key $TUTORIAL_PRIVATE_KEY --rpc-url $TUTORIAL_RPC_URL --json | jq -r '.logs[0].topics[2]' | cast parse-bytes32-address
```
This path uses [Remix](https://remix.ethereum.org) so you can easily deploy a token without a framework like [Hardhat](https://hardhat.org) or [Foundry](https://getfoundry.sh).
You can follow the same general process within your favorite framework if you prefer.
In this section, you'll be creating an ERC-20 token that can be deposited but cannot be withdrawn.
This is just one example of the endless ways in which you could customize your L2 token.
You will need to add the OP Sepolia network to your wallet in order to follow this path.
You can use [this website](https://chainid.link?network=op-sepolia) to connect your wallet to OP Sepolia.
Navigate to [Remix](https://remix.ethereum.org) in your browser.
Click the 📄 ("Create new file") button to create a new empty Solidity file.
You can name this file whatever you'd like, for example `MyCustomL2Token.sol`.
Copy the following example contract into your new file:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
// Import the standard ERC20 implementation from OpenZeppelin
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
/**
* @title ILegacyMintableERC20
* @notice Legacy interface for the StandardL2ERC20 contract.
*/
interface ILegacyMintableERC20 {
function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external;
function l1Token() external view returns (address);
function l2Bridge() external view returns (address);
}
/**
* @title IOptimismMintableERC20
* @notice Interface for the OptimismMintableERC20 contract.
*/
interface IOptimismMintableERC20 {
function remoteToken() external view returns (address);
function bridge() external view returns (address);
function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external;
}
/**
* @title Simplified Semver for tutorial
* @notice Simple contract to track semantic versioning
*/
contract Semver {
string public version;
// Simple function to convert uint to string for version numbers
function toString(uint256 value) internal pure returns (string memory) {
// This function handles numbers from 0 to 999 which is sufficient for versioning
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
constructor(uint256 major, uint256 minor, uint256 patch) {
version = string(abi.encodePacked(
toString(major),
".",
toString(minor),
".",
toString(patch)
));
}
}
/**
* @title MyCustomL2Token
* @notice A custom L2 token based on OptimismMintableERC20 that can be deposited
* from L1 to L2, but cannot be withdrawn from L2 to L1.
*/
contract MyCustomL2Token is IOptimismMintableERC20, ILegacyMintableERC20, ERC20, Semver {
/// @notice Address of the corresponding token on the remote chain.
address public immutable REMOTE_TOKEN;
/// @notice Address of the StandardBridge on this network.
address public immutable BRIDGE;
/// @notice Emitted whenever tokens are minted for an account.
/// @param account Address of the account tokens are being minted for.
/// @param amount Amount of tokens minted.
event Mint(address indexed account, uint256 amount);
/// @notice Emitted whenever tokens are burned from an account.
/// @param account Address of the account tokens are being burned from.
/// @param amount Amount of tokens burned.
event Burn(address indexed account, uint256 amount);
/// @notice A modifier that only allows the bridge to call
modifier onlyBridge() {
require(msg.sender == BRIDGE, "MyCustomL2Token: only bridge can mint and burn");
_;
}
/// @param _bridge Address of the L2 standard bridge.
/// @param _remoteToken Address of the corresponding L1 token.
/// @param _name ERC20 name.
/// @param _symbol ERC20 symbol.
constructor(
address _bridge,
address _remoteToken,
string memory _name,
string memory _symbol
)
ERC20(_name, _symbol)
Semver(1, 0, 0)
{
REMOTE_TOKEN = _remoteToken;
BRIDGE = _bridge;
}
/// @notice Allows the StandardBridge on this network to mint tokens.
/// @param _to Address to mint tokens to.
/// @param _amount Amount of tokens to mint.
function mint(
address _to,
uint256 _amount
)
external
virtual
override(IOptimismMintableERC20, ILegacyMintableERC20)
onlyBridge
{
_mint(_to, _amount);
emit Mint(_to, _amount);
}
/// @notice Burns tokens from an account.
/// @dev This function always reverts to prevent withdrawals to L1.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function burn(
address _from,
uint256 _amount
)
external
virtual
override(IOptimismMintableERC20, ILegacyMintableERC20)
onlyBridge
{
// Instead of calling _burn(_from, _amount), we revert
// This makes it impossible to withdraw tokens back to L1
revert("MyCustomL2Token: withdrawals are not allowed");
// Note: The following line would normally execute but is unreachable
// _burn(_from, _amount);
// emit Burn(_from, _amount);
}
/// @notice ERC165 interface check function.
/// @param _interfaceId Interface ID to check.
/// @return Whether or not the interface is supported by this contract.
function supportsInterface(bytes4 _interfaceId) external pure virtual returns (bool) {
bytes4 iface1 = type(IERC165).interfaceId;
// Interface corresponding to the legacy L2StandardERC20
bytes4 iface2 = type(ILegacyMintableERC20).interfaceId;
// Interface corresponding to the updated OptimismMintableERC20
bytes4 iface3 = type(IOptimismMintableERC20).interfaceId;
return _interfaceId == iface1 || _interfaceId == iface2 || _interfaceId == iface3;
}
/// @notice Legacy getter for the remote token. Use REMOTE_TOKEN going forward.
function l1Token() public view override returns (address) {
return REMOTE_TOKEN;
}
/// @notice Legacy getter for the bridge. Use BRIDGE going forward.
function l2Bridge() public view override returns (address) {
return BRIDGE;
}
/// @notice Getter for REMOTE_TOKEN.
function remoteToken() public view override returns (address) {
return REMOTE_TOKEN;
}
/// @notice Getter for BRIDGE.
function bridge() public view override returns (address) {
return BRIDGE;
}
}
```
Take a moment to review the example contract. It's closely based on the official [`OptimismMintableERC20`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol) contract with one key modification:
The `burn` function has been modified to always revert, making it impossible to withdraw tokens back to L1.
Since the bridge needs to burn tokens when users want to withdraw them to L1, this means that users will not be able to withdraw tokens from this contract. Here's the key part of the contract that prevents withdrawals:
```solidity theme={null}
/// @notice Burns tokens from an account.
/// @dev This function always reverts to prevent withdrawals to L1.
/// @param _from Address to burn tokens from.
/// @param _amount Amount of tokens to burn.
function burn(
address _from,
uint256 _amount
)
external
virtual
override(IOptimismMintableERC20, ILegacyMintableERC20)
onlyBridge
{
// Instead of calling _burn(_from, _amount), we revert
// This makes it impossible to withdraw tokens back to L1
revert("MyCustomL2Token: withdrawals are not allowed");
// Note: The following line would normally execute but is unreachable
// _burn(_from, _amount);
// emit Burn(_from, _amount);
}
```
Save the file to automatically compile the contract.
If you've disabled auto-compile, you'll need to manually compile the contract by clicking the "Solidity Compiler" tab (this looks like the letter "S") and press the blue "Compile" button.
Make sure you're using Solidity compiler version 0.8.20, the exact version the example contract's `pragma solidity 0.8.20;` requires.
Open the deployment tab (this looks like an Ethereum logo with an arrow pointing left).
Make sure that your environment is set to "Injected Provider", your wallet is connected to OP Sepolia, and Remix has access to your wallet.
Then, select the `MyCustomL2Token` contract from the deployment dropdown and deploy it with the following parameters:
```text theme={null}
_bridge: "0x4200000000000000000000000000000000000010" // L2 Standard Bridge address
_remoteToken: "" // Your L1 token address
_name: "My Custom L2 Token" // Your token name
_symbol: "MCL2T" // Your token symbol
```
Note: The L2 Standard Bridge address is a predefined address on all OP Stack chains, so it will be the same on OP Sepolia and OP Mainnet.
## Bridge some tokens
Now that you have an L2 ERC-20 token, you can bridge some tokens from L1 to L2.
Check out the tutorial on [Bridging ERC-20 tokens with viem](./cross-dom-bridge-erc20) to learn how to bridge your L1 ERC-20 to L2s using viem.
If you deployed the custom example token from this tutorial, remember that the withdrawal step will *not* work for it: the example contract's `burn` function always reverts, which is exactly what that example was meant to demonstrate.
## Add to the Superchain Token List
The [Superchain Token List](https://github.com/ethereum-optimism/ethereum-optimism.github.io#readme) is a common list of tokens deployed on chains within the Optimism Superchain.
This list is used by services like the [Superchain Bridges UI](https://app.optimism.io/bridge?utm_source=op-docs\&utm_medium=docs).
If you want your OP Mainnet token to be included in this list, take a look at the [review process and merge criteria](https://github.com/ethereum-optimism/ethereum-optimism.github.io#review-process-and-merge-criteria).
# Deploy a contract to OP Sepolia
Source: https://docs.optimism.io/app-developers/tutorials/deploy-a-contract
Deploy your first smart contract to an OP Stack chain and interact with it using Foundry.
This tutorial walks you through deploying your first smart contract to an OP Stack chain from scratch.
You'll deploy a small `Greeter` contract to the OP Sepolia testnet with [Foundry](https://getfoundry.sh/), then read from and write to it from the command line.
OP Stack chains are [EVM equivalent](/op-stack/protocol/differences), so the workflow here is the same one you'd use on Ethereum: the only OP-specific detail is the RPC endpoint and chain ID you point at.
By the end you'll have a live contract on OP Sepolia and the commands to interact with any contract you deploy later.
This tutorial uses the OP Sepolia testnet, so you won't spend real funds.
The same steps work on any OP Stack chain — swap in that chain's RPC URL and fund your account on that network.
## Dependencies
* [Foundry](https://book.getfoundry.sh/getting-started/installation) — installed in the first step below.
* A terminal with `curl` available (preinstalled on macOS and most Linux distributions).
## Install Foundry
Foundry is a toolkit for Ethereum development.
This tutorial uses two of its command-line tools: `forge` (to compile and deploy) and `cast` (to send transactions and read state).
```bash theme={null}
curl -L https://foundry.paradigm.xyz | bash
```
This installs `foundryup`, Foundry's version manager.
Follow the on-screen instructions to add it to your `PATH` (you may need to open a new terminal).
```bash theme={null}
foundryup
```
### Verify the install
Confirm `forge` and `cast` are available:
```bash theme={null}
forge --version
cast --version
```
Each command should print a version string.
If the command isn't found, revisit the `PATH` instructions from `foundryup` and open a new terminal.
## Create a project and contract
```bash theme={null}
mkdir first-contract
cd first-contract
forge init
```
`forge init` scaffolds a new project with `src/`, `test/`, and `script/` directories.
Replace the contents of `src/Greeter.sol` with the following.
This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol): it stores a greeting string, exposes it through `greet()`, and lets anyone update it through `setGreeting()`.
```solidity theme={null}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Greeter {
string greeting;
event SetGreeting(
address indexed sender, // msg.sender
string greeting
);
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
emit SetGreeting(msg.sender, _greeting);
}
}
```
```bash theme={null}
forge build
```
### Verify the build
`forge build` should report a successful compilation and write artifacts to the `out/` directory.
If compilation fails, check that `src/Greeter.sol` matches the code above exactly.
## Configure OP Sepolia and your account
You need two things to deploy: an RPC endpoint for OP Sepolia and a private key to sign the deployment transaction.
Create a fresh key for this tutorial rather than reusing a key that holds real funds.
```bash theme={null}
cast wallet new
```
This prints an `Address` and a `Private key`.
Save both somewhere safe.
Export the OP Sepolia RPC URL and the private key you just created.
These variables are read by the `forge` and `cast` commands in the rest of the tutorial.
```bash theme={null}
export L2_RPC_URL=https://sepolia.optimism.io
export PRIVATE_KEY=0x...your-private-key...
export ACCOUNT_ADDRESS=$(cast wallet address --private-key $PRIVATE_KEY)
```
`https://sepolia.optimism.io` is a public, rate-limited endpoint suited to development and testing.
For a full list of endpoints and production providers, see the [OP Stack RPC directory](/app-developers/reference/rpc-providers).
For OP Sepolia's chain ID (`11155420`) and other network parameters, see [Connecting to OP Mainnet](/op-mainnet/network-information/connecting-to-op#op-sepolia).
## Fund your account
Deploying a contract costs gas, so your account needs testnet ETH on OP Sepolia.
Use the [Superchain Faucet](https://console.optimism.io/faucet?utm_source=op-docs\&utm_medium=docs) to send OP Sepolia ETH to your `ACCOUNT_ADDRESS`.
### Verify your balance
Check that the faucet funds have arrived before deploying:
```bash theme={null}
cast balance --ether $ACCOUNT_ADDRESS --rpc-url $L2_RPC_URL
```
The command prints your balance in ETH.
Wait until it's greater than `0` before continuing.
## Deploy the contract
Deploy `Greeter` to OP Sepolia and capture the resulting contract address.
```bash theme={null}
CONTRACT_ADDRESS=$(forge create \
--rpc-url $L2_RPC_URL \
--private-key $PRIVATE_KEY \
Greeter \
--broadcast \
| awk '/Deployed to:/ {print $3}')
echo "Deployed to: $CONTRACT_ADDRESS"
```
The `forge create` command compiles (if needed), signs, and broadcasts the deployment transaction.
Its output includes a `Deployed to:` line; the `awk` command extracts that address into the `CONTRACT_ADDRESS` variable so you can reuse it in the next step.
Run `forge create` on its own (without the `awk` pipe) if you want to see the full output — the deployer address, the new contract address, and the transaction hash.
### Verify the deployment
Confirm the contract exists on-chain by fetching its bytecode:
```bash theme={null}
cast code $CONTRACT_ADDRESS --rpc-url $L2_RPC_URL
```
A deployed contract returns a long hex string.
If it returns `0x`, the deployment didn't land — re-check your balance and rerun the deploy step.
## Interact with the contract
Now read from and write to your live contract using `cast`.
```bash theme={null}
cast call --rpc-url $L2_RPC_URL $CONTRACT_ADDRESS "greet()" | cast --to-ascii
```
The greeting starts empty, so this returns an empty string.
This sends a transaction that calls `setGreeting()`:
```bash theme={null}
cast send \
--private-key $PRIVATE_KEY \
--rpc-url $L2_RPC_URL \
$CONTRACT_ADDRESS \
"setGreeting(string)" "Hello from OP Sepolia"
```
```bash theme={null}
cast call --rpc-url $L2_RPC_URL $CONTRACT_ADDRESS "greet()" | cast --to-ascii
```
This now returns `Hello from OP Sepolia`, confirming your write landed on-chain.
## View your contract on the block explorer
Open an [OP Sepolia block explorer](/app-developers/tools-sdks/block-explorers) and search for your `CONTRACT_ADDRESS` to see the deployment transaction and the `setGreeting` call you just sent.
Publishing (verifying) your contract's source code on the explorer is optional but recommended, because it lets anyone read and interact with the contract from the explorer UI.
## Next steps
* Learn the broader conventions in [Building apps on OP Stack chains](/app-developers/guides/building-apps).
* Understand [the differences between Ethereum and OP Stack chains](/op-stack/protocol/differences).
* Try a cross-chain tutorial next, such as [bridging ERC-20 tokens](/app-developers/tutorials/bridging/cross-dom-bridge-erc20).
**Running your app in production**
A production application depends on infrastructure your team does not run: RPC endpoints that hold up under real traffic (the public endpoints are rate-limited and not built for production), bridges your users rely on, and a chain whose operator keeps sequencing, upgrades, and incident response going around the clock. These docs cover building and testing. If your application is growing toward dedicated blockspace of its own, [OP Enterprise](https://optimism.io/op-enterprise?utm_source=docs\&utm_medium=docs\&utm_campaign=op-enterprise) offers managed and supported paths to running a chain. These docs stay the reference for what you build either way. OP Enterprise is Optimism's managed offering.
# First steps
Source: https://docs.optimism.io/app-developers/tutorials/development/supersim/first-steps
Take your first steps with Supersim.
`supersim` allows testing multichain features **locally**. Previously, testing multichain features required complex docker setups or using a testnet. To see it in practice, this tutorial walks you through sending some ETH from the L1 to the L2.
## Deposit ETH from the L1 into the L2 (L1 to L2 message passing)
Grab the balance of the sender account on L2:
```sh theme={null}
cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9545
```
You can use two different methods to complete this action: `OptimismPortal` or `L1StandardBridge`.
#### First method: `OptimismPortal`
* Send the Ether to `OptimismPortal` contract of the respective L2 (on chain 900)
For chain 901, the contract is `0x37a418800d0c812A9dE83Bc80e993A6b76511B57`.
* Initiate a bridge transaction on the L1:
```sh theme={null}
cast send 0x37a418800d0c812A9dE83Bc80e993A6b76511B57 --value 0.1ether --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
#### Second method: `L1StandardBridge`
* Call `bridgeETH` function on the `L1StandardBridgeProxy` / `L1StandardBridge` contract of the respective L2 on L1 (chain 900)
For chain 901, the contract is `0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0`.
* Initiate a bridge transaction on the L1:
```sh theme={null}
cast send 0x8d515eb0e5F293B16B6bBCA8275c060bAe0056B0 "bridgeETH(uint32 _minGasLimit, bytes calldata _extraData)" 50000 0x --value 0.1ether --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
Verify that the ETH balance of the sender has increased on the L2:
```sh theme={null}
cast balance 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9545
```
## Send an interoperable ERC20 token from chain 901 to 902 (L2 to L2 message passing)
In a typical L2 to L2 cross-chain transfer, two transactions are required:
1. Send transaction on the source chain – This initiates the token transfer on Chain 901.
2. Relay message transaction on the destination chain – This relays the transfer details to Chain 902.
To simplify this process, you can use the `--interop.autorelay` flag. This flag automatically triggers the relay message transaction once the initial send transaction is completed on the source chain, improving the developer experience by removing the need to manually send the relay message.
```sh theme={null}
supersim --interop.autorelay
```
Run the following command to mint 1000 test ERC20 tokens to the recipient address:
```sh theme={null}
cast send 0x420beeF000000000000000000000000000000001 "mint(address _to, uint256 _amount)" 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
Send the tokens from Chain 901 to Chain 902 using the following command:
```sh theme={null}
cast send 0x4200000000000000000000000000000000000028 "sendERC20(address _token, address _to, uint256 _amount, uint256 _chainId)" 0x420beeF000000000000000000000000000000001 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1000 902 --rpc-url http://127.0.0.1:9545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
In a few seconds, you should see the RelayedMessage on chain 902:
```sh theme={null}
# example
INFO [08-30|14:30:14.698] SuperchainTokenBridge#RelayERC20 token=0x420beeF000000000000000000000000000000001 from=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 to=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 amount=1000 source=901
```
Verify that the balance of the test ERC20 on chain 902 has increased:
```sh theme={null}
cast balance --erc20 0x420beeF000000000000000000000000000000001 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --rpc-url http://127.0.0.1:9546
```
With the steps above, you've now successfully completed both an L1 to L2 ETH bridge and an L2 to L2 interoperable ERC20 token transfer, all done locally using `supersim`. This approach simplifies multichain testing, allowing you to focus on development without the need for complex setups or relying on external testnets.
## Next steps
* Learn how to start Supersim in [vanilla (non-forked) mode](/app-developers/reference/tools/supersim/vanilla) or [forked mode](/app-developers/reference/tools/supersim/fork).
* Explore the Supersim [included contracts](/app-developers/reference/tools/supersim/included-contracts) being used to help replicate the OP Stack environment.
# Install Supersim
Source: https://docs.optimism.io/app-developers/tutorials/development/supersim/installation
Learn how to install Supersim and start it in vanilla mode.
This page provides installation instructions for `supersim`.
`supersim` requires `anvil`, which is installed alongside the Foundry toolchain.
Follow the [Foundry toolchain](https://book.getfoundry.sh/getting-started/installation) guide for detailed instructions.
Either download precompiled binaries or install using Homebrew:
* Precompiled binaries: Download the executable for your platform from the [GitHub releases page](https://github.com/ethereum-optimism/supersim/releases).
* Homebrew: Install [Homebrew](https://brew.sh/) (OS X, Linux), and then run:
```sh theme={null}
brew tap ethereum-optimism/tap
brew install supersim
```
Start `supersim` in vanilla mode by running:
```sh theme={null}
supersim
```
Vanilla mode will start 3 chains, with the OP Stack contracts already deployed.
* (1) L1 Chain
* Chain 900
* (2) L2 Chains
* Chain 901
* Chain 902
## Next steps
* Continue to the [First Steps](/app-developers/tutorials/development/supersim/first-steps) tutorial to try L1 to L2 message passing.
* Explore [Supersim](/app-developers/tools-sdks/supersim) features, particularly in [vanilla mode](/app-developers/reference/tools/supersim/vanilla), which starts 3 chains (L1 and L2).
# Making crosschain contract calls (ping pong)
Source: https://docs.optimism.io/app-developers/tutorials/interoperability/contract-calls
Understand how the CrossChainPingPong example contract uses the L2ToL2CrossDomainMessenger to make crosschain contract calls, and the design choices behind it.
OP Stack interop is in active development. Some features may be experimental.
This page explains the design of the `CrossChainPingPong.sol` example contract and how it integrates the `L2ToL2CrossDomainMessenger` to make crosschain contract calls. For more info, view the [source code](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/pingpong/CrossChainPingPong.sol).
## High level overview
`CrossChainPingPong.sol` implements a cross-chain ping-pong game using the `L2ToL2CrossDomainMessenger`.
* Players hit a **virtual ball** back and forth between allowed L2 chains. The game starts with a serve
* from a designated start chain, and each hit increases the rally count. The contract tracks the last hitter's address, chain ID, and the current rally count.
### Diagram
```mermaid theme={null}
sequenceDiagram
participant Chain1 as Chain 1
participant Chain2 as Chain 2
Note over Chain1: 🚀 Game Starts (starting chain)
Note over Chain1: 🏓 Hit Ball
Chain1->>Chain2: 📤 Send PingPongBall {rallyCount: 1, lastHitter: Chain1}
Chain1-->>Chain1: Emit BallSent event
activate Chain2
Note over Chain2: 📥 Receive Ball
Chain2-->>Chain2: Emit BallReceived event
Note over Chain2: 🏓 Hit Ball
Chain2->>Chain1: 📤 Send PingPongBall {rallyCount: 2, lastHitter: Chain2}
Chain2-->>Chain2: Emit BallSent event
deactivate Chain2
activate Chain1
Note over Chain1: 📥 Receive Ball
Chain1-->>Chain1: Emit BallReceived event
Note over Chain1,Chain2: Game continues...
```
### Flow
* Deployed on all participating chains
* Utilizes CREATE2 with the same parameter, `_serverChainId`, resulting in the same address and initial state.
* Call `hitBallTo` on the chain with the ball, specifying a destination chain.
* Contract uses `L2ToL2CrossDomainMessenger` to send the ball data to the specified chain.
* The reference to the ball is deleted from the serving chain.
* `L2ToL2CrossDomainMessenger` on destination chain calls `receiveBall`.
* Contract verifies the message sender and origin.
* Ball data is stored, indicating its presence on this chain.
* Any user on the chain currently holding the ball calls `hitBallTo` to send it to another chain.
* Contract updates the `PingPongBall` data (increment rally count, update last hitter).
* Process repeats from step 2.
## Walkthrough
Here's an explanation of the functions in the contract, with a focus on how it interacts with `L2ToL2CrossChainMessenger`.
### Initialize contract state
#### Constructor Setup
```solidity theme={null}
constructor(uint256 _serverChainId) {
if (block.chainid == _serverChainId) {
ball = PingPongBall(1, block.chainid, msg.sender);
}
}
```
If the starting chain, initialize the ball allowing it to be hittable.
#### Reliance on CREATE2 for cross chain consistency
While not explicitly mentioned in the code, this contract's design implicitly assumes the use of CREATE2 for deployment. Here's why CREATE2 is crucial for this setup:
* **Predictable Addresses**:
CREATE2 enables deployment at the same address on all chains, crucial for cross-chain message verification:
```solidity theme={null}
if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender();
```
* **Self-referential Messaging**:
The contract sends messages to itself on other chains:
```solidity theme={null}
messenger.sendMessage(_toChainId, address(this), _message);
```
This requires `address(this)` to be consistent across chains.
* **Initialization State Considerations**:
The starting chain id is part of the initcode, meaning a deployment with a differing value would result in a different address via CREATE2. This is a nice feature as there's an implicit agreement on the starting chain from the address.
Without CREATE2, you would need to:
* Manually track contract addresses for each chain.
* Implement a more complex initialization process to register contract addresses across chains.
* Potentially redesign the security model that relies on address matching.
### Hit the ball
`hitBallTo`: This function is used to hit the ball, when present, to another chain
#### Hitting constraints
```solidity theme={null}
function hitBallTo(uint256 _toChainId) public {
if (ball.lastHitterAddress == address(0)) revert BallNotPresent();
if (_toChainId == block.chainid) revert InvalidDestination();
...
}
```
* The `ball` contract variable is populated on the chain, indicating its presence
* The destination must be a different chain
### Define receiving handler
```solidity theme={null}
modifier onlyCrossDomainCallback() {
if (msg.sender != address(messenger)) revert CallerNotL2ToL2CrossDomainMessenger();
if (messenger.crossDomainMessageSender() != address(this)) revert InvalidCrossDomainSender();
_;
}
function receiveBall(PingPongBall memory _ball) onlyCrossDomainCallback() external {
// Hold reference to the ball
ball = _ball;
emit BallReceived(messenger.crossDomainMessageSource(), block.chainid, _ball);
}
```
* The handler simply stores reference to the received ball
* The handler can only be invocable by the cross chain messenger
* Since the contract is self-referential, the cross chain sender must be the same contract address
### Hit the ball cross-chain
```solidity theme={null}
function hitBallTo(uint256 _toChainId) public {
...
// Construct a new ball
PingPongBall memory newBall = PingPongBall(ball.rallyCount + 1, block.chainid, msg.sender);
// Delete current reference
delete ball;
// Send to the destination
messenger.sendMessage(_toChainId, address(this), abi.encodeCall(this.receiveBall, (newBall)));
emit BallSent(block.chainid, _toChainId, newBall);
}
```
* Populate a new ball with updated properties
* Delete reference to the current ball so it's no longer hittable
* Invoke the contract on the destination chain matching the `receiveBall` handler defined in (2).
## Takeaways
This is just one of many patterns to use the `L2ToL2CrossDomainMessenger` in your contract to power cross chain calls. Key points to remember:
* **Simple Message Passing**: This design sends simple messages between identical contracts on different chains. Each message contains only the essential game state (rally count, last hitter). More complex systems might involve multiple contracts, intermediary relayers.
* **Cross Chain Sender Verification**: Always verify the sender of cross-chain messages. This includes checking both the immediate caller (the messenger) and the original sender on the source chain.
* **Cross Chain Contract Coordination**: This design uses CREATE2 for consistent contract addresses across chains, simplifying cross-chain verification. Alternative approaches include:
* Beacon proxy patterns for upgradeable contracts
* Post-deployment setup where contract addresses are specified after deployment
# Deploying crosschain event composability (contests)
Source: https://docs.optimism.io/app-developers/tutorials/interoperability/event-contests
Understand how the Contests example composes with cross-chain events, using the CrossL2Inbox to consume events emitted by contracts on any OP Stack chain.
OP Stack interop is in active development. Some features may be experimental.
We showcase cross chain composability through the implementation of contests. Leveraging the same underlying mechanism powering [TicTacToe](/app-developers/tutorials/interoperability/event-reads), these contests can permissionlessly integrate with the events emitted by any contract on OP Stack chains.
See the [frontend documentation](https://github.com/ethereum-optimism/supersim/tree/main/examples/contests) for how the contests UI is presented to the user.
## How it works
Unlike [TicTacToe](/app-developers/tutorials/interoperability/event-reads) which is deployed on every participating chain, the contests are deployed on a single L2, behaving like an application-specific OP Stack chain rather than a horizontally scaled app.
[Contests.sol](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/contests/Contests.sol) contains the implementation of the contests. We won't go into the details of the implementation here, but instead focus on how the contests can leverage cross chain event reading to compose with other contracts on OP Stack chains.
The system predeploy that enables pulling in validated cross-chain events is the [CrossL2Inbox](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs\&utm_medium=docs#crossl2inbox).
```solidity theme={null}
contract ICrossL2Inbox {
function validateMessage(Identifier calldata _id, bytes32 _msgHash) external view;
}
```
The two contest options are detailed below: [BlockHash contest](#blockhash-contest) and [TicTacToe contest](#tictactoe-contest).
#### BlockHash contest
With the existence of an event that emits the blockhash and height of a block, we can create a contest on the parity of the blockhash being even or odd.
```solidity theme={null}
contract BlockHashEmitter {
event BlockHash(uint256 blockHeight, bytes32 blockHash);
function emitBlockHash(uint256 _blockHeight) external {
bytes32 hash = blockhash(_blockHeight);
require(hash != bytes32(0));
emit BlockHash(_blockHeight, hash);
}
}
```
Integrating this emitter into a contest is extremely simple. The `BlockHashContestFactory` is a simple factory that creates a new contest for a given chain and block height.
#### TicTacToe contest
A contest for TicTacToe is created on an accepted game between two players, captured by the emitted `AcceptedGame` event. When decoding the event, the game is uniquely identified by the chain it was created on, `chainId`, and the associated `gameId`. These identifying properties of the game are used to create the resolver for the game.
```solidity theme={null}
contract TicTacToeContestFactory {
Contests public contests;
TicTacToe public tictactoe;
function newContest(Identifier calldata _id, bytes calldata _data) public payable {
// Validate Log
require(_id.origin == address(tictactoe), "not an event from the TicTacToe contract");
CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_data));
bytes32 selector = abi.decode(_data[:32], (bytes32));
require(selector == TicTacToe.AcceptedGame.selector, "incorrect event");
// Decode the event data
(uint256 chainId, uint256 gameId, address creator,) = abi.decode(_data[32:], (uint256, uint256, address, address));
IContestResolver resolver = new TicTacToeGameResolver(contests, tictactoe, chainId, gameId, creator);
contests.newContest{ value: msg.value }(resolver, msg.sender);
}
}
```
A contest is identified by and has its outcome determined by the `IContestResolver` instance. The resolver starts in the `UNDECIDED` state, updated into `YES` or `NO` when resolving itself
with the contest.
```solidity theme={null}
enum ContestOutcome {
UNDECIDED,
YES,
NO
}
interface IContestResolver {
function outcome() external returns (ContestOutcome);
}
```
#### Resolve BlockHash contest
When live, **anyone** can resolve the BlockHash contest by simply providing the right `BlockHash` event to the deployed resolver.
```solidity theme={null}
contract BlockHashContestFactory {
Contests public contests;
BlockHashEmitter public emitter; // Same emitter deployed on every chain
function newContest(uint256 _chainId, uint256 _blockNumber) public payable {
IContestResolver resolver = new BlockHashResolver(contests, emitter, _chainId, _blockNumber);
contests.newContest{ value: msg.value }(resolver, msg.sender);
}
}
contract BlockHashResolver is IContestResolver {
Contests public contests;
ContestOutcome public outcome;
BlockHashEmitter public emitter;
// The target chain & block height
uint256 public chainId;
uint256 public blockNumber;
function resolve(Identifier calldata _id, bytes calldata _data) external {
require(outcome == ContestOutcome.UNDECIDED);
// Validate Log
require(_id.origin == address(emitter), "not an event from the emitter");
require(_id.chainId == chainId, "must match target chain");
CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_data));
bytes32 selector = abi.decode(_data[:32], (bytes32));
require(selector == BlockHashEmitter.BlockHash.selector, "incorrect event");
// Event should correspond to the right contest
uint256 dataBlockNumber = abi.decode(_data[32:64], (uint256));
require(dataBlockNumber == blockNumber, "must match target block height");
// Resolve the contest (yes if odd, no if even)
bytes32 blockHash = abi.decode(_data[64:], (bytes32));
outcome = uint256(blockHash) % 2 != 0 ? ContestOutcome.YES : ContestOutcome.NO;
contests.resolveContest(this);
}
}
```
#### Resolve TicTacToe contest
When live, **anyone** can resolve the TicTacToe contest by providing the `GameWon` or `GameDraw` event of the associated game from the TicTacToe contract.
```solidity theme={null}
contract TicTacToeGameResolver is IContestResolver {
Contests public contests;
ContestOutcome public outcome;
TicTacToe public tictactoe;
// @notice Game for this resolver
Game public game;
constructor(Contests _contest, TicTacToe _tictactoe, uint256 _chainId, uint256 _gameId, address _creator) {
contests = _contest;
tictactoe = _tictactoe;
game = Game({chainId: _chainId, gameId: _gameId, creator: _creator});
outcome = ContestOutcome.UNDECIDED;
}
// @notice resolve this game by providing the game ending event
function resolve(Identifier calldata _id, bytes calldata _data) external {
// Validate Log
require(_id.origin == address(tictactoe));
CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_data));
// Ensure this is a finalizing event
bytes32 selector = abi.decode(_data[:32], (bytes32));
require(selector == TicTacToe.GameWon.selector || selector == TicTacToe.GameDraw.selector, "event not a game outcome");
// Event should correspond to the right game
(uint256 _chainId, uint256 gameId, address winner,,) = abi.decode(_data[32:], (uint256, uint256, address, uint8, uint8));
require(_chainId == game.chainId && gameId == game.gameId);
// Resolve based on if the creator has won (non-draw)
outcome = winner == game.creator && selector != TicTacToe.GameDraw.selector ? ContestOutcome.YES : ContestOutcome.NO;
contests.resolveContest(this);
}
}
```
## Takeaways
* Leveraging superchain interop, contracts in the superchain can compose with each other in a similar fashion to how they would on a single chain. No restrictions are placed on the kinds of events a contract can consume via the `CrossL2Inbox`.
* In this example, the `BlockHashContestFactory` and `TicTacToeContestFactory` can be seen as just starting points for the `Contests` app chain. As more contracts and apps are created in the superchain, developers can compose with them in a similar fashion without needing to change the `Contests` contract at all.
# Making cross-chain event reads (tic-tac-toe)
Source: https://docs.optimism.io/app-developers/tutorials/interoperability/event-reads
Understand how a horizontally scalable TicTacToe implementation uses CrossL2Inbox event reads to coordinate gameplay across interop chains.
OP Stack interop is in active development. Some features may be experimental.
This page explains a horizontally scalable implementation of TicTacToe. This [implementation](https://github.com/ethereum-optimism/supersim/blob/main/contracts/src/tictactoe/TicTacToe.sol) allows players to play each other from any chain without cross-chain calls, instead relying on cross-chain event reading. Since OP Stack interop can allow for event reading with a 1-block latency, the experience is the **same as a single-chain implementation**.
Check out the [frontend documentation](https://github.com/ethereum-optimism/supersim/tree/main/examples/tictactoe) to see how the game UI is presented to the player.
## How it works
We use events to define the ordering of a game with players only maintaining a local view. By default, a chain is also a part of its own interoperable dependency set, which means players on the same chain can also play each other **with no code changes**!
The system predeploy that enables pulling in validated cross-chain events is the [CrossL2Inbox](https://specs.optimism.io/interop/predeploys.html?utm_source=op-docs\&utm_medium=docs#crossl2inbox).
```solidity theme={null}
contract ICrossL2Inbox {
function validateMessage(Identifier calldata _id, bytes32 _msgHash) external view;
}
```
This contract relies on a **CREATE2** deployment to ensure a consistent address across all chains, used to assert the origin of the pulled in game event.
A game is uniquely identified by the chain it was started from with a unique nonce. This identifier is included in all event fields such that each player can uniquely reference it locally.
To start a game, a player invokes `newGame` which broadcasts a `NewGame` event that any opponent **on any chain** can react to.
```solidity theme={null}
event NewGame(uint256 chainId, uint256 gameId, address player);
function newGame() external {
emit NewGame(block.chainid, nextGameId, msg.sender);
nextGameId++;
}
```
When a `NewGame` event is observed, any player can declare their intent to play via `acceptGame`, referencing the `NewGame` event. An `AcceptedGame` event is emitted to signal to the creator that a game is ready to begin.
```solidity theme={null}
event AcceptedGame(uint256 chainId, uint256 gameId, address opponent, address player);
function acceptGame(ICrossL2Inbox.Identifier calldata _newGameId, bytes calldata _newGameData) external {
if (_newGameId.origin != address(this)) revert IdOriginNotTicTacToe();
ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_newGameId, keccak256(_newGameData));
bytes32 selector = abi.decode(_newGameData[:32], (bytes32));
if (selector != NewGame.selector) revert DataNotNewGame();
...
emit AcceptedGame(chainId, gameId, game.opponent, game.player);
}
```
To prepare for the game, the event data is decoded and a local view of this game is stored.
```solidity theme={null}
(uint256 chainId, uint256 gameId, address opponent) = abi.decode(_newGameData[32:], (uint256, uint256, address));
if (opponent == msg.sender) revert SenderIsOpponent();
// Record Game Metadata (no moves)
Game storage game = games[chainId][gameId][msg.sender];
game.player = msg.sender;
game.opponent = opponent;
game.gameId = gameId;
game.lastOpponentId = _newGameId;
game.movesLeft = 9;
emit AcceptedGame(chainId, gameId, game.opponent, game.player);
```
As `AcceptedGame` events are emitted, the player must pick one opponent to play. The opponent's `AcceptedGame` event is used to instantiate the game and play the starting move via the `MovePlayed` event.
```solidity theme={null}
event MovePlayed(uint256 chainId, uint256 gameId, address player, uint8 _x, uint8 _y);
function startGame(ICrossL2Inbox.Identifier calldata _acceptedGameId, bytes calldata _acceptedGameData, uint8 _x, uint8 _y) external {
if (_acceptedGameId.origin != address(this)) revert IdOriginNotTicTacToe();
ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_acceptedGameId, keccak256(_acceptedGameData));
bytes32 selector = abi.decode(_acceptedGameData[:32], (bytes32));
if (selector != AcceptedGame.selector) revert DataNotAcceptedGame();
...
emit MovePlayed(chainId, gameId, game.player, _x, _y);
```
The event fields contain the information required to perform the necessary validation.
* The game identifier for lookup
* The caller is the appropriate player
* The player is accepting from the same starting chain
```solidity theme={null}
(uint256 chainId, uint256 gameId, address player, address opponent) = // player, opponent swapped in local view
abi.decode(_acceptedGameData[32:], (uint256, uint256, address, address));
// The accepted game was started from this chain, from the sender
if (chainId != block.chainid) revert GameChainMismatch();
if (msg.sender != player) revert SenderNotPlayer();
// Game has not already been started with an opponent.
Game storage game = games[chainId][gameId][msg.sender];
if (game.opponent != address(0)) revert GameStarted();
// Store local view of this game
...
// Locally record the move by the player with 1
game.moves[_x][_y] = 1;
game.lastOpponentId = _acceptedGameId;
emit MovePlayed(chainId, gameId, game.player, _x, _y);
```
Once a game is started, players can continually make moves by invoking `makeMove`, reacting to a `MovePlayed` event of their opponent.
```solidity theme={null}
function makeMove(ICrossL2Inbox.Identifier calldata _movePlayedId, bytes calldata _movePlayedData, uint8 _x, uint8 _y) external {
if (_movePlayedId.origin != address(this)) revert IdOriginNotTicTacToe();
ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_movePlayedId, keccak256(_movePlayedData));
bytes32 selector = abi.decode(_movePlayedData[:32], (bytes32));
if (selector != MovePlayed.selector) revert DataNotMovePlayed();
}
```
Similar to `acceptGame`, validation is performed and the move of their opponent is first locally recorded.
* The game identifier for lookup
* The caller is the player for this game
* The opponent event corresponds to the same game
* Ordering is enforced by ensuring that the supplied event is always forward progressing.
```solidity theme={null}
(uint256 chainId, uint256 gameId,, uint8 oppX, uint8 oppY) = abi.decode(_movePlayedData[32:], (uint256, uint256, address, uint8, uint8));
// Game was instantiated for this player & the move is for the same game
Game storage game = games[chainId][gameId][msg.sender];
if (game.player != msg.sender) revert GameNotExists();
if (game.gameId != gameId) revert GameNotExists();
// The move played event is forward progressing from the last observed event
if (_movePlayedId.chainId != game.lastOpponentId.chainId) revert IdChainMismatch();
if (_movePlayedId.blockNumber <= game.lastOpponentId.blockNumber) revert MoveNotForwardProgressing();
game.lastOpponentId = _movePlayedId;
// Mark the opponents move
game.moves[oppX][oppY] = 2;
game.movesLeft--;
```
When a move is played we check if the game has been drawn or won, determining the subsequent event to emit.
The `makeMove` function is only callable when an opponent has a new `MovePlayed` event. Therefore, if the game is won or drawn, it cannot be progressed any further by the opponent.
```solidity theme={null}
// Make the players move
game.moves[_x][_y] = 1;
game.movesLeft--;
// Determine the status of the game
if (_isGameWon(game)) {
emit GameWon(chainId, gameId, game.player, _x, _y);
} else if (game.movesLeft == 0) {
emit GameDraw(chainId, gameId, game.player, _x, _y);
} else {
emit MovePlayed(chainId, gameId, game.player, _x, _y);
}
```
## Takeaways
Leveraging superchain interop, we can build new types of horizontally scalable contracts that do not rely on hub/spoke messaging with relayers.
* As new chains are added to the superchain, this contract can be installed by anyone and immediately playable with no necessary code changes. The frontend simply needs to react the addition of a new chain.
* The concept of a "chain" can be completely abstracted away from the user. When connecting their wallet, the frontend can simply pick the chain which the user has funds on with the lowest gas fees.
* Event reading enables a new level of composability for cross-chain interactions. Imagine [contests](/app-developers/tutorials/interoperability/event-contests) contract that resolves based on the outcome of a TicTacToe game via the `GameWon` or `GameLost` event without the need for a trusted oracle, nor permission or native integration with the TicTacToe contract.
# Relay transactions manually
Source: https://docs.optimism.io/app-developers/tutorials/interoperability/manual-relay
Learn to relay transactions directly by sending the correct transaction.
OP Stack interop is in active development. Some features may be experimental.
Messages are relayed automatically in the interop devnet.
## Overview
Learn to relay transactions directly by sending the correct transaction.
**Prerequisite technical knowledge**
* Familiarity with blockchain concepts
* Familiarity with [Foundry](https://book.getfoundry.sh/getting-started/installation), especially `cast`
**What you'll learn**
* How to use `cast` to relay transactions when autorelay does not work
* How to relay transactions using JavaScript
**Development environment requirements**
* Unix-like operating system (Linux, macOS, or WSL for Windows)
* Node.js version 16 or higher
* Git for version control
* Supersim environment configured and running
* Foundry tools installed (forge, cast, anvil)
### What you'll build
* A program to relay messages using [the JavaScript library](https://www.npmjs.com/package/@eth-optimism/viem)
* A shell script to relay messages using [`cast`](https://book.getfoundry.sh/cast/)
## Setup
These steps are necessary to run the tutorial, regardless of whether you are using `cast` or the JavaScript API.
This exercise needs to be done on Supersim.\
You cannot use the devnets because you cannot disable autorelay on them.
1. Follow [Install Supersim](/app-developers/tutorials/development/supersim/installation).
2. Run Supersim *without* `--interop.relay`.
```sh theme={null}
./supersim
```
The results of this step are similar to what the [message passing tutorial](/app-developers/tutorials/interoperability/message-passing) would produce if you did not have autorelay on.
Execute this script:
```sh theme={null}
#! /bin/sh
# full shell script preserved here...
# (Greeter.sol, GreetingSender.sol, sendAndRelay.sh setup)
# ...
```
This script installs `Greeter.sol` on chain B and `GreetingSender.sol` on chain A.\
These smart contracts let us send a message from chain A that needs to be relayed to chain B.
Then, the script creates `./manual-relay/sendAndRelay.sh` to manually relay a message from chain A to chain B.\
That script is [explained below](#manual-relay-using-cast).
Finally, this script writes out some parameter setting lines that should be executed on the main shell before you continue.\
With a fresh Supersim running, these should be:
```sh theme={null}
GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
```
## Manual relay using the API
Use a [Node](https://nodejs.org/en) project.
1. Initialize a new Node project.
```sh theme={null}
mkdir -p manual-relay/offchain
cd manual-relay/offchain
npm init -y
npm install --save-dev viem @eth-optimism/viem
mkdir src
```
2. Export environment variables:
```sh theme={null}
export GREETER_A_ADDRESS GREETER_B_ADDRESS PRIVATE_KEY
```
Create a file `manual-relay.mjs` with:
```javascript theme={null}
import {
createWalletClient,
http,
publicActions,
getContract,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'
import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem'
import { readFileSync } from 'fs';
const greeterData = JSON.parse(readFileSync('../onchain/out/Greeter.sol/Greeter.json'))
const greetingSenderData = JSON.parse(readFileSync('../onchain/out/Greeter.sol/Greeter.json'))
const account = privateKeyToAccount(process.env.PRIVATE_KEY)
const walletA = createWalletClient({
chain: supersimL2A,
transport: http(),
account
}).extend(publicActions)
.extend(publicActionsL2())
// .extend(walletActionsL2())
const walletB = createWalletClient({
chain: supersimL2B,
transport: http(),
account
}).extend(publicActions)
// .extend(publicActionsL2())
.extend(walletActionsL2())
const greeter = getContract({
address: process.env.GREETER_B_ADDRESS,
abi: greeterData.abi,
client: walletB
})
const greetingSender = getContract({
address: process.env.GREETER_A_ADDRESS,
abi: greetingSenderData.abi,
client: walletA
})
const txnBHash = await greeter.write.setGreeting(
["Greeting directly to chain B"])
await walletB.waitForTransactionReceipt({hash: txnBHash})
const greeting1 = await greeter.read.greet()
console.log(`Chain B Greeting: ${greeting1}`)
const txnAHash = await greetingSender.write.setGreeting(
["Greeting through chain A"])
const receiptA = await walletA.waitForTransactionReceipt({hash: txnAHash})
const greeting2 = await greeter.read.greet()
console.log(`Greeting before the relay transaction: ${greeting2}`)
const sentMessages = await walletA.interop.getCrossDomainMessages({
logs: receiptA.logs,
})
const sentMessage = sentMessages[0] // We only sent 1 message
const relayMessageParams = await walletA.interop.buildExecutingMessage({
log: sentMessage.log,
})
const relayMsgTxnHash = await walletB.interop.relayCrossDomainMessage(relayMessageParams)
const receiptRelay = await walletB.waitForTransactionReceipt({
hash: relayMsgTxnHash,
})
const greeting3 = await greeter.read.greet()
console.log(`Greeting after the relay transaction: ${greeting3}`)
```
```javascript theme={null}
import { supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'
import { walletActionsL2, publicActionsL2 } from '@eth-optimism/viem'
```
Run JavaScript program:
```sh theme={null}
node manual-relay.mjs
```
To see what messages were relayed by a specific transaction:
```javascript theme={null}
import { decodeRelayedL2ToL2Messages } from '@eth-optimism/viem'
const decodedRelays = decodeRelayedL2ToL2Messages({ receipt: receiptRelay })
console.log(decodedRelays)
console.log(decodedRelays.successfulMessages[0].log)
```
## Manual relay using `cast`
You can see an example of how to manually relay using `cast` in `manual-relay/sendAndRelay.sh`.\
It is somewhat complicated, so the setup creates one that is tailored to your environment.
Run the script:
```sh theme={null}
./manual-relay/sendAndRelay.sh
```
Here is the detailed explanation:
1. Configuration parameters
```sh theme={null}
#! /bin/sh
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
URL_CHAIN_A=http://localhost:9545
URL_CHAIN_B=http://localhost:9546
GREETER_A_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
GREETER_B_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3
CHAIN_ID_B=902
```
This is the configuration.
The greeter addresses are identical because the nonce for the user address has an identical nonce on both chains.
2. Send a message that needs to be relayed
```sh theme={null}
cast send -q --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A $$"
```
Send a message from chain A to chain B. The `$$` is the process ID, so if you rerun the script you'll see that the information changes.
3. Find the log entry to relay
```sh theme={null}
cast logs "SentMessage(uint256,address,uint256,address,bytes)" --rpc-url $URL_CHAIN_A | tail -14 > log-entry
```
Whenever `L2ToL2CrossDomainMessenger` sends a message to a different blockchain, it emits a [`SendMessage`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L83-L91) event.
Extract only the latest `SendMessage` event from the logs.
```yaml theme={null}
- address: 0x4200000000000000000000000000000000000023
blockHash: 0xcd0be97ffb41694faf3a172ac612a23f224afc1bfecd7cb737a7a464cf5d133e
blockNumber: 426
data: 0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa300000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064a41368620000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001948656c6c6f2066726f6d20636861696e2041203131333030370000000000000000000000000000000000000000000000000000000000000000000000
logIndex: 0
removed: false
topics: [
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320
0x0000000000000000000000000000000000000000000000000000000000000386
0x0000000000000000000000005fbdb2315678afecb367f032d93f642f64180aa3
0x0000000000000000000000000000000000000000000000000000000000000000
]
transactionHash: 0x1d6f2e5e2c8f3eb055e95741380ca36492f784b9782848b66b66c65c5937ff3a
transactionIndex: 0
```
4. Manipulate the log entry to obtain information
```sh theme={null}
TOPICS=`cat log-entry | grep -A4 topics | awk '{print $1}' | tail -4 | sed 's/0x//'`
TOPICS=`echo $TOPICS | sed 's/ //g'`
```
Consolidate the log topics into a single hex string.
```sh theme={null}
ORIGIN=0x4200000000000000000000000000000000000023
BLOCK_NUMBER=`cat log-entry | awk '/blockNumber/ {print $2}'`
LOG_INDEX=`cat log-entry | awk '/logIndex/ {print $2}'`
TIMESTAMP=`cast block $BLOCK_NUMBER --rpc-url $URL_CHAIN_A | awk '/timestamp/ {print $2}'`
CHAIN_ID_A=`cast chain-id --rpc-url $URL_CHAIN_A`
SENT_MESSAGE=`cat log-entry | awk '/data/ {print $2}'`
```
Read additional fields from the log entry.
```sh theme={null}
LOG_ENTRY=0x`echo $TOPICS$SENT_MESSAGE | sed 's/0x//'`
```
Consolidate the entire log entry.
5. Create the access list for the executing message
```sh theme={null}
RPC_PARAMS=$(cat <
OP Stack interop is in active development. Some features may be experimental.
## Overview
This tutorial demonstrates how to implement cross-chain communication within the OP Stack ecosystem. You'll build a complete
message passing system that enables different chains to interact with each other using the `L2ToL2CrossDomainMessenger` contract.
**Prerequisite technical knowledge**
* Intermediate Solidity programming
* Basic TypeScript knowledge
* Understanding of smart contract development
* Familiarity with blockchain concepts
**What you'll learn**
* How to deploy contracts across different chains
* How to implement cross-chain message passing
* How to handle sender verification across chains
* How to relay messages manually between chains
**Development environment**
* Unix-like operating system (Linux, macOS, or WSL for Windows)
* Node.js version 16 or higher
* Git for version control
**Required tools**
The tutorial uses these primary tools:
* Foundry: For smart contract development
* Supersim: For local blockchain simulation (optional)
* TypeScript: For offchain code (for relaying messages manually)
* Viem: For interactions with the chain from the offchain app
### What You'll Build
* A `Greeter` contract that stores and updates a greeting
* A `GreetingSender` contract that sends cross-chain messages to update the greeting
* A TypeScript application to relay messages between chains
This tutorial provides step-by-step instructions for implementing cross-chain messaging.
For a conceptual overview,
see the [Message Passing Explainer](/app-developers/guides/interoperability/message-passing).
In this tutorial, you will learn how to use the [`L2ToL2CrossDomainMessenger`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol) contract to pass messages between interoperable blockchains.
## Setting up your development environment
* Foundry for smart contract development (required in all cases)
* Supersim for local blockchain simulation (optional)
```sh theme={null}
forge --version
./supersim --version
```
## Implementing onchain message passing (in Solidity)
The implementation consists of three main components:
1. **Greeter Contract**: Deployed on `Chain B`, receives and stores messages.
2. **GreetingSender Contract**: Deployed on `Chain A`, initiates cross-chain messages.
1) If you are using [Supersim](/app-developers/tools-sdks/supersim), go to the directory where Supersim is installed and start it with autorelay.
```sh theme={null}
./supersim --interop.autorelay
```
If you are using [the devnets](/app-developers/guides/building-apps), just skip this step.
Supersim creates three `anvil` blockchains:
| Role | ChainID | RPC URL |
| -------- | ------: | ---------------------------------------------- |
| L1 | 900 | [http://127.0.0.1:8545](http://127.0.0.1:8545) |
| OPChainA | 901 | [http://127.0.0.1:9545](http://127.0.0.1:9545) |
| OPChainB | 902 | [http://127.0.0.1:9546](http://127.0.0.1:9546) |
These are the three networks involved in the devnet:
| Role | ChainID | RPC URL |
| ------------ | --------: | -------------------------------------------------------------------------------- |
| L1 (Sepolia) | 11155111 | [https://eth-sepolia.public.blastapi.io](https://eth-sepolia.public.blastapi.io) |
| ChainA | 420120000 | [https://interop-alpha-0.optimism.io](https://interop-alpha-0.optimism.io) |
| ChainB | 420120001 | [https://interop-alpha-1.optimism.io](https://interop-alpha-1.optimism.io) |
2) In a separate shell, store the configuration in environment variables.
Set these parameters for Supersim.
```sh theme={null}
PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
USER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
URL_CHAIN_A=http://127.0.0.1:9545
URL_CHAIN_B=http://127.0.0.1:9546
INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
```
For Devnet, specify in `PRIVATE_KEY` the private key you used for the setup script and then these parameters.
```sh theme={null}
USER_ADDRESS=`cast wallet address --private-key $PRIVATE_KEY`
URL_CHAIN_A=https://interop-alpha-0.optimism.io
URL_CHAIN_B=https://interop-alpha-1.optimism.io
INTEROP_BRIDGE=0x4200000000000000000000000000000000000028
```
To verify that the chains are running, check the balance of `$USER_ADDRESS`.
```sh theme={null}
cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_A
cast balance --ether $USER_ADDRESS --rpc-url $URL_CHAIN_B
```
1. Create a new Foundry project.
```sh theme={null}
mkdir onchain-code
cd onchain-code
forge init
```
2. In `src/Greeter.sol` put this file.
This is a variation on [Hardhat's Greeter contract](https://github.com/matter-labs/hardhat-zksync/blob/main/examples/upgradable-example/contracts/Greeter.sol).
```solidity theme={null}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Greeter {
string greeting;
event SetGreeting(
address indexed sender, // msg.sender
string greeting
);
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
emit SetGreeting(msg.sender, _greeting);
}
}
```
3. Deploy the `Greeter` contract to Chain B and store the resulting contract address in the `GREETER_B_ADDRESS` environment variable.
```sh theme={null}
GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
```
The command that deploys the contract is:
```sh theme={null}
forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast
```
The command output gives us the deployer address, the address of the new contract, and the transaction hash:
```
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
Transaction hash: 0xf155d360ec70ee10fe0e02d99c16fa5d6dc2a0e79b005fec6cbf7925ff547dbf
```
The [`awk`](https://www.tutorialspoint.com/awk/index.htm) command looks for the line that has `Deployed to:` and writes the third word in that line, which is the address.
```sh theme={null}
awk '/Deployed to:/ {print $3}'
```
Finally, in UNIX (including Linux and macOS) when the command line includes backticks, the shell executes the code between the backticks and puts the output, in this case the contract address, in the command.
So we get.
```sh theme={null}
GREETER_B_ADDRESS=
```
Run these commands to verify the contract works.
The first and third commands retrieve the current greeting, while the second command updates it.
```sh theme={null}
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "setGreeting(string)" Hello$$
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
```
4. Install the Optimism Solidity libraries into the project.
```sh theme={null}
cd lib
npm install @eth-optimism/contracts-bedrock
cd ..
echo @eth-optimism/=lib/node_modules/@eth-optimism/ >> remappings.txt
```
5. Create `src/GreetingSender.sol`.
```solidity theme={null}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol";
import { IL2ToL2CrossDomainMessenger } from "@eth-optimism/contracts-bedrock/src/L2/IL2ToL2CrossDomainMessenger.sol";
import { Greeter } from "src/Greeter.sol";
contract GreetingSender {
IL2ToL2CrossDomainMessenger public immutable messenger =
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
address immutable greeterAddress;
uint256 immutable greeterChainId;
constructor(address _greeterAddress, uint256 _greeterChainId) {
greeterAddress = _greeterAddress;
greeterChainId = _greeterChainId;
}
function setGreeting(string calldata greeting) public {
bytes memory message = abi.encodeCall(
Greeter.setGreeting,
(greeting)
);
messenger.sendMessage(greeterChainId, greeterAddress, message);
}
}
```
```solidity theme={null}
function setGreeting(string calldata greeting) public {
bytes memory message = abi.encodeCall(
Greeter.setGreeting,
(greeting)
);
messenger.sendMessage(greeterChainId, greeterAddress, message);
}
```
This function encodes a call to `setGreeting` and sends it to a contract on another chain.
`abi.encodeCall(Greeter.setGreeting, (greeting))` constructs the [calldata](https://docs.soliditylang.org/en/latest/internals/layout_in_calldata.html) by encoding the function selector and parameters.
The encoded message is then passed to `messenger.sendMessage`, which forwards it to the destination contract (`greeterAddress`) on the specified chain (`greeterChainId`).
This ensures that `setGreeting` is executed remotely with the provided `greeting` value (as long as there is an executing message to relay it).
6. Deploy `GreetingSender` to chain A.
```sh theme={null}
CHAIN_ID_B=`cast chain-id --rpc-url $URL_CHAIN_B`
GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
```
Send a greeting from chain A to chain B.
```sh theme={null}
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A"
sleep 4
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
```
The `sleep` call is because it can take up to two seconds until the transaction is included in chain A, and then up to two seconds until the relay transaction is included in chain B.
## Sender information
Run this command to view the events to see who called `setGreeting`.
```sh theme={null}
cast logs --rpc-url $URL_CHAIN_B 'SetGreeting(address,string)'
```
The sender information is stored in the second event topic.
However, for cross-chain messages, this value corresponds to the local `L2ToL2CrossDomainMessenger` contract address (`4200000000000000000000000000000000000023`), making it ineffective for identifying the original sender.
In this section we change `Greeter.sol` to emit a separate event in it receives a cross domain message, with the sender's identity (address and chain ID).
1. Modify `src/Greeter.sol` to this code.
```solidity theme={null}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Predeploys } from "@eth-optimism/contracts-bedrock/src/libraries/Predeploys.sol";
interface IL2ToL2CrossDomainMessenger {
function crossDomainMessageContext() external view returns (address sender_, uint256 source_);
}
contract Greeter {
IL2ToL2CrossDomainMessenger public immutable messenger =
IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
string greeting;
event SetGreeting(
address indexed sender, // msg.sender
string greeting
);
event CrossDomainSetGreeting(
address indexed sender, // Sender on the other side
uint256 indexed chainId, // ChainID of the other side
string greeting
);
function greet() public view returns (string memory) {
return greeting;
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
emit SetGreeting(msg.sender, _greeting);
if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
(address sender, uint256 chainId) =
messenger.crossDomainMessageContext();
emit CrossDomainSetGreeting(sender, chainId, _greeting);
}
}
}
```
```solidity theme={null}
interface IL2ToL2CrossDomainMessenger {
function crossDomainMessageContext() external view returns (address sender_, uint256 source_);
}
```
This definition isn't part of the [npmjs package](https://www.npmjs.com/package/@eth-optimism/contracts-bedrock) at writing, so we just add it here.
```solidity theme={null}
if (msg.sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
(address sender, uint256 chainId) =
messenger.crossDomainMessageContext();
emit CrossDomainSetGreeting(sender, chainId, _greeting);
}
```
If we see that we got a message from `L2ToL2CrossDomainMessenger`, we call [`L2ToL2CrossDomainMessenger.crossDomainMessageContext`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol#L118-L126).
2. Redeploy the contracts.
Because the address of `Greeter` is immutable in `GreetingSender`, we need to redeploy both contracts.
```sh theme={null}
GREETER_B_ADDRESS=`forge create --rpc-url $URL_CHAIN_B --private-key $PRIVATE_KEY Greeter --broadcast | awk '/Deployed to:/ {print $3}'`
GREETER_A_ADDRESS=`forge create --rpc-url $URL_CHAIN_A --private-key $PRIVATE_KEY --broadcast GreetingSender --constructor-args $GREETER_B_ADDRESS $CHAIN_ID_B | awk '/Deployed to:/ {print $3}'`
```
1. Set the greeting through `GreetingSender`.
```sh theme={null}
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
cast send --private-key $PRIVATE_KEY --rpc-url $URL_CHAIN_A $GREETER_A_ADDRESS "setGreeting(string)" "Hello from chain A, with a CrossDomainSetGreeting event"
sleep 4
cast call --rpc-url $URL_CHAIN_B $GREETER_B_ADDRESS "greet()" | cast --to-ascii
```
2. Read the log entries.
```sh theme={null}
cast logs --rpc-url $URL_CHAIN_B 'CrossDomainSetGreeting(address,uint256,string)'
echo $GREETER_A_ADDRESS
echo 0x385=`echo 0x385 | cast --to-dec`
echo 0x190a85c0=`echo 0x190a85c0 | cast --to-dec`
```
See that the second topic (the first indexed log parameter) is the same as `$GREETER_A_ADDRESS`.
The third topic can be either `0x385=901`, which is the chain ID for supersim chain A, or `0x190a85c0=420120000`, which is the chain ID for devnet alpha 0.
## Next steps
* Review the [OP Stack Interop Explainer](/op-stack/interop/explainer) for answers to common questions about interoperability.
* Read the [Message Passing Explainer](/app-developers/guides/interoperability/message-passing) to understand what happens "under the hood".
* Write a revolutionary app that uses multiple blockchains within the OP Stack ecosystem.
# Estimating transaction costs on OP Stack
Source: https://docs.optimism.io/app-developers/tutorials/transactions/sdk-estimate-costs
Learn how to use viem to estimate the cost of a transaction on OP Stack.
In this tutorial, you'll learn how to use [viem](https://viem.sh/op-stack/) to estimate the cost of a transaction on OP Mainnet.
You'll learn how to estimate the [execution gas fee](/app-developers/transactions/fees#execution-gas-fee) and the [L1 data fee](/app-developers/transactions/fees#l1-data-fee) independently.
You'll also learn how to estimate the total cost of the transaction all at once.
Check out the full explainer on [OP Stack transaction fees](/app-developers/guides/transactions/fees) for more information on how OP Mainnet charges fees under the hood.
## Supported networks
Viem supports any of the [OP Stack networks](/op-mainnet/network-information/connecting-to-op).
The OP Stack networks are included in Viem by default.
If you want to use a network that isn't included by default, you can add it to Viem's chain configurations.
## Dependencies
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
## Create a demo project
You're going to use the library for this tutorial.
Since is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.
```bash theme={null}
mkdir op-est-cost-tutorial
cd op-est-cost-tutorial
```
```bash theme={null}
pnpm init
```
```bash theme={null}
pnpm add viem
```
## Get ETH on OP Sepolia
This tutorial explains how to estimate transaction costs on OP Sepolia.
You will need to get some ETH on OP Sepolia in order to run the code in this tutorial.
## Add a private key to your environment
You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
Make sure this private key corresponds to an address that has ETH on .
Want to create a new wallet for this tutorial?
If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
```bash theme={null}
export TUTORIAL_PRIVATE_KEY=0x...
```
## Start the Node REPL
You're going to use the Node REPL to interact with .
To start the Node REPL, run the following command in your terminal:
```bash theme={null}
node
```
This will bring up a Node REPL prompt that allows you to run JavaScript code.
## Set session variables
You'll need a few variables throughout this tutorial.
Let's set those up now.
```js theme={null}
const { createPublicClient, createWalletClient, http, parseEther, parseGwei, formatEther } = require('viem');
const { privateKeyToAccount } = require('viem/accounts');
const { optimismSepolia } = require('viem/chains');
const { publicActionsL2, walletActionsL2 } = require('viem/op-stack');
```
```js theme={null}
const privateKey = process.env.TUTORIAL_PRIVATE_KEY
const account = privateKeyToAccount(privateKey)
```
```js theme={null}
const publicClient = createPublicClient({
chain: optimismSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(publicActionsL2())
```
```js theme={null}
const walletClientL2 = createWalletClient({
chain: optimismSepolia,
transport: http("https://sepolia.optimism.io"),
}).extend(walletActionsL2())
```
## Estimate transaction costs
You're now going to use the Viem to estimate the cost of a transaction on OP Mainnet.
Here you'll estimate the cost of a simple transaction that sends a small amount of ETH from your address to the address `0x1000000000000000000000000000000000000000`.
Viem makes it easy to create unsigned transactions so you can estimate the cost of a transaction before asking a user to sign it.
Here you'll create an unsigned transaction that sends a small amount of ETH from your address to the address `0x1000000000000000000000000000000000000000`.
```js theme={null}
const transaction = {
account,
to: '0x1000000000000000000000000000000000000000',
value: parseEther('0.00069420'),
gasPrice: await publicClient.getGasPrice()
}
```
With Viem you can estimate the total cost of a transaction using the [estimateTotalFee](https://viem.sh/op-stack/actions/estimateTotalFee) method.
```js theme={null}
const totalEstimate = await publicClient.estimateTotalFee(transaction)
console.log(`Estimated Total Cost: ${formatEther(totalEstimate)} ETH`)
```
Now that you've estimated the total cost of the transaction, go ahead and send it to the network.
This will make it possible to see the actual cost of the transaction to compare to your estimate.
```js theme={null}
const txHash = await walletClientL2.sendTransaction(transaction)
console.log(`Transaction Hash: ${txHash}`)
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash })
console.log('receipt', receipt);
```
Once you get back the transaction receipt, check the actual execution gas fee.
You can do so by accessing the `gasUsed` and `effectiveGasPrice` from the transaction receipt.
You can then multiply these values to get the actual L2 cost of the transaction
```js theme={null}
const l2CostActual = receipt.gasUsed * receipt.effectiveGasPrice
console.log(`Actual Execution Gas Fee: ${formatEther(l2CostActual)} ETH`)
```
You can also check the actual L1 data fee.
```js theme={null}
const l1CostActual = receipt.l1Fee
console.log(`Actual L1 Data Fee: ${formatEther(l1CostActual)} ETH`)
```
Sum these two together to get the actual total cost of the transaction.
```js theme={null}
const totalActual = l2CostActual + l1CostActual
console.log(`Actual Total Cost: ${formatEther(totalActual)} ETH`)
```
Finally, check the difference between the estimated total cost and the actual total cost.
This will give you a sense of how accurate your estimate was.
Estimates will never be entirely accurate, but they should be close!
```js theme={null}
const difference = totalEstimate >= totalActual ? totalEstimate - totalActual : totalActual - totalEstimate
console.log(`Estimation Difference: ${formatEther(difference)} ETH`)
```
Estimates will never be entirely accurate due to network conditions and gas price fluctuation, but they should be close to the actual costs.
## Next steps
* Always estimate before sending: Estimating costs before sending a transaction helps prevent unexpected fees and failed transactions.
* Account for gas price volatility: Gas prices can change rapidly. Consider adding a buffer to your estimates or implementing a gas price oracle for more accurate pricing.
* Optimize transaction data: Minimize the amount of data in your transactions to reduce L1 data fees.
* Monitor network conditions: Keep an eye on network congestion and adjust your estimates accordingly.
* Use appropriate gas limits: Setting too low a gas limit can cause transactions to fail, while setting it too high can result in unnecessary costs.
* Implement retry mechanisms: If a transaction fails due to underestimated gas, implement a retry mechanism with adjusted gas parameters.
# Tracing deposits and withdrawals
Source: https://docs.optimism.io/app-developers/tutorials/transactions/sdk-trace-txns
Learn how to use the viem library to trace deposits and withdrawals between L1 and L2.
In this tutorial, you'll learn how to use the [viem](https://viem.sh) library to trace a [Standard Bridge](/app-developers/guides/bridging/standard-bridge) deposit or withdrawal between L1 and L2.
You'll specifically learn how to determine the status of a deposit or withdrawal and how to retrieve the transaction receipt for the executed transaction on L1 (for withdrawals) or L2 (for deposits).
## Dependencies
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
## Create a demo project
You're going to use the library for this tutorial.
Since is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.
```bash theme={null}
mkdir trace-trx
cd trace-trx
```
```bash theme={null}
pnpm init
```
```bash theme={null}
pnpm add viem
```
## Add RPC URLs to your environment
You'll be using the `getTransactionReceipt` function from the viem library during this tutorial. This function uses event queries to retrieve the receipt for a deposit or withdrawal.
Since this function uses large event queries, you'll need to use an RPC provider like [Alchemy](https://alchemy.com) that supports indexed event queries.
Grab an L1 and L2 RPC URL for Sepolia and OP Sepolia, respectively.
```bash theme={null}
export L1_RPC_URL="https://YOUR_L1_ SEPOLIA_RPC_URL_HERE"
export L2_RPC_URL="https://YOUR_L2_OP_SEPOLIA_RPC_URL_HERE"
```
## Start the Node REPL
You're going to use the Node REPL to interact with .
To start the Node REPL, run the following command in your terminal:
```bash theme={null}
node
```
This will bring up a Node REPL prompt that allows you to run JavaScript code.
## Import dependencies
You need to import some dependencies into your Node REPL session.
### Import viem
```js theme={null}
const { createPublicClient, http } = require('viem');
const { optimismSepolia, sepolia } = require('viem/chains');
```
## Set session variables
You'll need a few variables throughout this tutorial. Let's set those up now.
```js theme={null}
const l1RpcUrl = process.env.L1_RPC_URL;
const l2RpcUrl = process.env.L2_RPC_URL;
```
You'll be tracing a specific deposit in this tutorial. Deposit tracing is generally based on the transaction hash of the transaction that triggered the deposit. You can replace this transaction hash with your own if you'd like.
```js theme={null}
const depositHash = '0x5896d6e4a47b465e0d925723bab838c62ef53468139a5e9ba501efd70f90cccb'
```
You'll also be tracing a specific withdrawal in this tutorial. Like with deposits, withdrawal tracing is generally based on the transaction hash of the transaction that triggered the withdrawal. You can replace this transaction hash with your own if you'd like.
```js theme={null}
const withdrawalHash = '0x18b8b4022b8d9e380fd89417a2e897adadf31e4f41ca17442870bf89ad024f42'
```
```js theme={null}
const l1Client = createPublicClient({
chain: sepolia,
transport: http(l1RpcUrl),
});
const l2Client = createPublicClient({
chain: optimismSepolia,
transport: http(l2RpcUrl),
});
```
## Trace a Deposit
You can use viem to trace a deposit.
You can query for the deposit status using the transaction hash of the deposit.
```js theme={null}
console.log('Grabbing deposit status...')
const depositStatus = await l2Client.getTransactionReceipt({ hash: depositHash });
console.log(depositStatus);
```
Retrieve the transaction receipt for the deposit using the viem client.
```js theme={null}
console.log('Grabbing deposit receipt...')
const depositReceipt = await l2Client.getTransaction({ hash: depositHash });
console.log(depositReceipt);
```
You can directly query for the L2 transaction that executed the deposit.
```js theme={null}
console.log('Grabbing deposit txn...')
const depositTransaction = await l2Client.getTransaction({ hash: depositHash });
console.log(depositTransaction);
```
## Trace a withdrawal
You can use viem's functions to trace a withdrawal.
Like deposits, withdrawals can have multiple statuses depending on where they are in the process.
```js theme={null}
console.log('Grabbing withdrawal status...')
const withdrawalStatus = await l1Client.getTransactionReceipt({ hash: withdrawalHash });
console.log(withdrawalStatus);
```
Retrieve the L1 transaction receipt for the withdrawal.
```js theme={null}
console.log('Grabbing withdrawal receipt...')
const withdrawalReceipt = await l1Client.getTransaction({ hash: withdrawalHash });
console.log(withdrawalReceipt);
```
Directly query for the L1 transaction that executed the withdrawal.
```js theme={null}
console.log('Grabbing withdrawal txn...')
const withdrawalTransaction = await l1Client.getTransaction({ hash: withdrawalHash });
console.log(withdrawalTransaction);
```
## Next steps
* Check out the tutorial on [bridging ERC-20 tokens with the @eth-optimism/viem package](/app-developers/tutorials/bridging/cross-dom-bridge-erc20) to learn how to create deposits and withdrawals.
# Triggering OP Stack transactions from Ethereum
Source: https://docs.optimism.io/app-developers/tutorials/transactions/send-tx-from-eth
Learn how to force transaction inclusion without the OP Stack Sequencer using Viem.
OP Stack currently uses a single-Sequencer block production model.
This means that there is only one Sequencer active on the network at any given time. Single-Sequencer models are simpler than their highly decentralized counterparts but they are also more vulnerable to potential downtime.
Sequencer downtime must not be able to prevent users from transacting on the network. As a result, OP Stack includes a mechanism for "forcing" transactions to be included in the blockchain. This mechanism involves triggering a transaction on OP Stack by sending a transaction on Ethereum.
In this tutorial you'll learn how to trigger a transaction on OP Stack from Ethereum using Viem. You'll use the OP Sepolia testnet, but the same logic will apply to OP Stack.
## Dependencies
* [node](https://nodejs.org/en/)
* [pnpm](https://pnpm.io/installation)
## Create a demo project
You're going to use the library for this tutorial.
Since is a [Node.js](https://nodejs.org/en/) library, you'll need to create a Node.js project to use it.
```bash theme={null}
mkdir trigger-transaction
cd trigger-transaction
```
```bash theme={null}
pnpm init
```
```bash theme={null}
pnpm add viem
```
## Get ETH on Sepolia and OP Sepolia
This tutorial explains how to bridge tokens from Sepolia to OP Sepolia. You will need to get some ETH on both of these testnets.
## Add a private key to your environment
You need a private key in order to sign transactions.
Set your private key as an environment variable with the `export` command.
Make sure this private key corresponds to an address that has ETH on .
Want to create a new wallet for this tutorial?
If you have [`cast`](https://book.getfoundry.sh/getting-started/installation) installed you can run `cast wallet new` in your terminal to create a new wallet and get the private key.
```bash theme={null}
export TUTORIAL_PRIVATE_KEY=0x...
```
## Start the Node REPL
You're going to use the Node REPL to interact with .
To start the Node REPL, run the following command in your terminal:
```bash theme={null}
node
```
This will bring up a Node REPL prompt that allows you to run JavaScript code.
## Import dependencies
You need to import some dependencies into your Node REPL session.
```js theme={null}
const { createPublicClient, createWalletClient, http, parseEther, formatEther } = require('viem');
const { optimismSepolia, sepolia } = require('viem/chains');
const { privateKeyToAccount } = require('viem/accounts');
const { publicActionsL2, publicActionsL1, walletActionsL2, walletActionsL1, getL2TransactionHashes } = require ('viem/op-stack')
```
## Set session variables
You'll need a few variables throughout this tutorial. Let's set those up now.
```js theme={null}
const privateKey = process.env.TUTORIAL_PRIVATE_KEY;
const account = privateKeyToAccount(privateKey);
```
```js theme={null}
const l1PublicClient = createPublicClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") }).extend(publicActionsL1())
const l2PublicClient = createPublicClient({ chain: optimismSepolia, transport: http("https://sepolia.optimism.io") }).extend(publicActionsL2());
const l1WalletClient = createWalletClient({ chain: sepolia, transport: http("https://rpc.ankr.com/eth_sepolia") }).extend(walletActionsL1());
```
## Check your initial balance
You'll be sending a small amount of ETH as part of this tutorial. Quickly check your balance on OP Sepolia so that you know how much you had at the start of the tutorial.
```js theme={null}
const initialBalance = await l2PublicClient.getBalance({ address });
console.log(`Initial balance: ${formatEther(initialBalance)} ETH`);
```
## Trigger the transaction
Now you'll use the `OptimismPortal` contract to trigger a transaction on OP Sepolia by sending a transaction on Sepolia.
```js theme={null}
const optimismPortalAbi = [
{
inputs: [
{ internalType: 'uint256', name: '_gasLimit', type: 'uint256' },
{ internalType: 'bytes', name: '_data', type: 'bytes' },
],
name: 'depositTransaction',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
];
```
When sending transactions via the `OptimismPortal` contract it's important to always include a gas buffer. This is because the `OptimismPortal` charges a variable amount of gas depending on the current demand for L2 transactions triggered via L1. If you do not include a gas buffer, your transactions may fail.
```js theme={null}
const optimismPortalAddress = '0x5b47E1A08Ea6d985D6649300584e6722Ec4B1383';
const gasLimit = 100000n;
const data = '0x';
const value = parseEther('0.000069420');
const gasEstimate = await l1PublicClient.estimateContractGas({
address: optimismPortalAddress,
abi: optimismPortalAbi,
functionName: 'depositTransaction',
args: [gasLimit, data],
value,
account: account.address,
});
```
Now you'll send the transaction. Note that you are including a buffer of 20% on top of the gas estimate.
```js theme={null}
const { request } = await l1PublicClient.simulateContract({
account,
address: optimismPortalAddress,
abi: optimismPortalAbi,
functionName: 'depositTransaction',
args: [gasLimit, data],
value,
gas: gasEstimate * 120n / 100n, // 20% buffer
})
const l1TxHash = await l1WalletClient.writeContract(request)
console.log(`L1 transaction hash: ${l1TxHash}`)
```
First you'll need to wait for the L1 transaction to be mined.
```js theme={null}
const l1TxHash = await l1WalletClient.writeContract(request)
```
Now you'll need to wait for the corresponding L2 transaction to be included in a block. This transaction is automatically created as a result of your L1 transaction. Here you'll determine the hash of the L2 transaction and then wait for that transaction to be included in the L2 blockchain.
```js theme={null}
const [l2Hash] = getL2TransactionHashes(l1TxHash)
console.log(`Corresponding L2 transaction hash: ${l2Hash}`);
const l2Receipt = await l2PublicClient.waitForTransactionReceipt({
hash: l2Hash,
});
console.log('L2 transaction confirmed:', l2Receipt);
```
## Check your updated balance
You should have a little less ETH on OP Sepolia now. Check your balance to confirm.
```js theme={null}
const finalBalance = await l2Wallet.getBalance()
console.log(`Final balance: ${formatEther(finalBalance)} ETH`);
```
Make sure that the difference is equal to the amount you were expecting to send.
```js theme={null}
const difference = initialBalance - finalBalance
console.log(`Difference in balance: ${formatEther(difference)} ETH`);
```
## Next steps
You've successfully triggered a transaction on OP Sepolia by sending a transaction on Sepolia using Viem. Although this tutorial demonstrated the simple example of sending a basic ETH transfer from your L2 address via the OptimismPortal contract, you can use this same technique to trigger any transaction you want. You can trigger smart contracts, send ERC-20 tokens, and more.
# Configure the batcher
Source: https://docs.optimism.io/chain-operators/guides/configuration/batcher
Learn how to configure the op-batcher for your chain, covering the batcher policy, cost tuning, multi-blob transactions, and sequencer throttling.
The op-batcher posts L2 sequencer data to the L1, to make it available for
verifiers. This guide walks through the policy constraints every OP Stack chain
must respect and the settings with the biggest impact on cost and stability.
For a catalogue of every CLI flag and environment variable, see the
[batcher configuration reference](/chain-operators/reference/batcher-configuration).
## Batcher policy
The batcher policy defines high-level constraints and responsibilities regarding how L2 data is posted to L1. Below are the [standard guidelines](/op-stack/protocol/superchain-registry#what-is-a-standard-chain) for configuring the batcher within the OP Stack.
| Parameter | Description | Administrator | Requirement | Notes |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Data Availability Type | Specifies whether the batcher uses **blobs**, **calldata**, or **auto** to post transaction data to L1. | Batch submitter address | Ethereum (Blobs or Calldata) | - Alternative data availability (Alt-DA) is not yet supported in the standard configuration.
- The sequencer can switch at will between blob transactions and calldata, with no restrictions, because both are fully secured by L1. |
| Batch Submission Frequency | Determines how frequently the batcher submits aggregated transaction data to L1 (via the batcher transaction). | Batch submitter address | Must target **1,800 L1 blocks** (6 hours on Ethereum, assuming 12s L1 block time) or lower | - Batches must be posted before the sequencing window closes (commonly 12 hours by default).
- Leave a buffer for L1 network congestion and data size to ensure that each batch is fully committed in a timely manner. |
* **Data Availability Types**:
* **Calldata** is generally simpler but can be more expensive on mainnet Ethereum, depending on gas prices.
* **Blobs** are typically lower cost when your chain has enough transaction volume to fill large chunks of data.
* The `op-batcher` can toggle between these approaches by setting the `--data-availability-type=` flag or with the `OP_BATCHER_DATA_AVAILABILITY_TYPE` env variable. Setting this flag to `auto` will allow the batcher to automatically switch between `calldata` and `blobs` based on the current L1 gas price.
* **Batch Submission Frequency** (`OP_BATCHER_MAX_CHANNEL_DURATION` and related flags):
* Standard OP Chains frequently target a maximum channel duration between 1–6 hours.
* Your chain should never exceed your L2's sequencing window (commonly 12 hours).
* If targeting a longer submission window (e.g., 5 or 6 hours), be aware that the [safe head](https://specs.optimism.io/glossary.html#safe-l2-head) can stall up to that duration.
Include these high-level "policy" requirements when you set up or modify your `op-batcher` configuration. See the [batcher configuration reference](/chain-operators/reference/batcher-configuration), which explains each CLI flag and environment variable in depth.
## Recommendations
### Set your `OP_BATCHER_MAX_CHANNEL_DURATION`
The default value inside `op-batcher`, if not specified, is still `0`, which means channel duration tracking is disabled.
For very low throughput chains, this would mean to fill channels until close to the sequencing window and post the channel to `L1 SUB_SAFETY_MARGIN` L1 blocks before the sequencing window expires.
To minimize costs, we recommend setting your `OP_BATCHER_MAX_CHANNEL_DURATION` to target 5 hours, with a value of `1500` L1 blocks. When non-zero, this parameter is the max time (in L1 blocks, which are 12 seconds each) between which batches will be submitted to the L1. If you have this set to 5 for example, then your batcher will send a batch to the L1 every 5\*12=60 seconds. When using blobs, because 130kb blobs need to be purchased in full, if your chain doesn't generate at least \~130kb of data in those 60 seconds, then you'll be posting only partially full blobs and wasting storage.
* We do not recommend setting any values higher than targeting 5 hours, as batches have to be submitted within the sequencing window which defaults to 12 hours for OP chains, otherwise your chain may experience a 12 hour long chain reorg. 5 hours is the longest length of time we recommend that still sits snugly within that 12 hour window to avoid affecting stability.
* If your chain fills up full blobs of data before the `OP_BATCHER_MAX_CHANNEL_DURATION` elapses, a batch will be submitted anyways - (e.g. even if the OP Mainnet batcher sets an `OP_BATCHER_MAX_CHANNEL_DURATION` of 5 hours, it will still be submitting batches every few minutes)
While setting an`OP_BATCHER_MAX_CHANNEL_DURATION` of `1500` results in the cheapest fees, it also means that your [safe head](https://specs.optimism.io/glossary.html#safe-l2-head) can stall for up to 5 hours.
* This will negatively impact apps on your chain that rely on the safe head for operation. While many apps can likely operate simply by following the unsafe head, often Centralized Exchanges or third party bridges wait until transactions are marked safe before processing deposits and withdrawal.
* Thus a larger gap between posting batches can result in significant delays in the operation of certain types of high-security applications.
### Configure your batcher to use multiple blobs
When there's blob congestion, running with high blob counts can backfire, because you will have a harder time getting blobs included and then fees will bump, which always means a doubling of the priority fees.
The `op-batcher` has the capabilities to send multiple blobs per single blob transaction. This is accomplished by the use of multi-frame channels, see the [specs](https://specs.optimism.io/protocol/derivation.html?utm_source=op-docs\&utm_medium=docs#frame-format) for more technical details on channels and frames.
A minimal batcher configuration (with env vars) to enable 6-blob batcher transactions is:
```
- OP_BATCHER_BATCH_TYPE=1 # span batches, optional
- OP_BATCHER_DATA_AVAILABILITY_TYPE=blobs
- OP_BATCHER_TARGET_NUM_FRAMES=6 # 6 blobs per tx
- OP_BATCHER_TXMGR_MIN_BASEFEE=2.0 # 2 gwei, might need to tweak, depending on gas market
- OP_BATCHER_TXMGR_MIN_TIP_CAP=2.0 # 2 gwei, might need to tweak, depending on gas market
- OP_BATCHER_RESUBMISSION_TIMEOUT=240s # wait 4 min before bumping fees
```
This enables blob transactions and sets the target number of frames to 6, which translates to 6 blobs per transaction.
The minimum tip cap and base fee are also lifted to 2 gwei because it is uncertain how easy it will be to get 6-blob transactions included and slightly higher priority fees should help.
The resubmission timeout is increased to a few minutes to give more time for inclusion before bumping the fees because current transaction pool implementations require a doubling of fees for blob transaction replacements.
Multi-blob transactions are particularly useful for medium to high-throughput chains, where enough transaction volume exists to fill up 6 blobs in a reasonable amount of time.
To determine what number of blobs is right for your chain, estimate the compressed batch data your chain produces per channel duration and divide by blob capacity (about 130 KB); [Size your channels](/use-cases/tune-batcher-costs#step-4-size-your-channels) in the Tune batcher costs guide walks through this estimate and the matching fee scalar configuration. Please also refer to the [Post batch data as blobs](/chain-operators/guides/features/blobs) guide for chain operators.
### Set your `--batch-type=1` to use span batches
Span batches reduce the overhead of OP Stack chains, introduced in the Delta network upgrade. This is beneficial for sparse and low-throughput OP Stack chains.
The overhead is reduced by representing a span of consecutive L2 blocks in a more efficient manner, while preserving the same consistency checks as regular batch data.
For step-by-step instructions, including how to confirm Delta is active first, see [Enable span batches](/chain-operators/guides/features/enable-span-batches).
## Batcher sequencer throttling
This feature is a batcher-driven sequencer-throttling control loop. This is to avoid sudden spikes in L1 DA-usage consuming too much available gas and causing a backlog in batcher transactions. The batcher can throttle the sequencer's data throughput instantly when it sees too much batcher data built up.
There are two throttling knobs:
1. Transaction throttling, which skips individual transactions whose estimated compressed L1 DA usage goes over a certain threshold, and
2. Block throttling, which caps a block's estimated total L1 DA usage and leads to not including transactions during block building that would move the block's L1 DA usage past a certain threshold.
**Feature requirements**
* This feature is enabled by default and requires the sequencer's `op-reth` node to expose the `miner_setMaxDASize` RPC (enable the `miner` namespace, below). It can be disabled by setting `--throttle.unsafe-da-bytes-lower-threshold` (env var `OP_BATCHER_THROTTLE_UNSAFE_DA_BYTES_LOWER_THRESHOLD`) to 0, which is the only flag you need to change to turn throttling off. The sequencer's `op-reth` node has to be updated first, before updating the batcher, so that the required RPC is available at the time of the batcher restart.
* It is required to upgrade to `op-conductor/v0.2.0` if you are using conductor's leader-aware rpc proxy feature. This conductor release includes support for proxying the `miner_setMaxDASize` RPC.
**Configuration**
Note that this feature requires the batcher to correctly follow the sequencer at all times, or it would set throttling parameters on a non-sequencer EL client. That means, active sequencer follow mode has to be enabled correctly by listing all the possible sequencers in the L2 rollup and EL endpoint flags.
The batcher is configured for throttling by default with the parameters described below (and corresponding flags which allow the defaults to be overridden):
* Backlog of pending block bytes beyond which the batcher will enable throttling on the sequencer via `--throttle.unsafe-da-bytes-lower/upper-threshold` (env var `OP_BATCHER_THROTTLE_UNSAFE_DA_BYTES_LOWER/UPPER_THRESHOLD`): 3\_200\_000 and 12\_800\_000 (batcher backlog of 3.2MB to 12.8MB of data to batch). Disable throttling by setting the lower threshold to `0`. The upper threshold sets the level where the maximum throttling intensity is reached.
* Individual tx size throttling via `--throttle.tx-size-lower/upper-limit` (env var `OP_BATCHER_THROTTLE_TX_SIZE_LOWER/UPPER_LIMIT`): 150 and 20\_000. This is the limit on the estimated compressed size of a transaction when throttling is at maximum/minimum intensity respectively.
* Block size throttling via `--throttle.block-size-lower/upper-limit` (env var `OP_BATCHER_THROTTLE_BLOCK_SIZE_LOWER/UPPER_LIMIT`): 2\_000 and 130\_000. This is the limit on the estimated compressed size of a block when throttling is at maximum/minimum intensity respectively.
* Throttler controller type via `--throttle.controller-type` (env var `OP_BATCHER_THROTTLE_CONTROLLER_TYPE`): `quadratic` by default. This determines how transaction and block size limits are interpolated when throttling is at intermediate intensity.
For full details, see the [readme](https://github.com/ethereum-optimism/optimism/blob/develop/op-batcher/readme.md).
If the batcher at startup has throttling enabled and the sequencer's `op-reth` node to which it's talking doesn't have the `miner_setMaxDASize` RPC enabled, it will fail with an error message like:
```
lvl=warn msg="Served miner_setMaxDASize" reqid=1 duration=11.22µs err="the method miner_setMaxDASize does not exist/is not available"
In this case, make sure the miner API namespace is enabled for the correct transport protocol (HTTP or WS), see next paragraph.
```
The `miner_setMaxDASize` RPC has to be enabled by adding the `miner` namespace to `op-reth`'s API flags:
```
--http.api=web3,debug,eth,txpool,net,miner
--ws.api=web3,debug,eth,txpool,net,miner
```
It is recommended to add it to both HTTP and WS.
## Example configuration
This is a basic example of a batcher configuration. Optimal batcher configuration is going to differ for each chain,
however you can see some of the most important variables configured below:
```
OP_BATCHER_WAIT_NODE_SYNC: true
OP_BATCHER_CHECK_RECENT_TXS_DEPTH: 5
OP_BATCHER_POLL_INTERVAL: "5s"
OP_BATCHER_BATCH_TYPE: "1" # span
OP_BATCHER_COMPRESSION_ALGO: brotli-10
OP_BATCHER_DATA_AVAILABILITY_TYPE: auto
OP_BATCHER_MAX_CHANNEL_DURATION: "150" # up to 30 min to fill blobs
OP_BATCHER_TARGET_NUM_FRAMES: "5" # 5 blobs, can go to 6 with Pectra activated on L1
OP_BATCHER_SUB_SAFETY_MARGIN: "300" # 1h safety margin to prevent seq window elapse
OP_BATCHER_NUM_CONFIRMATIONS: "4"
OP_BATCHER_NETWORK_TIMEOUT: "10s"
OP_BATCHER_TXMGR_MIN_BASEFEE: "2.0"
OP_BATCHER_TXMGR_MIN_TIP_CAP: "2.0"
OP_BATCHER_TXMGR_FEE_LIMIT_MULTIPLIER: 16 # allow up to 4 doublings
OP_BATCHER_MAX_PENDING_TX: "10"
OP_BATCHER_RESUBMISSION_TIMEOUT: "180s" # wait 3 min before bumping fees
OP_BATCHER_ACTIVE_SEQUENCER_CHECK_DURATION: 5s
```
Lower throughput chains, which aren't filling up channels before the `MAX_CHANNEL_DURATION` is hit,
may save gas by increasing the `MAX_CHANNEL_DURATION`. See the [recommendations section](#set-your--op_batcher_max_channel_duration).
# Chain Operator Configurations
Source: https://docs.optimism.io/chain-operators/guides/configuration/getting-started
Learn how to configure an OP Stack chain.
OP Stack chains can be configured for the Chain Operator's needs.
Each component of the stack has its own considerations.
See the following for documentation for details on configuring each piece.
Deploying your OP Stack contracts requires creating a deployment configuration
JSON file. This defines the behavior of your network at its genesis.
* **Important Notes:**
* The Rollup Configuration sets parameters for the L1 smart contracts upon deployment. These parameters govern the behavior of your chain and are critical to its operation.
* Be aware that many of these values cannot be changed after deployment or require a complex process to update.
Carefully consider and validate all settings during configuration to avoid issues later.
* [Rollup Deployment Configuration reference](/chain-operators/reference/rollup-deployment-configuration)
The batcher is the service that submits the L2 Sequencer data to L1, to make
it available for verifiers. These configurations determine the batcher's
behavior.
* [Batcher Configuration Documentation](/chain-operators/guides/configuration/batcher)
The proposer is the service that submits the output roots to the L1. These
configurations determine the proposer's behavior.
* [Proposer Configuration Documentation](/chain-operators/guides/configuration/proposer)
The rollup node has a wide array of configurations for both the consensus and
execution clients.
* [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients)
* [Execution Client Configuration](/node-operators/guides/configuration/execution-clients)
# How to configure challenger for your chain
Source: https://docs.optimism.io/chain-operators/guides/configuration/op-challenger-config-guide
Learn how to configure challenger for your OP Stack chain.
This guide provides step-by-step instructions for setting up the configuration and monitoring options for `op-challenger`.
The challenger is a critical fault proofs component that monitors dispute games and challenges invalid claims to protect your OP Stack chain. See the [op-challenger explainer](/op-stack/fault-proofs/challenger) for a general overview of this fault proofs feature.
The challenger is responsible for:
* Monitoring dispute games created by the fault proof system
* Challenging invalid claims in dispute games
* Defending valid state transitions
* Resolving games when possible
For the complete catalog of flags, environment variables, and defaults, see the
[challenger configuration reference](/chain-operators/reference/challenger-configuration).
**From the Karst upgrade, `cannon-kona` is the respected fault-proof game type, replacing op-program.** Configure `op-challenger` with the `cannon-kona` trace type and a kona-client absolute prestate. A challenger still running the `cannon` / op-program trace type past Karst will not defend your chain.
**op-geth has reached end-of-support (2026-05-31) and does not support the now-active Karst hardfork, so op-geth nodes can no longer follow the canonical chain.** Migrate to op-reth, the primary supported execution client. See the [op-geth deprecation notice](/notices/archive/op-geth-deprecation) for the full migration plan.
## Prerequisites
### Essential requirements
Before configuring your challenger, complete the following steps:
* L1 contracts deployed with dispute game factory
* Fault proof system active on your chain
* Access to your chain's contract addresses
* [Generate an absolute prestate](/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) for your network version - This is critical as the challenger will refuse to interact with games if it doesn't have the matching prestate
* L1 RPC endpoint (Ethereum, Sepolia, etc.)
* L1 Beacon node endpoint (for blob access)
* L2 archive node with debug API enabled
* Rollup node (op-node) with historical data
* `rollup.json` - Rollup configuration file
* `genesis-l2.json` - L2 genesis file
* `prestate.json` - The absolute prestate file generated in step 1
### Software requirements
* Git (for cloning repositories)
* Go 1.21+ (if building from source)
* Docker and Docker Compose (optional but recommended)
* Access to a funded Ethereum account for challenger operations
### Finding the current stable releases
To ensure you're using the latest compatible versions of OP Stack components, always check the official releases page:
[OP Stack releases page](https://github.com/ethereum-optimism/optimism/releases)
This guide is verified against the following versions:
* **op-challenger** — `op-challenger/v1.9.4` (look for the latest `op-challenger/v*`).
* **op-reth** — `v2.2.5` (look for the latest [op-reth release](https://github.com/ethereum-optimism/optimism/releases?q=op-reth)). op-reth is both the sequencer's execution client and the archive node the challenger reads withdrawal proofs from.
* **kona-client** — the absolute prestate is built from a tagged `kona-client/v*` release (e.g. `kona-client/v1.6.0-rc.1`). Use the tag matching the prestate registered on your chain; for governance-approved upgrades the version is named in the upgrade notice. See the [kona-client prestate tutorial](/chain-operators/tutorials/kona-custom-prestate).
Always check the release notes to ensure you're using compatible versions with your chain's deployment. Using the op-challenger and kona-client versions named in the upgrade notice (or the latest matching releases) is the supported path.
## Software installation
For challenger deployment, you can either build from source (recommended for better control and debugging) or use Docker for a containerized setup.
### Build and configure
Building from source gives you full control over the binaries and is the preferred approach for production deployments.
**Clone and build op-challenger**
```bash theme={null}
# Clone the optimism monorepo
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
# Check out the latest release of op-challenger
git checkout op-challenger/v1.9.4
# Generate the embedded superchain config bundle (initializes the
# superchain-registry submodule; op-challenger embeds this file at compile time)
just build-superchain-go
# Install dependencies and build
just op-challenger
# Build the Cannon VM binary used to run dispute traces
just cannon
# Binaries will be available at ./op-challenger/bin/op-challenger
# and ./cannon/bin/cannon
```
**Build kona-host**
The challenger also needs `kona-host`, the pre-image oracle server for the `cannon-kona` game type. Build it from the kona-client release tag matching the absolute prestate registered on your chain (see [Finding the current stable releases](#finding-the-current-stable-releases)), so the server matches the `kona-client` build committed on chain:
```bash theme={null}
# In a checkout of the tag your prestate was built from
cd rust
cargo build --release --bin kona-host
# Binary will be available at ./rust/target/release/kona-host
```
### Verify installation
Check that you have properly installed the challenger component:
```bash theme={null}
# Make sure you're in the optimism directory
./op-challenger/bin/op-challenger --help
# You should see the challenger help output with available commands and flags
```
## Configuration setup
After building the binaries, create your challenger working directory:
```bash theme={null}
# Create challenger directory (this should be at the same level as optimism directory)
mkdir challenger-node
cd challenger-node
# Create necessary subdirectories
mkdir scripts
mkdir challenger-data
# Verify the optimism directory is accessible
# Directory structure should look like:
# /optimism/ (contains the built binaries)
# /challenger-node/ (your working directory)
```
```bash theme={null}
# Copy configuration files to your challenger directory
# Adjust paths based on your deployment setup
cp /path/to/your/rollup.json .
cp /path/to/your/genesis-l2.json .
```
You'll need to gather several pieces of information before creating your configuration. Here's where to get each value:
**L1 network access:**
* L1 RPC URL: Your L1 node endpoint (Infura, Alchemy, or self-hosted)
* L1 Beacon URL: Beacon chain API endpoint for blob access
**L2 network access:**
* L2 RPC URL: Your op-reth archive node endpoint
* Rollup RPC URL: Your op-node endpoint with historical data
**Challenger wallet:**
* Private key for challenger operations (must be funded)
**Network configuration:**
* Game factory address from your contract deployment
* Network identifier (e.g., op-sepolia, op-mainnet, or custom)
Copy and paste in your terminal, to create your env file.
```bash theme={null}
# Create .env file with your actual values
cat > .env << 'EOF'
# L1 Configuration - Replace with your actual RPC URLs
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
# L2 Configuration - Replace with your actual node endpoints
L2_RPC_URL=http://localhost:8545
ROLLUP_RPC_URL=http://localhost:8547
L1_BEACON=http://sepolia-cl-1:5051
# Wallet configuration - Choose either mnemonic + HD path OR private key
MNEMONIC="test test test test test test test test test test test junk"
HD_PATH="m/44'/60'/0'/0/0"
# PRIVATE_KEY=0xYOUR_ACTUAL_PRIVATE_KEY # Alternative to mnemonic
# Network configuration
NETWORK=op-sepolia
GAME_FACTORY_ADDRESS=0xYOUR_GAME_FACTORY_ADDRESS
# Trace configuration (cannon-kona is the respected game type from the Karst upgrade)
TRACE_TYPE=permissioned,cannon-kona
# Data directory
DATADIR=./challenger-data
# Cannon VM binary (shared by cannon and cannon-kona; built from the optimism repo)
CANNON_BIN=/cannon/bin/cannon
# Configuration files
CANNON_ROLLUP_CONFIG=
CANNON_L2_GENESIS=
# kona executable used as the pre-image oracle server, and the kona-client absolute prestate
CANNON_KONA_SERVER=
CANNON_KONA_PRESTATE=
EOF
```
**Important:** Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values.
* This is the HTTP provider URL for a standard L1 node, can be a full node. `op-challenger` will be sending many requests, so chain operators need a node that is trusted and can easily handle many transactions.
* Note: Challenger has a lot of money, and it will spend it if it needs to interact with games. That might risk not defending games or challenging games correctly, so chain operators should really trust the nodes being pointed at Challenger.
* This is needed just to get blobs from.
* In some instances, chain operators might need a blob archiver or L1 consensus node configured not to prune blobs:
* If the chain is proposing regularly, a blob archiver isn't needed. There's only a small window in the blob retention period that games can be played.
* If the chain doesn't post a valid output root in 18 days, then a blob archiver running a challenge game is needed. If the actor gets pushed to the bottom of the game, it could lose if it's the only one protecting the chain.
* This needs to be an `op-reth` archive node, with `debug` enabled.
* Technically doesn't need to go to bedrock, but needs to have access to the start of any game that is still in progress.
* The withdrawal-proof data the challenger reads via `eth_getProof` is served by op-reth's historical-proofs store. Enable it with `--proofs-history --proofs-history.storage-version v2`, set a persistent `--proofs-history.storage-path`, and size `--proofs-history.window` to cover the dispute game window (≥ 28 days). On permissioned chains, `--rpc.eth-proof-window` bounds how far back `eth_getProof` will serve. See [Running op-reth with historical proofs](/node-operators/tutorials/reth-historical-proofs).
* **Seed the proofs storage once before starting the node with `--proofs-history`**, or op-reth refuses to start (the `proofs-history` ExEx panics with `Proofs storage not initialized`). With the node stopped, run `op-reth proofs init --chain --datadir --proofs-history.storage-path --proofs-history.storage-version v2`. It snapshots the chain's current state to seed the sidecar; the ExEx then indexes forward as the node syncs. Initialize at (or near) genesis so the whole fault-proof window is covered — a node seeded at the current tip only serves proofs for blocks after that point.
* This needs to be an `op-node` archive node because challenger needs access to output roots from back when the games start. See below for important configuration details:
1. Safe Head Database (SafeDB) Configuration for op-node:
* The `op-node` behind the `op-conductor` must have the SafeDB enabled to ensure it is not stateless.
* To enable SafeDB, set the `--safedb.path` value in your configuration. This specifies the file path used to persist safe head update data.
* Example Configuration:
```bash theme={null}
--safedb.path # Replace with your actual path
```
If this path is not set, the SafeDB feature will be disabled.
**Never restore the SafeDB from a snapshot.** The SafeDB records the safe head as derived from L1, and a snapshot may not reflect the actual on-chain derivation state. If the SafeDB is out of sync with L1, `op-challenger` will act on an incorrect safe head and may incorrectly attack valid outputs. `op-challenger` cannot detect this condition — the data it receives appears normal. If you suspect the SafeDB was restored from a snapshot or is otherwise corrupted, delete it and resync from a snapshot that is at least 30 days old — `op-node` does not backfill the SafeDB, so it will only populate it going forward from that point. A 30-day-old snapshot provides enough history to cover the 28-day dispute game window.
2. Ensuring Historical Data Availability:
* Both `op-node` and `op-reth` must have data from the start of the games to maintain network consistency and allow nodes to reference historical state and transactions.
* For `op-node`: Configure it to maintain a sufficient history of blockchain data locally or use an archive node.
* For `op-reth`: Similarly, configure to store or access historical data.
* Example Configuration:
```bash theme={null}
op-node \
--rollup-rpc \
--safedb.path
```
Replace `` with the URL of your archive node and `` with the desired path for storing SafeDB data.
* Chain operators must specify a private key or use something else (like `op-signer`).
* This uses the same transaction manager arguments as `op-node` , batcher, and proposer, so chain operators can choose one of the following options:
* a mnemonic
* a private key
* `op-signer` endpoints
* This identifies the L2 network `op-challenger` is running for, e.g., `op-sepolia` or `op-mainnet`.
* When using the `--network` flag, the `--game-factory-address` will be automatically pulled from the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json).
* When the trace is generated, challenger needs the rollup config and the L2 genesis file. Both files are automatically loaded when a registry `--network` is used, but custom networks must specify both the L2 genesis and rollup config.
* For custom networks not in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json), the `--game-factory-address` and rollup must be specified, as follows:
```bash theme={null}
--cannon-kona-rollup-config rollup.json \
--cannon-kona-l2-genesis genesis-l2.json \
# use this if running challenger outside of the docker image
--cannon-kona-server ./kona \
# version of kona-client deployed on chain
# if you use the wrong one, you will lose the game
# if you deploy your own contracts, you specify the hash, the root of the json file
# OP Mainnet uses tagged versions of kona-client
# build with: just reproducible-prestate-kona
# challenger verifies that onchain
--cannon-kona-prestate ./prestate.json \
# load the game factory address from system config or superchain registry
# point the game factory address at the dispute game factory proxy
--game-factory-address
```
These options vary based on which `--network` is specified. Chain operators always need to specify a way to load prestates and must also specify the `--cannon-kona-server` whenever the docker image isn't being used.
* This is a directory that `op-challenger` can write to and store whatever data it needs. It will manage this directory to add or remove data as needed under that directory.
* If running in docker, it should point to a docker volume or mount point, so the data isn't lost on every restart. The data can be recreated if needed but particularly if challenger has executed cannon as part of responding to a game it may mean a lot of extra processing.
The prestate is effectively the version of `kona-client` that is deployed on chain (run inside the Cannon VM as the `cannon-kona` game type). And chain operators must use the right version. `op-challenger` will refuse to interact with games that have a different absolute prestate hash to avoid making invalid claims. If deploying your own contracts, chain operators must specify an absolute prestate hash taken from the `just reproducible-prestate-kona` command during contract deployment, which will also build the required prestate file.
All governance approved releases use a tagged version of `kona-client`. These can be rebuilt by checking out the version tag and running `just reproducible-prestate-kona`.
* There are two ways to specify the prestate to use:
* `--cannon-kona-prestate`: specifies a path to a single kona-client absolute-prestate file
* `--cannon-kona-prestates-url`: specifies a URL to load prestates from. This enables participating in games that use different prestates, for example due to a network upgrade. The prestates are stored in this directory named by their hash.
* Example final URL for a prestate:
* [https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json](https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json)
* This file contains the cannon memory state.
Challenger will refuse to interact with any games if it doesn't have the matching prestate.
Check this [guide](/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) on how to generate a absolute prestate.
### Create challenger startup script
Create `scripts/start-challenger.sh`:
```bash theme={null}
#!/bin/bash
source .env
# Path to the challenger binary
../optimism/op-challenger/bin/op-challenger \
--trace-type permissioned,cannon-kona \
--l1-eth-rpc=$L1_RPC_URL \
--l2-eth-rpc=$L2_RPC_URL \
--l1-beacon=$L1_BEACON \
--rollup-rpc=$ROLLUP_RPC_URL \
--game-factory-address $GAME_FACTORY_ADDRESS \
--datadir=$DATADIR \
--cannon-bin=$CANNON_BIN \
--cannon-kona-rollup-config=$CANNON_ROLLUP_CONFIG \
--cannon-kona-l2-genesis=$CANNON_L2_GENESIS \
--cannon-kona-server=$CANNON_KONA_SERVER \
--cannon-kona-prestate=$CANNON_KONA_PRESTATE \
--mnemonic "$MNEMONIC" \
--hd-path "$HD_PATH"
```
## Initializing and starting the challenger
### Start the challenger
```bash theme={null}
# Make sure you're in the challenger-node directory
cd challenger-node
# Make script executable
chmod +x scripts/start-challenger.sh
# Start challenger
./scripts/start-challenger.sh
```
### Verify challenger is running
Monitor challenger logs to ensure it's operating correctly:
```bash theme={null}
# Check challenger logs
tail -f challenger-data/challenger.log
# Or if running in foreground, monitor the output
```
The challenger should show logs indicating:
* Successful connection to L1 and L2 nodes
* Loading of prestates and configuration
* Monitoring of dispute games
### Docker setup
The Docker setup provides a containerized environment for running the challenger. This method uses the official Docker image that includes the embedded `kona` server and Cannon executable.
First, create a `.env` file with your configuration values. This file will be used by Docker Compose to set up the environment variables:
```bash theme={null}
# Create .env file with your actual values
cat > .env << 'EOF'
# L1 Configuration - Replace with your actual RPC URLs
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
L1_BEACON=http://sepolia-cl-1:5051
# L2 Configuration - Replace with your actual node endpoints
L2_RPC_URL=http://localhost:8545
ROLLUP_RPC_URL=http://localhost:8547
# Wallet configuration - Choose either mnemonic + HD path OR private key
MNEMONIC="test test test test test test test test test test test junk"
HD_PATH="m/44'/60'/0'/0/0"
# Network configuration
NETWORK=op-sepolia
GAME_FACTORY_ADDRESS=0xYOUR_GAME_FACTORY_ADDRESS
EOF
```
**Important:** Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values.
Each environment variable maps to a specific challenger configuration flag. Here's what each one does:
* This is the HTTP provider URL for a standard L1 node, can be a full node. `op-challenger` will be sending many requests, so chain operators need a node that is trusted and can easily handle many transactions.
* Note: Challenger has a lot of money, and it will spend it if it needs to interact with games. That might risk not defending games or challenging games correctly, so chain operators should really trust the nodes being pointed at Challenger.
* This is needed just to get blobs from.
* In some instances, chain operators might need a blob archiver or L1 consensus node configured not to prune blobs:
* If the chain is proposing regularly, a blob archiver isn't needed. There's only a small window in the blob retention period that games can be played.
* If the chain doesn't post a valid output root in 18 days, then a blob archiver running a challenge game is needed. If the actor gets pushed to the bottom of the game, it could lose if it's the only one protecting the chain.
* This needs to be an `op-reth` archive node, with `debug` enabled.
* Technically doesn't need to go to bedrock, but needs to have access to the start of any game that is still in progress.
* The withdrawal-proof data the challenger reads via `eth_getProof` is served by op-reth's historical-proofs store. Enable it with `--proofs-history --proofs-history.storage-version v2`, set a persistent `--proofs-history.storage-path`, and size `--proofs-history.window` to cover the dispute game window (≥ 28 days). On permissioned chains, `--rpc.eth-proof-window` bounds how far back `eth_getProof` will serve. See [Running op-reth with historical proofs](/node-operators/tutorials/reth-historical-proofs).
* **Seed the proofs storage once before starting the node with `--proofs-history`**, or op-reth refuses to start (the `proofs-history` ExEx panics with `Proofs storage not initialized`). With the node stopped, run `op-reth proofs init --chain --datadir --proofs-history.storage-path --proofs-history.storage-version v2`. It snapshots the chain's current state to seed the sidecar; the ExEx then indexes forward as the node syncs. Initialize at (or near) genesis so the whole fault-proof window is covered — a node seeded at the current tip only serves proofs for blocks after that point.
* This needs to be an `op-node` archive node because challenger needs access to output roots from back when the games start. See below for important configuration details:
1. Safe Head Database (SafeDB) Configuration for op-node:
* The `op-node` behind the `op-conductor` must have the SafeDB enabled to ensure it is not stateless.
* To enable SafeDB, set the `--safedb.path` value in your configuration. This specifies the file path used to persist safe head update data.
* Example Configuration:
```bash theme={null}
--safedb.path # Replace with your actual path
```
If this path is not set, the SafeDB feature will be disabled.
**Never restore the SafeDB from a snapshot.** The SafeDB records the safe head as derived from L1, and a snapshot may not reflect the actual on-chain derivation state. If the SafeDB is out of sync with L1, `op-challenger` will act on an incorrect safe head and may incorrectly attack valid outputs. `op-challenger` cannot detect this condition — the data it receives appears normal. If you suspect the SafeDB was restored from a snapshot or is otherwise corrupted, delete it and resync via consensus sync from a snapshot that is at least 30 days old — `op-node` does not backfill the SafeDB, so it will only populate it going forward from that point. A 30-day-old snapshot provides enough history to cover the 28-day dispute game window.
2. Ensuring Historical Data Availability:
* Both `op-node` and `op-reth` must have data from the start of the games to maintain network consistency and allow nodes to reference historical state and transactions.
* For `op-node`: Configure it to maintain a sufficient history of blockchain data locally or use an archive node.
* For `op-reth`: Similarly, configure to store or access historical data.
* Example Configuration:
```bash theme={null}
op-node \
--rollup-rpc \
--safedb.path
```
Replace `` with the URL of your archive node and `` with the desired path for storing SafeDB data.
* Chain operators must specify a private key or use something else (like `op-signer`).
* This uses the same transaction manager arguments as `op-node` , batcher, and proposer, so chain operators can choose one of the following options:
* a mnemonic
* a private key
* `op-signer` endpoints
* This identifies the L2 network `op-challenger` is running for, e.g., `op-sepolia` or `op-mainnet`.
* When using the `--network` flag, the `--game-factory-address` will be automatically pulled from the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json).
* When the trace is generated, challenger needs the rollup config and the L2 genesis file. Both files are automatically loaded when a registry `--network` is used, but custom networks must specify both the L2 genesis and rollup config.
* For custom networks not in the [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json), the `--game-factory-address` and rollup must be specified, as follows:
```bash theme={null}
--cannon-kona-rollup-config rollup.json \
--cannon-kona-l2-genesis genesis-l2.json \
# use this if running challenger outside of the docker image
--cannon-kona-server ./kona \
# version of kona-client deployed on chain
# if you use the wrong one, you will lose the game
# if you deploy your own contracts, you specify the hash, the root of the json file
# OP Mainnet uses tagged versions of kona-client
# build with: just reproducible-prestate-kona
# challenger verifies that onchain
--cannon-kona-prestate ./prestate.json \
# load the game factory address from system config or superchain registry
# point the game factory address at the dispute game factory proxy
--game-factory-address
```
These options vary based on which `--network` is specified. Chain operators always need to specify a way to load prestates and must also specify the `--cannon-kona-server` whenever the docker image isn't being used.
* This is a directory that `op-challenger` can write to and store whatever data it needs. It will manage this directory to add or remove data as needed under that directory.
* If running in docker, it should point to a docker volume or mount point, so the data isn't lost on every restart. The data can be recreated if needed but particularly if challenger has executed cannon as part of responding to a game it may mean a lot of extra processing.
The prestate is effectively the version of `kona-client` that is deployed on chain (run inside the Cannon VM as the `cannon-kona` game type). And chain operators must use the right version. `op-challenger` will refuse to interact with games that have a different absolute prestate hash to avoid making invalid claims. If deploying your own contracts, chain operators must specify an absolute prestate hash taken from the `just reproducible-prestate-kona` command during contract deployment, which will also build the required prestate file.
All governance approved releases use a tagged version of `kona-client`. These can be rebuilt by checking out the version tag and running `just reproducible-prestate-kona`.
* There are two ways to specify the prestate to use:
* `--cannon-kona-prestate`: specifies a path to a single kona-client absolute-prestate file
* `--cannon-kona-prestates-url`: specifies a URL to load prestates from. This enables participating in games that use different prestates, for example due to a network upgrade. The prestates are stored in this directory named by their hash.
* Example final URL for a prestate:
* [https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json](https://example.com/prestates/0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808.json)
* This file contains the cannon memory state.
Challenger will refuse to interact with any games if it doesn't have the matching prestate.
Check this [guide](/chain-operators/tutorials/absolute-prestate#generating-the-absolute-prestate) on how to generate a absolute prestate.
Create a `docker-compose.yml` file that defines the challenger service:
```yaml theme={null}
version: '3.8'
services:
challenger:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.9.4
user: "1000"
volumes:
- ./challenger-data:/data
- ./rollup.json:/workspace/rollup.json:ro
- ./genesis-l2.json:/workspace/genesis-l2.json:ro
environment:
- L1_RPC_URL=${L1_RPC_URL}
- L1_BEACON=${L1_BEACON}
- L2_RPC_URL=${L2_RPC_URL}
- ROLLUP_RPC_URL=${ROLLUP_RPC_URL}
- MNEMONIC=${MNEMONIC}
- HD_PATH=${HD_PATH}
- NETWORK=${NETWORK}
- GAME_FACTORY_ADDRESS=${GAME_FACTORY_ADDRESS}
command:
- "op-challenger"
- "--l1-eth-rpc=${L1_RPC_URL}"
- "--l1-beacon=${L1_BEACON}"
- "--l2-eth-rpc=${L2_RPC_URL}"
- "--rollup-rpc=${ROLLUP_RPC_URL}"
- "--selective-claim-resolution"
- "--mnemonic=${MNEMONIC}"
- "--hd-path=${HD_PATH}"
- "--network=${NETWORK}"
- "--game-factory-address=${GAME_FACTORY_ADDRESS}"
- "--datadir=/data"
- "--trace-type=cannon-kona"
- "--cannon-kona-prestate=/workspace/prestate-proof.json"
restart: unless-stopped
ports:
- "8548:8548" # If challenger exposes metrics endpoint
```
Start the challenger service and monitor its logs:
```bash theme={null}
# Start the challenger service
docker-compose up -d
# View logs
docker-compose logs -f challenger
```
### Monitoring with op-dispute-mon
Consider running [`op-dispute-mon`](/chain-operators/tools/chain-monitoring#dispute-mon) for enhanced security monitoring:
* Provides visibility into all game statuses for the last 28 days
* Essential for production challenger deployments
## Next steps
* Read the [OP-Challenger Explainer](/op-stack/fault-proofs/challenger) for additional context and FAQ
* Review the detailed [challenger specifications](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html) for implementation details
* If you experience any problems, reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions)
# Proposer Configuration
Source: https://docs.optimism.io/chain-operators/guides/configuration/proposer
Reference for the op-proposer configuration options and the proposer policy constraints.
This page lists all configuration options for op-proposer. The op-proposer posts
output roots (proposals) to L1, making them available for verifiers. Withdrawals to L1 must reference an output root.
If the chain is running permissioned fault proofs, only the [designated proposer](/op-stack/protocol/privileged-roles) can submit output roots.
With [permissionless fault proofs](/op-stack/fault-proofs/explainer), anyone can make a proposal.
## Proposer policy
The proposer policy defines high-level constraints and responsibilities regarding how L2 output roots are posted to L1. Below are the [standard guidelines](/op-stack/protocol/superchain-registry#what-is-a-standard-chain) for configuring the proposer within the OP Stack.
| Parameter | Description | Administrator | Requirement | Notes |
| ---------------- | ----------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------- | --------------------------------------------------------------- |
| Output Frequency | Defines how frequently L2 output roots are submitted to L1 (via the output oracle). | L1 Proxy Admin | **43,200 L2 blocks** (24 hours at 2s block times) or lower | It cannot be set to 0 (there must be some cadence for outputs). |
### Example configuration
```
OP_PROPOSER_L1_ETH_RPC: YOUR_L1_RPC_URL_HERE
OP_PROPOSER_ROLLUP_RPC: YOUR_CHAINS_RPC_URL_HERE
OP_PROPOSER_GAME_FACTORY_ADDRESS: YOUR_CHAINS_GAME_FACTORY_ADDRESS_HERE
OP_PROPOSER_PROPOSAL_INTERVAL: 5h
OP_PROPOSER_WAIT_NODE_SYNC: true
OP_PROPOSER_ALLOW_NON_FINALIZED: "false"
OP_PROPOSER_POLL_INTERVAL: "20s"
OP_PROPOSER_NUM_CONFIRMATIONS: "1"
OP_PROPOSER_SAFE_ABORT_NONCE_TOO_LOW_COUNT: "3"
OP_PROPOSER_RESUBMISSION_TIMEOUT: "30s"
OP_PROPOSER_METRICS_ENABLED: "true"
OP_PROPOSER_METRICS_ADDR: 0.0.0.0
OP_PROPOSER_METRICS_PORT: 7300
```
Higher throughput chains can decrease the `proposal-interval` to allow users submit withdrawals more often.
## Flags
Generated from [`op-proposer/v1.16.3`](https://github.com/ethereum-optimism/optimism/releases/tag/op-proposer%2Fv1.16.3)
flag definitions. 54 flags: 1 required, 53 optional.
### Required flags
| Flag | Description | Environment variable |
| -------------- | ------------------------ | ------------------------ |
| `--l1-eth-rpc` | HTTP provider URL for L1 | `OP_PROPOSER_L1_ETH_RPC` |
### Optional flags
| Flag | Description | Default | Environment variable |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------- |
| `--active-sequencer-check-duration` | The duration between checks to determine the active sequencer endpoint. | `2m0s` | `OP_PROPOSER_ACTIVE_SEQUENCER_CHECK_DURATION` |
| `--allow-non-finalized` | Allow the proposer to submit proposals for L2 blocks derived from non-finalized L1 blocks. | `false` | `OP_PROPOSER_ALLOW_NON_FINALIZED` |
| `--fee-limit-multiplier` | The multiplier applied to fee suggestions to put a hard limit on fee increases | `5` | `OP_PROPOSER_TXMGR_FEE_LIMIT_MULTIPLIER` |
| `--game-factory-address` | Address of the DisputeGameFactory contract | — | `OP_PROPOSER_GAME_FACTORY_ADDRESS` |
| `--game-type` | Dispute game type to create via the configured DisputeGameFactory | `0` | `OP_PROPOSER_GAME_TYPE` |
| `--hd-path` | The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set. | — | `OP_PROPOSER_HD_PATH` |
| `--l2-output-hd-path` | DEPRECATED:The HD path used to derive the l2output wallet from the mnemonic. The mnemonic flag must also be set. | — | `OP_PROPOSER_L2_OUTPUT_HD_PATH` |
| `--log.color` | Color the log output if in terminal mode | `false` | `OP_PROPOSER_LOG_COLOR` |
| `--log.format` | Format the log output. Supported formats: text, terminal, logfmt, logfmtms, json, jsonms | `text` | `OP_PROPOSER_LOG_FORMAT` |
| `--log.level` | The lowest log level that will be output | `INFO` | `OP_PROPOSER_LOG_LEVEL` |
| `--log.pid` | Show pid in the log | `false` | `OP_PROPOSER_LOG_PID` |
| `--metrics.addr` | Metrics listening address | `"0.0.0.0"` | `OP_PROPOSER_METRICS_ADDR` |
| `--metrics.enabled` | Enable the metrics server | `false` | `OP_PROPOSER_METRICS_ENABLED` |
| `--metrics.port` | Metrics listening port | `7300` | `OP_PROPOSER_METRICS_PORT` |
| `--mnemonic` | The mnemonic used to derive the wallets for either the service | — | `OP_PROPOSER_MNEMONIC` |
| `--network-timeout` | Timeout for all network operations | `10s` | `OP_PROPOSER_NETWORK_TIMEOUT` |
| `--num-confirmations` | Number of confirmations which we will wait after sending a transaction | `10` | `OP_PROPOSER_NUM_CONFIRMATIONS` |
| `--poll-interval` | Delay between periodic checks on whether it is time to load an output root and propose it. | `12s` | `OP_PROPOSER_POLL_INTERVAL` |
| `--pprof.addr` | pprof listening address | `"0.0.0.0"` | `OP_PROPOSER_PPROF_ADDR` |
| `--pprof.enabled` | Enable the pprof server | `false` | `OP_PROPOSER_PPROF_ENABLED` |
| `--pprof.path` | pprof file path. If it is a directory, the path is \{dir}/\{profileType}.prof | — | `OP_PROPOSER_PPROF_PATH` |
| `--pprof.port` | pprof listening port | `6060` | `OP_PROPOSER_PPROF_PORT` |
| `--pprof.type` | pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, allocs | — | `OP_PROPOSER_PPROF_TYPE` |
| `--private-key` | The private key to use with the service. Must not be used with mnemonic. | — | `OP_PROPOSER_PRIVATE_KEY` |
| `--proposal-interval` | Interval between submitting L2 output proposals when the dispute game factory address is set | `0s` | `OP_PROPOSER_PROPOSAL_INTERVAL` |
| `--resubmission-timeout` | Duration we will wait before resubmitting a transaction to L1 | `48s` | `OP_PROPOSER_RESUBMISSION_TIMEOUT` |
| `--rollup-rpc` | HTTP provider URL for the rollup node. A comma-separated list enables the active rollup provider. | — | `OP_PROPOSER_ROLLUP_RPC` |
| `--rpc.addr` | rpc listening address | `"0.0.0.0"` | `OP_PROPOSER_RPC_ADDR` |
| `--rpc.enable-admin` | Enable the admin API | `false` | `OP_PROPOSER_RPC_ENABLE_ADMIN` |
| `--rpc.port` | rpc listening port | `8545` | `OP_PROPOSER_RPC_PORT` |
| `--safe-abort-nonce-too-low-count` | Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation | `3` | `OP_PROPOSER_SAFE_ABORT_NONCE_TOO_LOW_COUNT` |
| `--signer.address` | Address the signer is signing requests for | — | `OP_PROPOSER_SIGNER_ADDRESS` |
| `--signer.endpoint` | Signer endpoint the client will connect to | — | `OP_PROPOSER_SIGNER_ENDPOINT` |
| `--signer.header` | Headers to pass to the remote signer. Format `key=value`. Value can contain any character allowed in a HTTP header. When using env vars, split with commas. When using flags one key value pair per flag. | — | `OP_PROPOSER_SIGNER_HEADER` |
| `--signer.tls.ca` | tls ca cert path | `"tls/ca.crt"` | `OP_PROPOSER_SIGNER_TLS_CA` |
| `--signer.tls.cert` | tls cert path | `"tls/tls.crt"` | `OP_PROPOSER_SIGNER_TLS_CERT` |
| `--signer.tls.enabled` | Enable or disable TLS client authentication for the signer | `true` | `OP_PROPOSER_SIGNER_TLS_ENABLED` |
| `--signer.tls.key` | tls key | `"tls/tls.key"` | `OP_PROPOSER_SIGNER_TLS_KEY` |
| `--supernode-rpcs` | HTTP provider URLs for the supernode instances. Multiple URLs can be provided to automatically fail over. | — | `OP_PROPOSER_SUPERNODE_RPCS` |
| `--txmgr.already-published-custom-errs` | List of custom RPC error messages that indicate that a transaction has already been published. | — | `OP_PROPOSER_TXMGR_ALREADY_PUBLISHED_CUSTOM_ERRS` |
| `--txmgr.cell-proof-time` | Enables cell proofs in blob transactions for Fusaka (EIP-7742) compatibility from the provided unix timestamp. Should be set to the L1 Fusaka time. May be left blank for Ethereum Mainnet, Sepolia, Holesky, or Hoodi L1s. | `18446744073709551615` | `OP_PROPOSER_TXMGR_CELL_PROOF_TIME` |
| `--txmgr.fee-limit-threshold` | The minimum threshold (in GWei) at which fee bumping starts to be capped. Allows arbitrary fee bumps below this threshold. | `100` | `OP_PROPOSER_TXMGR_FEE_LIMIT_THRESHOLD` |
| `--txmgr.max-basefee` | Enforces a maximum base fee (in GWei) to assume when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default. | `0` | `OP_PROPOSER_TXMGR_MAX_BASEFEE` |
| `--txmgr.max-retries` | Maximum number of times to resubmit a transaction to L1 on a transient error. Set to 0 to disable retries. | `10` | `OP_PROPOSER_TXMGR_MAX_RETRIES` |
| `--txmgr.max-tip-cap` | Enforces a maximum tip cap (in GWei) to use when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default. | `0` | `OP_PROPOSER_TXMGR_MAX_TIP_CAP` |
| `--txmgr.min-basefee` | Enforces a minimum base fee (in GWei) to assume when determining tx fees. 1 GWei by default. | `1` | `OP_PROPOSER_TXMGR_MIN_BASEFEE` |
| `--txmgr.min-tip-cap` | Enforces a minimum tip cap (in GWei) to use when determining tx fees. 1 GWei by default. | `1` | `OP_PROPOSER_TXMGR_MIN_TIP_CAP` |
| `--txmgr.not-in-mempool-timeout` | Timeout for aborting a tx send if the tx does not make it to the mempool. | `2m0s` | `OP_PROPOSER_TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT` |
| `--txmgr.rebroadcast-interval` | Interval at which a published transaction will be rebroadcasted if it has not yet been mined. Should be less than ResubmissionTimeout to have an effect. | `12s` | `OP_PROPOSER_TXMGR_REBROADCAST_INTERVAL` |
| `--txmgr.receipt-query-interval` | Frequency to poll for receipts | `12s` | `OP_PROPOSER_TXMGR_RECEIPT_QUERY_INTERVAL` |
| `--txmgr.retry-interval` | Duration we will wait before resubmitting a transaction to L1 on a transient error. Values \<= 0 will result in retrying immediately. Should be less than ResubmissionTimeout to have an effect. | `1s` | `OP_PROPOSER_TXMGR_RETRY_INTERVAL` |
| `--txmgr.send-timeout` | Timeout for sending transactions. If 0 it is disabled. | `0s` | `OP_PROPOSER_TXMGR_TX_SEND_TIMEOUT` |
| `--wait-node-sync` | Indicates if, during startup, the proposer should wait for the rollup node to sync to the current L1 tip before proceeding with its driver loop. | `false` | `OP_PROPOSER_WAIT_NODE_SYNC` |
# How to run an Alt-DA mode chain
Source: https://docs.optimism.io/chain-operators/guides/features/alt-da-mode-guide
Learn how to configure and run an Alt-DA mode chain within the OP Stack.
The Alt-DA Mode feature is currently in Beta within the MIT-licensed OP Stack. Beta features are built and reviewed by core contributors, and provide developers with early access to highly requested configurations.
These features may experience stability issues, and we encourage feedback from our early users.
This guide provides a walkthrough for chain operators who want to run an Alt-DA Mode chain. See the [Alt-DA Mode Explainer](/op-stack/features/experimental/alt-da-mode) for a general overview of this OP Stack configuration.
An Alt-DA Mode OP Stack chain enables a chain operator to post and read data to any alternative data availability layer that has built a functioning OP Stack DA Server.
This page includes providers that meet specific [inclusion criteria](#inclusion-criteria), as outlined below.
## Prerequisite
You should use at least the following compatible op\* versions when running your chain.
* op-node/v1.9.1
* op-proposer/v1.9.1
* op-batcher/v1.9.1
* Latest version of op-reth (see the [execution clients guide](/node-operators/guides/configuration/execution-clients))
**op-geth has reached end-of-support (2026-05-31) and does not support the now-active Karst hardfork, so op-geth nodes can no longer follow the canonical chain.** Migrate to op-reth, the primary supported execution client. See the [op-geth deprecation notice](/notices/archive/op-geth-deprecation) for the full migration plan.
DA Servers are not built or maintained by core contributors. DA servers are maintained by third parties and run at your own risk. Please reach out to the team who built the DA Server you are trying to run with any questions or issues.
* Celestia's docs on how to run the [Celestia DA server](https://github.com/celestiaorg/op-plasma-celestia/blob/main/README.md)
* EigenDA's docs on how to run the [EigenDA DA server](https://github.com/Layr-Labs/op-plasma-eigenda/blob/main/README.md)
* Avail's docs on how to run the [AvailDA DA Server](https://docs.availproject.org/docs/build-with-avail/deploy-rollup-on-avail/Optimium)
* 0gDA's docs on how to run the [0gDA DA Server](https://github.com/0glabs/0g-da-op-plasma)
* Near DA's docs on how to run the [Near DA Server](https://github.com/Nuffle-Labs/data-availability/blob/84b484de98f58a91bf12c8abe8df27f5e753f63a/docs/OP-Alt-DA.md)
* Spin up your OP chain as usual but set `--altda.enabled=true` and point both `op-batcher` and `op-node` to the DA server.
* No configuration changes are required for your execution client (`op-reth`) or `op-proposer`.
```
Alt-DA (EXPERIMENTAL)
--altda.da-server value ($OP_NODE_ALTDA_DA_SERVER)
HTTP address of a DA Server
--altda.enabled (default: false) ($OP_NODE_ALTDA_ENABLED)
Enable Alt-DA mode
--altda.verify-on-read (default: true) ($OP_NODE_ALTDA_VERIFY_ON_READ)
Verify input data matches the commitments from the DA storage service
```
* Set `--altda.enabled=true` and `--altda.da-service=true`.
* Provide the URL for `--altda.da-server=$DA_SERVER_HTTP_URL`.
```
--altda.da-server value ($OP_BATCHER_ALTDA_DA_SERVER)
HTTP address of a DA Server
--altda.da-service (default: false) ($OP_BATCHER_ALTDA_DA_SERVICE)
Use DA service type where commitments are generated by the DA server
--altda.enabled (default: false) ($OP_BATCHER_ALTDA_ENABLED)
Enable Alt-DA mode
--altda.verify-on-read (default: true) ($OP_BATCHER_ALTDA_VERIFY_ON_READ)
Verify input data matches the commitments from the DA storage service
```
After completing steps 1-3 above, you will have an Alt-DA mode chain up and running.
* Chain operators are not posting everything to Ethereum, just commitments, so chain operators will need to determine fee scalars values to charge users. The fee scalar values are network throughput dependent, so values will need to be adjusted by chain operators as needed.
* Cost structure for Alt-DA Mode: The transaction data is split up into 128kb chunks and then submitted to your DA Layer. Then, 32 byte commitments are submitted (goes to batch inbox address) to L1 for each 128kb chunk. Then, figure out how much that costs relative to the number of transactions your chain is putting through.
* Set scalar values inside the deploy config. The example below shows some possible fee scalar values, calculated assuming negligible DA Layer costs, but will need to be adjusted up or down based on network throughput - as a reminder of how to set your scalar values, see [this section](/chain-operators/guides/features/blobs#update-your-scalar-values-for-blobs) of the docs.
```
// Set in Deploy Config
"gasPriceOracleBaseFeeScalar": 7663, // Approximate commitment tx base cost
"gasPriceOracleBlobBaseFeeScalar": 0, // blobs aren't used for submitting the small data commitments
```
Some initial scalar values must be set early on in the deploy config in [Step 2](#configure-your-op-node). And then at a later point, chain operators can update the scalar values with an L1 transaction.
## For node operators (full and archive nodes)
* Run a DA server as laid out in [Step 1](#setup-your-da-server)
* Provide the same `--altda.enabled=true, --altda.da-server...` on `op-node` as listed in [Step 2](#configure-your-op-node)
## Inclusion criteria
Alt DA teams who want to be featured on this page must meet the following criteria:
* Functional [DA Server](https://specs.optimism.io/experimental/alt-da.html?utm_source=op-docs\&utm_medium=docs#da-server), maintained in your own repo
* Supporting detailed documentation, to be referenced [here](#setup-your-da-server)
* Functioning OP Stack devnet using your DA server with linked configuration, contract addresses, and RPC address
## Breaking changes: renaming Plasma Mode to Alt-DA Mode
This feature has been renamed from Plasma Mode to Alt-DA Mode in the monorepo at: [0bb2ff5](https://github.com/ethereum-optimism/optimism/commit/0bb2ff57c8133f1e3983820c0bf238001eca119b). There are several breaking changes you should be aware of. These include changes to configuration file parameters, environment variables, and CLI commands.
Before proceeding with the migration, ensure you are using [OP Stack v1.9.1](https://github.com/ethereum-optimism/optimism/releases/tag/v1.9.1) or later.
### Modify `rollup.json` config
Update your `rollup.json` configuration file by replacing the old Plasma config with the new Alt-DA config.
There are two possible formats for the old Plasma config:
### Legacy plasma config
If your config looks like this:
```json theme={null}
"use_plasma": true,
"da_commitment_type": "GenericCommitment",
"da_challenge_contract_address": "0xAAA",
"da_challenge_window": 1000,
"da_resolve_window": 2000,
```
### Recent plasma config
Or if it looks like this:
```json theme={null}
"plasma_config": {
"da_commitment_type": "GenericCommitment",
"da_challenge_contract_address": "0xAAA",
"da_challenge_window": 1000,
"da_resolve_window": 2000
}
```
### New Alt-DA config
Replace either of the above configurations with:
```json theme={null}
"alt_da": {
"da_commitment_type": "GenericCommitment",
"da_challenge_contract_address": "0xAAA",
"da_challenge_window": 1000,
"da_resolve_window": 2000
}
```
Only include fields in the new config that were present in your old config.
## Updating OP Stack runtime config parameters
### CLI parameters
Update the following CLI parameters for both `op-node` and `op-batcher`:
| Former CLI param | Current CLI param |
| ------------------------- | ------------------------ |
| `--plasma.enabled` | `--altda.enabled` |
| `--plasma.da-server` | `--altda.da-server` |
| `--plasma.verify-on-read` | `--altda.verify-on-read` |
| `--plasma.da-service` | `--altda.da-service` |
### Environment variables
#### op-node
| Former env var | Current env var |
| ------------------------------- | ------------------------------ |
| `OP_NODE_PLASMA_ENABLED` | `OP_NODE_ALTDA_ENABLED` |
| `OP_NODE_PLASMA_DA_SERVER` | `OP_NODE_ALTDA_DA_SERVER` |
| `OP_NODE_PLASMA_VERIFY_ON_READ` | `OP_NODE_ALTDA_VERIFY_ON_READ` |
| `OP_NODE_PLASMA_DA_SERVICE` | `OP_NODE_ALTDA_DA_SERVICE` |
#### op-batcher
| Former env var | Current env var |
| ---------------------------------- | --------------------------------- |
| `OP_BATCHER_PLASMA_ENABLED` | `OP_BATCHER_ALTDA_ENABLED` |
| `OP_BATCHER_PLASMA_DA_SERVER` | `OP_BATCHER_ALTDA_DA_SERVER` |
| `OP_BATCHER_PLASMA_VERIFY_ON_READ` | `OP_BATCHER_ALTDA_VERIFY_ON_READ` |
| `OP_BATCHER_PLASMA_DA_SERVICE` | `OP_BATCHER_ALTDA_DA_SERVICE` |
#### op-alt-da (formerly op-plasma) daserver
| Former env var | Current env var |
| ------------------------------------------ | -------------------------------------- |
| `OP_PLASMA_DA_SERVER_ADDR` | `OP_ALTDA_SERVER_ADDR` |
| `OP_PLASMA_DA_SERVER_PORT` | `OP_ALTDA_SERVER_PORT` |
| `OP_PLASMA_DA_SERVER_FILESTORE_PATH` | `OP_ALTDA_SERVER_FILESTORE_PATH` |
| `OP_PLASMA_DA_SERVER_GENERIC_COMMITMENT` | `OP_ALTDA_SERVER_GENERIC_COMMITMENT` |
| `OP_PLASMA_DA_SERVER_S3_BUCKET` | `OP_ALTDA_SERVER_S3_BUCKET` |
| `OP_PLASMA_DA_SERVER_S3_ENDPOINT` | `OP_ALTDA_SERVER_S3_ENDPOINT` |
| `OP_PLASMA_DA_SERVER_S3_ACCESS_KEY_ID` | `OP_ALTDA_SERVER_S3_ACCESS_KEY_ID` |
| `OP_PLASMA_DA_SERVER_S3_ACCESS_KEY_SECRET` | `OP_ALTDA_SERVER_S3_ACCESS_KEY_SECRET` |
After making these changes, your system should be properly configured to use the new Alt-DA Mode.
Remember to thoroughly test your configuration in testnet before going mainnet.
## Next steps
* Additional questions? See the FAQ section in the [Alt-DA Mode Explainer](/op-stack/features/experimental/alt-da-mode#faqs).
* For more detailed info on Alt-DA Mode, see the [specs](https://specs.optimism.io/experimental/alt-da.html?utm_source=op-docs\&utm_medium=docs).
* If you experience any problems, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions).
# Post batch data as blobs
Source: https://docs.optimism.io/chain-operators/guides/features/blobs
Learn how to switch your chain's batcher to posting batch data as blobs.
This guide walks you through how to switch to using blobs for your chain.
This feature was introduced with the Ecotone network upgrade.
## Switch to using blobs
The first step to switching to submit data with Blobs is to calculate the
scalar values you wish to set for the formula to charge users fees.
Your scalar values depend on the average transactions per day your chain is processing, the types of transactions that occur on your chain, the [`OP_BATCHER_MAX_CHANNEL_DURATION`](/chain-operators/guides/configuration/batcher#set-your--op_batcher_max_channel_duration) you have parameterized on your `op-batcher`, and the target margin you wish to charge users on top of your L1 costs.
For a walkthrough of sizing the scalars against your measured batcher spend, see [Recover the spend with fee scalars](/use-cases/tune-batcher-costs#step-5-recover-the-spend-with-fee-scalars) in the Tune batcher costs guide.
For more details on fee scalar, see [Transaction Fees, Ecotone section](/op-stack/transactions/fees#ecotone).
#### Adjust fees to change margins
As a chain operator, you may want to scale your scalar values up or down either because the throughput of your chain has changed and you are either filling significantly more or less of blobs, or because you wish to simply increase your margin to cover operational expenses.
So, to increase or decrease your margin on L1 data costs, you would simply scale both the `l1baseFeeScalar` and the `l1blobBaseFeeScalar` by the same multiple.
For example, if you wished to increase your margin on L1 data costs by \~10%, you would do:
```
newBaseFeeScalar = prevBaseFeeScalar * 1.1
newBlobBaseFeeScalar = prevBlobBaseFeeScalar * 1.1
```
Once you have determined your ideal `BaseFeeScalar` and `BlobBaseFeeScalar`, you will need to apply those values for your chain. The first step is to encode both values into a single value to be set in your L1 Config:
You can set your Scalar Values to send transaction to the L1 SystemConfigProxy.setGasConfigEcotone
```bash theme={null}
cast send \
--private-key $GS_ADMIN_PRIVATE_KEY \
--rpc-url $ETH_RPC_URL \
\
"setGasConfigEcotone(uint32,uint32)" \
```
Check that the gas price oracle on L2 returns the expected values for `baseFeeScalar` and `blobBaseFeeScalar` (wait \~1 minute):
This is checked on L2, so ensure you are using an RPC URL for your chain. You'll also need to provide a `gas-price` to geth when making this call.
```shell theme={null}
cast call 0x420000000000000000000000000000000000000F 'baseFeeScalar()(uint256)' --rpc-url YOUR_L2_RPC_URL
```
```shell theme={null}
cast call 0x420000000000000000000000000000000000000F 'blobBaseFeeScalar()(uint256)' --rpc-url YOUR_L2_RPC_URL
```
Now that the fee config has been updated, you should immediately configure your batcher!
Your chain may be undercharging users during the time between updating the scalar values and updating the Batcher, so aim to do this immediately after.
Steps to configure the batcher:
* Configure `OP_BATCHER_DATA_AVAILABILITY_TYPE=blobs`. The batcher will have to be restarted for it to take effect.
* Ensure your `OP_BATCHER_MAX_CHANNEL_DURATION` is properly set to maximize your fee savings. See [OP Batcher Max Channel Configuration](/chain-operators/guides/configuration/batcher#set-your--op_batcher_max_channel_duration) for more details.
* Optionally, you can configure your batcher to support multi-blobs. See [Multi-Blob Batcher Configuration](/chain-operators/guides/configuration/batcher#configure-your-batcher-to-use-multiple-blobs) for more details.
## Switch back to using calldata
As a chain operator, if the `blobBaseFee` is expensive enough and your chain is
not processing enough transactions to meaningfully fill blobs within your
configured batcher `OP_BATCHER_MAX_CHANNEL_DURATION`, you may wish to switch
back to posting data to calldata. To judge whether your transactions will be cheaper as blobs or as calldata, compare the current blob base fee with the L1 base fee, or set `OP_BATCHER_DATA_AVAILABILITY_TYPE=auto` to let the batcher make the comparison automatically; see [Choose a data availability type](/use-cases/tune-batcher-costs#step-3-choose-a-data-availability-type) in the Tune batcher costs guide for the trade-offs. Chains can follow these steps to switch from
blobs back to using calldata.
If you are using calldata, then you can set your `BaseFeeScalar` similarly to
how you would have set "scalar" prior to Ecotone, though with a 5-10% bump to
compensate for the removal of the "overhead" component.
For a walkthrough of sizing the scalars against your measured batcher spend, see [Recover the spend with fee scalars](/use-cases/tune-batcher-costs#step-5-recover-the-spend-with-fee-scalars) in the Tune batcher costs guide.
Since the Pectra upgrade on L1, chains which exclusively use calldata DA need to scale up their BaseFeeScalar by 10/4. See [this notice](/notices/archive/pectra-changes).
Chains can update their fees to increase or decrease their margin. If using calldata, then `BaseFeeScalar` should be scaled to achieve the desired margin.
For example, to increase your L1 Fee margin by 10%:
```
BaseFeeScalar = BaseFeeScalar * 1.1
BlobBaseFeeScalar = 0
```
To set your scalar values, follow the same process as laid out in [Update your Scalar values for Blobs](#update-your-scalar-values-for-blobs).
Now that the fee config has been updated, you will want to immediately configure your batcher.
Reminder, that your chain may be undercharging users during the time between updating the scalar values and updating the Batcher, so aim to do this immediately after.
* Configure `OP_BATCHER_DATA_AVAILABILITY_TYPE=calldata`. The batcher will have to be restarted for it to take effect.
* Ensure your `OP_BATCHER_MAX_CHANNEL_DURATION` is properly set to maximize savings. **NOTE:** While setting a high value here will lower costs, it will be less meaningful than for low throughput chains using blobs. See [OP Batcher Max Channel Configuration](/chain-operators/guides/configuration/batcher#set-your--op_batcher_max_channel_duration) for more details.
## Use auto DA mode in your batcher
The batcher now supports automatically switching from blobs to calldata depending on which DA type is more affordable. This is an optimization which allows for a slightly better DA profit margin for your chain.
To enable this mode, set `OP_BATCHER_DATA_AVAILABILITY_TYPE=auto`.
## Other considerations
* For information on L1 Data Fee changes related to the Ecotone upgrade, visit the [Transaction Fees page](/op-stack/transactions/fees#ecotone).
* If you want to enable archive nodes, you will need to access a blob archiver service. You can use [Optimism's](/op-mainnet/network-information/snapshots) or [run your own](/chain-operators/tools/explorer#create-an-archive-node).
# Deploy a Custom Gas Token chain
Source: https://docs.optimism.io/chain-operators/guides/features/custom-gas-token-guide
Learn how to deploy a Custom Gas Token chain using OP Deployer.
This guide provides instructions for chain operators who want to deploy a Custom Gas Token (CGT) chain.
See the [Custom Gas Token overview](/op-stack/features/custom-gas-token) for a general understanding of this OP Stack feature.
A Custom Gas Token chain enables you to use any asset as the native fee currency instead of ETH.
This asset may be an existing L1 token, a representation of a token from another chain, or a newly defined asset (or new L2 token) created at genesis.
Custom Gas Token v2 is a new implementation that is not compatible with legacy CGT chains.
There is currently no migration path from legacy CGT to CGT v2, though one is planned to be put together.
## Prerequisites
Before deploying a CGT chain, ensure you have:
* **OP Deployer installed**: See [Install op-deployer](/chain-operators/tools/op-deployer/installation) for setup instructions
* **L1 RPC endpoint**: Access to an Ethereum L1 node for deployment
* **Funded deployer account**: ETH for L1 gas costs during deployment
* **Token design**: Clear plan for your native asset (supply, distribution, bridging mechanism)
* **Security audit**: Thorough review of any authorized minters or bridge contracts you plan to deploy
## Deployment steps
Deploying a CGT chain follows the standard OP Stack deployment process with additional configuration for Custom Gas Token specific parameters.
Create a new intent file using OP Deployer's [init command](/chain-operators/tools/op-deployer/usage/init):
```bash theme={null}
op-deployer init \
--l1-chain-id \
--l2-chain-ids \
--intent-type custom
```
This creates an `intent.toml` file.
Open your `intent.toml` file and add the Custom Gas Token configuration. Below is an example with CGT-specific fields:
```toml theme={null}
configType = "custom"
l1ChainID = 11155111
fundDevAccounts = false
useInterop = false
l1ContractsLocator = "tag://op-contracts/v6.0.0"
l2ContractsLocator = "tag://op-contracts/v6.0.0"
[superchainRoles]
SuperchainProxyAdminOwner = "0xYourMultisigAddress"
SuperchainGuardian = "0xYourMultisigAddress"
Challenger = "0xYourMultisigAddress"
[[chains]]
id = "0x0000000000000000000000000000000000000000000000000000000000001234"
baseFeeVaultRecipient = "0xYourFeeRecipientAddress"
l1FeeVaultRecipient = "0xYourFeeRecipientAddress"
sequencerFeeVaultRecipient = "0xYourFeeRecipientAddress"
operatorFeeVaultRecipient = "0xYourFeeRecipientAddress"
eip1559DenominatorCanyon = 0
eip1559Denominator = 0
eip1559Elasticity = 0
gasLimit = 60000000
operatorFeeScalar = 0
operatorFeeConstant = 0
minBaseFee = 0
daFootprintGasScalar = 0
[chains.roles]
l1ProxyAdminOwner = "0xYourMultisigAddress"
l2ProxyAdminOwner = "0xYourMultisigAddress"
systemConfigOwner = "0xYourMultisigAddress"
unsafeBlockSigner = "0xYourSequencerAddress"
batcher = "0xYourBatcherAddress"
proposer = "0xYourProposerAddress"
challenger = "0xYourChallengerAddress"
[chains.customGasToken]
name = "My Custom Token"
symbol = "MCT"
initialLiquidity = "0x..." # optional, default: type(uint248).max
liquidityControllerOwner = "0x..." # optional, default: L2ProxyAdminOwner
```
### Key CGT configuration fields
| Field | Description | Required |
| -------------------------- | -------------------------------------------------------- | ----------------------------------------------- |
| `name` | Name of the native asset (e.g., "My Custom Token") | Yes |
| `symbol` | Symbol for the native asset (e.g., "MCT") | Yes |
| `initialLiquiditySupply` | Initial supply minted to NativeAssetLiquidity at genesis | No, default: type(uint248).max |
| `liquidityControllerOwner` | Manages the asset supply | No, `L2ProxyAdminOwner` will be used as default |
**Fee parameter calculation is critical**: Your `minBaseFee` and `operatorFee` must accurately account for L1 gas costs and DA fees denominated in your custom token. If set too low, your chain may not cover operational costs. If set too high, users will pay excessive fees.
**Decimal support**: CGT v2 currently supports only 18-decimal tokens. Support for other decimals may be added in future releases.
Deploy your L1 contracts using OP Deployer's [apply command](/chain-operators/tools/op-deployer/usage/apply)
```bash theme={null}
op-deployer apply \
--l1-rpc-url \
--private-key
```
This deploys all necessary L1 contracts including `SystemConfig` with the `isCustomGasToken` flag enabled, which instructs L1 contracts to reject ETH-value transactions.
The deployment process will automatically deploy the CGT-specific predeploys (`NativeAssetLiquidity` and `LiquidityController`) during genesis initialization.
Optionally you can use OP Deployer's [verify command](/chain-operators/tools/op-deployer/usage/verify) to verify your L1 contracts on Blockscout or Etherscan.
After L1 deployment completes, initialize your L2 genesis with OP Deployer's [apply command](/chain-operators/tools/op-deployer/usage/apply):
```bash theme={null}
op-deployer apply \
--deployment-target genesis
```
The genesis configuration is applied based on your `intent.toml`.
Start your OP Stack services to start sequencing your CGT chain:
* Sequencer Execution Client
* Sequencer Consensus Client
* Batcher
* Proposer
* Challenger
Before going live, thoroughly test your CGT chain. The following are some key areas to check:
**Verify flag alignment**
* Check L1 SystemConfig.isCustomGasToken() returns true
* Check L2 L1Block.isCustomGasToken() returns true
* Verify both flags match
**Test native asset operations**
* Test minting native assets via authorized minter
* Test burning native assets
* Test fee payments in native token
* Verify fee vault accumulation
**Test ETH rejection**
* Attempt ETH deposit via OptimismPortal (should fail)
* Attempt ETH withdrawal via L2ToL1MessagePasser (should fail)
* Verify ETH operations are properly blocked
**Test bridge functionality**
* Test deposits (L1 → L2 native asset minting)
* Test withdrawals (L2 native → L1 token)
* Verify proper locking/unlocking in liquidity contract
## Post-deployment considerations
### Supply management
After deployment, you can:
* **Withdraw excess liquidity**: If genesis created more supply than needed, withdraw and burn via `L2ToL1MessagePasser`
* **Add new minters**: Authorize additional contracts to mint native assets as your ecosystem grows
* **Revoke minters**: Remove authorization from compromised or deprecated contracts
* **Implement rate limits**: Add safeguards to control minting velocity
### Fee parameter adjustments
Monitor your chain's operational costs and adjust fee parameters as needed:
* **minBaseFee**: Adjust based on L1 gas costs and your token's value, using `SystemConfig.setMinBaseFee()`
* **operatorFee**: Adjust based on data availability costs, using `SystemConfig.setOperatorFeeScalars()`
### Developer documentation
Create clear documentation for your users covering:
* How to acquire native assets (bridge, DEX, faucet, etc.)
* Bridge contract addresses and interfaces
* Fee structure and token economics
* Wallet configuration (RPC endpoints, chain ID, token metadata)
## Troubleshooting
### Flag mismatch errors
**Symptom**: Transactions failing with "custom gas token mismatch" errors
**Solution**: Verify that `SystemConfig.isCustomGasToken()` on L1 and `L1Block.isCustomGasToken()` on L2 return the same value. If mismatched, this indicates a critical configuration error.
### Fee parameter issues
**Symptom**: Chain operator losing money on transaction costs or users complaining about excessive fees
**Solution**: Review and recalculate your `minBaseFee` and `operatorFee` parameters based on:
* Current L1 gas prices
* Your token's market value or peg
* Data availability costs
* Target fee structure for users
### Liquidity depletion
**Symptom**: Minting transactions failing due to insufficient liquidity
**Solution**:
* Check `NativeAssetLiquidity` balance
* If depleted, this indicates an imbalance between minting and burning
* Review bridge logic to ensure burns are occurring correctly
* Consider increasing initial liquidity supply in future deployments
### Unauthorized minting attempts
**Symptom**: Unauthorized addresses attempting to mint native assets
**Solution**:
* Review access control configuration on `LiquidityController`
* Ensure only audited and secured contracts are authorized
* Implement rate limiting if not already in place
* Consider revoking and re-authorizing with additional safeguards
## Resources
* [Custom Gas Token feature overview](/op-stack/features/custom-gas-token)
* [OP Deployer documentation](/chain-operators/tools/op-deployer/overview)
# Enable span batches
Source: https://docs.optimism.io/chain-operators/guides/features/enable-span-batches
Learn how to enable span batches on your OP Stack chain by confirming Delta activation and configuring the batch type on op-batcher.
Span batches encode a span of consecutive L2 blocks in a single batch, reducing the overhead of batch submission.
This is especially beneficial for sparse, low-throughput chains.
The format was introduced in the Delta network upgrade, and chains opt into it through the `op-batcher` configuration.
For background on what span batches are and why they exist, see [Span batches](/op-stack/features/span-batches).
## Before you begin
Span batches are only valid once the Delta upgrade is active on your chain.
The `op-node` derivation pipeline drops any span batch whose L1 origin predates Delta activation, logging `dropping span batch before Delta activation`.
Do not enable span batches on the batcher before Delta is active on your chain.
Verifier nodes will drop the span batches your batcher posts, so the data you pay to submit will not advance the safe chain.
## Enable span batches
Delta activation is recorded as `delta_time` in your chain's rollup configuration (the `rollup.json` file passed to `op-node`).
Delta is active once `delta_time` is set and the L2 block timestamp has passed that value.
You can read it from the rollup configuration file directly, or query a running `op-node`:
```bash theme={null}
cast rpc --rpc-url optimism_rollupConfig | jq .delta_time
```
If the result is a timestamp in the past, Delta is active.
If it is `null`, Delta is not scheduled on your chain and must be activated first.
For new chains, the `l2GenesisDeltaTimeOffset` deploy configuration parameter schedules Delta relative to genesis (`0` activates it at genesis) — see the [rollup deployment configuration reference](/chain-operators/reference/rollup-deployment-configuration).
For Delta activation times on OP Mainnet and OP Sepolia, see [Network upgrades](/op-stack/protocol/network-upgrades).
The `op-batcher` `--batch-type` flag (environment variable `OP_BATCHER_BATCH_TYPE`) selects the batch format: `0` for singular batches (the default) and `1` for span batches.
Set it to `1` and restart the batcher:
```bash theme={null}
--batch-type=1
```
or, as an environment variable:
```bash theme={null}
OP_BATCHER_BATCH_TYPE=1
```
Optionally, `--max-blocks-per-span-batch` (`OP_BATCHER_MAX_BLOCKS_PER_SPAN_BATCH`) caps the number of L2 blocks added to a single span batch.
The default is `0`, which means no maximum.
See the [batcher configuration reference](/chain-operators/reference/batcher-configuration) for details on both flags.
On startup, the batcher logs its channel configuration.
Check the `Initialized channel-config` log line and confirm it reports `batch_type=1`:
```text theme={null}
INFO [...] Initialized channel-config ... batch_type=1 ...
```
## Revert to singular batches
Singular batches remain valid after Delta, so you can switch back at any time by setting `--batch-type=0` (or unsetting `OP_BATCHER_BATCH_TYPE`, since `0` is the default) and restarting the batcher.
## Next steps
* Learn what span batches are and how the format works in the [span batches explainer](/op-stack/features/span-batches).
* Review the other batcher settings that affect cost and stability in [Configure the batcher](/chain-operators/guides/configuration/batcher).
* Read the [span batches specification](https://specs.optimism.io/protocol/delta/span-batches.html?utm_source=op-docs\&utm_medium=docs) for the full format definition.
# Set the DA Footprint Gas Scalar
Source: https://docs.optimism.io/chain-operators/guides/features/setting-da-footprint
Learn how to set a Data Availability (DA) Footprint on your OP-Stack Chain
## Overview
| Parameter | Type | Default | Recommended | Units |
| --------------------------------------------------------------------------------------------------------------- | ------ | ----------- | ----------- | ------------ |
| `daFootprintGasScalar` | Number | `0` (`400`) | `400` | gas per byte |
The default value of `0` is the same as setting the scalar to `400`. In order to effectively disable this feature, set the scalar to a very low value such as `1`.
See the [specs for more detail](https://specs.optimism.io/protocol/jovian/l1-attributes.html).
The `daFootprintGasScalar` controls the **Data Availability (DA) Footprint Block Limit** introduced in the [Jovian hardfork](https://docs.optimism.io/notices/upgrade-17), which caps the total amount of transaction data that can fit into a block based on a scaled estimate of its compressed size.
The effective limit of estimated DA usage per block is `gasLimit / daFootprintGasScalar` bytes, so *increasing* the scalar *decreases* the limit, and vice versa.
For how the DA footprint is calculated, why the limit exists, and how the default of `400` was chosen, see [How the DA footprint block limit works](/chain-operators/reference/da-footprint).
## Choose a value
* If no limit is set, or the value 0 is set for the `daFootprintGasScalar` in the `SystemConfig`, the default value of `400` is used
* In order to effectively disable this feature, set the scalar to a very low value such as `1`.
* Setting the `daFootprintGasScalar` too high may exclude too many transactions from your blocks.
* Setting the `daFootprintGasScalar` too low may prove ineffective at preventing batcher throttling or protecting against continuous DA heavy transactions.
* When a chain's gas limit is changed, the DA footprint scales proportionally by design. However, if you want to retain the same absolute DA footprint limit, then you must also scale the `daFootprintGasScalar` accordingly.
## Update the `daFootprintGasScalar`
The steps below explain how to update the `daFootprintGasScalar` parameter on-chain using the `SystemConfig` contract.
The [SystemConfig](https://specs.optimism.io/protocol/system-config.html) contract stores configurable protocol parameters such as gas limits and fee settings.\
You can find its proxy address in the [state.json generated by op-deployer](https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-deployer-setup#deploy-l1-contracts).
The [SystemConfig owner](/op-stack/protocol/privileged-roles) is the only address that can make these changes.
To update the `daFootprintGasScalar`, call the following method on the `SystemConfig` contract:
`setDAFootprintGasScalar(uint16 daFootprintGasScalar) external onlyOwner;`
Example (using [cast](https://getfoundry.sh/cast/reference/cast/)) to double the scalar, so lower the effective DA usage limit by half:
```bash title="Cast" theme={null}
cast send -r "setDAFootprintGasScalar(uint16)" 800
```
After the transaction confirms, verify the current value by calling the following getter method on the `SystemConfig` contract:
`function daFootprintGasScalar() external view returns (uint16);`
Example (using [cast](https://getfoundry.sh/cast/reference/cast/)):
```bash title="cast" theme={null}
cast call -r "daFootprintGasScalar()"
```
And on your L2 chain you can query the scalar at the [L1Block predeploy](https://specs.optimism.io/protocol/predeploys.html#l1block) to confirm the new scalar has been propagated to the chain:
```bash title="cast" theme={null}
cast call -r 0x4200000000000000000000000000000000000015 "daFootprintGasScalar()"
```
## Next steps
* [How the DA footprint block limit works](/chain-operators/reference/da-footprint) — the calculation, the scalar's semantics, and the rationale for the default value.
* [Fee parameters reference](/chain-operators/reference/fee-parameters) — the full set of fee-related `SystemConfig` parameters.
## References
* [DA Footprint Configuration Spec](https://specs.optimism.io/protocol/jovian/system-config.html#da-footprint-configuration)
* [Jovian Upgrade Spec](https://specs.optimism.io/protocol/jovian/overview.html)
* [SystemConfig Contract Spec](https://specs.optimism.io/protocol/system-config.html)
* [Design Doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/da-footprint-block-limit.md)
# Set the Minimum Base Fee
Source: https://docs.optimism.io/chain-operators/guides/features/setting-min-base-fee
Learn how to set a minimum base fee on your OP-Stack Chain
## Overview
| Parameter | Type | Default | Recommended | Max Allowed for Standard Chains | Units |
| ----------------------- | ------ | ------------ | ----------- | ------------------------------- | ----- |
| minBaseFee | Number | 0 (disabled) | 100,000 | 10,000,000,000 | wei |
The Minimum Base Fee (`minBaseFee`) configuration was introduced in the [Jovian hardfork](https://docs.optimism.io/notices/upgrade-17).
This feature allows chain operators to specify a minimum L2 base fee to which can help avoid excessively long priority fee auctions that can occur when the base fee falls too low.
When [batcher-sequencer throttling is active](https://docs.optimism.io/chain-operators/guides/configuration/batcher#batcher-sequencer-throttling) for long enough and the minBaseFee isn't enabled (or is zero), the base fee can drop all the way down to 1 wei. It can take a long time to recover back to a stable base fee.
The steps below explain how to update the `minBaseFee` parameter on-chain using the SystemConfig contract.
Setting the `minBaseFee` too high may make transactions harder to include for users.
## How to Update the Minimum Base Fee
The [SystemConfig](https://specs.optimism.io/protocol/system-config.html) contract stores configurable protocol parameters such as gas limits and fee settings.\
You can find its proxy address in the [state.json generated by op-deployer](https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-deployer-setup#deploy-l1-contracts).
Note that the owner of the `SystemConfig` contract is the [System Config Owner address](/op-stack/protocol/privileged-roles), so this transaction must be sent from that address.
To update the minimum base fee, call the following method on the `SystemConfig` contract:
`setMinBaseFee(uint64 minBaseFee) external onlyOwner;`
Example (using [cast](https://getfoundry.sh/cast/reference/cast/)):
```bash title="cast" theme={null}
cast send "setMinBaseFee(uint64)" 100000
```
After the transaction confirms, verify the current value by calling the following getter method on the `SystemConfig` contract:
`function minBaseFee() external view returns (uint64);`
Example (using [cast](https://getfoundry.sh/cast/reference/cast/)):
```bash title="cast" theme={null}
cast call "minBaseFee()"
```
***
## References
* [Minimum Base Fee Configuration Spec](https://specs.optimism.io/protocol/jovian/system-config.html#minimum-base-fee-configuration)
* [Jovian Upgrade Spec](https://specs.optimism.io/protocol/jovian/overview.html)
* [SystemConfig Contract Spec](https://specs.optimism.io/protocol/system-config.html)
* [Design Doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/minimum-base-fee.md)
# Set the Operator Fee
Source: https://docs.optimism.io/chain-operators/guides/features/setting-operator-fee
Learn how to configure the operator fee on your OP Stack Chain
## Overview
| Parameter | Default | Recommended | Type |
| :-------------------- | :-----: | :------------- | :---------------------------- |
| `operatorFeeScalar` | 0 | Chain-specific | uint32 scalar (scaled by 1e6) |
| `operatorFeeConstant` | 0 | Chain-specific | uint64 scalar (wei) |
The **Operator Fee** was introduced in the **[Isthmus upgrade](https://docs.optimism.io/notices/upgrade-14#operator-fee)** and modified in the **[Jovian upgrade](https://docs.optimism.io/notices/upgrade-17#breaking-changes)**.\
It allows OP Stack chain operators to charge an additional fee on transactions, on top of the **execution gas fee** (base fee + priority fee) and the **L1 data fee**.
This mechanism gives operators two levers:
* a **gas-proportional component** (`operatorFeeScalar`) that scales with gas used
* a **flat component** (`operatorFeeConstant`)
The operator fee is only applied on chains that have enabled the Isthmus upgrade.
**Deposit transactions do not get charged operator fees.**\
For all deposit transactions, regardless of the operator fee configuration, the operator fee is always zero.
***
## Operator Fee Formula
The operator fee is calculated using the following formula, depending on the active fork.
**After Isthmus:**
```text theme={null}
operatorFee = operatorFeeConstant + (operatorFeeScalar * gasUsed / 1e6)
```
**After Jovian:**
```text theme={null}
operatorFee = operatorFeeConstant + (operatorFeeScalar × gasUsed × 100)
```
Setting operator fee values too high can significantly increase transaction costs and reduce user adoption.
The operator fee directly impacts UX and competitiveness and should be adjusted conservatively.
The default value for standard chains is 0, any other value is considered non-standard.
***
## How to Update the Operator Fee
To update the operator fee parameters, call the following method on the `SystemConfig` contract from the `SystemConfigOwner`:
`setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) external onlyOwner;`
Example using cast:
```bash title="cast" theme={null}
cast send --rpc-url --private-key \
\
"setOperatorFeeScalars(uint32,uint64)" \
500000 1000000000000
```
After the transaction confirms, verify the current configuration by calling the following:
`function operatorFeeScalar() view returns (uint32);`\
`function operatorFeeConstant() view returns (uint64);`
Example using cast:
```bash title="cast" theme={null}
cast call --rpc-url "operatorFeeScalar()"
cast call --rpc-url "operatorFeeConstant()"
```
***
## References
* [Operator Fee Overview – OP Stack Fee Docs](https://docs.optimism.io/op-stack/transactions/fees#operator-fee-component)
* [SystemConfig Operator Fee Parameter Configuration – OP Stack Specs](https://specs.optimism.io/protocol/isthmus/system-config.html#operator-fee-parameter-configuration)
* [Isthmus Operator Fee Details – OP Stack Specs](https://specs.optimism.io/protocol/isthmus/exec-engine.html#operator-fee)
# Using snap sync for chain operators
Source: https://docs.optimism.io/chain-operators/guides/features/snap-sync
Learn how to enable snap sync on your OP Stack chain.
Snap sync significantly improves the experience of syncing an OP Stack node. On the consensus layer, `op-node` enables it with the `--syncmode=execution-layer` flag.
Rather than re-executing every block from genesis, the execution client downloads chain and state data from other nodes on the network over P2P and begins executing from the completed state.
This means that performing a snap sync is significantly faster than performing a full sync.
* Snap sync enables node operators on your network to sync faster.
* Snap sync removes the need for nodes on your post Ecotone network to run a [blob archiver](/node-operators/guides/management/blobs).
## Enable snap sync for chains
To enable snap sync, chain operators need to spin up a node which is exposed to the network and has transaction gossip disabled.
This node will serve snap sync requests on the execution layer from other nodes on the network.
For snap sync, all nodes should expose port `30303` TCP and `30303` UDP to easily find other nodes to sync from. These are op-reth's defaults for `--port` (TCP) and `--discovery.port` (UDP).
* If you set the port with [`--discovery.port`](/node-operators/op-reth/cli/op-reth/node), then you must open the port specified for UDP.
* If you set [`--port`](/node-operators/op-reth/cli/op-reth/node), then you must open the port specified for TCP.
* The only exception is for sequencers and transaction ingress nodes.
* Expose port `30303` (op-reth's default listening and discovery port) to the internet on TCP and UDP.
* Disable transaction gossip with the [`--rollup.disable-tx-pool-gossip`](/node-operators/op-reth/cli/op-reth/node) flag
* See the [sync modes reference](/node-operators/reference/consensus-layer-sync) for how node operators enable snap sync (execution-layer sync) on your chain network.
# Enabling Subblocks
Source: https://docs.optimism.io/chain-operators/guides/features/subblocks-guide
Learn about enabling Subblocks on an OP Stack chain.
Subblocks replace Flashblocks. Flashblocks are no longer officially supported on OP Stack chains.
Subblocks are a private feature available only to OP Enterprise customers. If you are interested in becoming an OP Enterprise customer, [contact the OP Enterprise team](https://optimism.io/learn-more). See [this explainer on Subblocks](/op-stack/features/subblocks) for more details.
# Switch to Kona Proofs
Source: https://docs.optimism.io/chain-operators/guides/features/switching-to-kona-proofs
Learn how to switch your OP Stack chain to use Kona-based fault proofs as the respected game type.
[Upgrade 18](/notices/archive/upgrade-18) (approved by Optimism Governance) introduced `CANNON_KONA` (8) as an available game type alongside `CANNON` (0). [Upgrade 19](/notices/archive/upgrade-19) promotes `CANNON_KONA` to the **respected game type** for chains managed by the Optimism Security Council — meaning it is applied automatically as part of the onchain upgrade. Non-managed chains that want to switch must follow this guide.
## Overview
| Parameter | Type | Current (typical) | Target when switching | Notes |
| ----------------------- | ------- | ----------------- | --------------------- | --------------------------------------------------- |
| Respected game type | Enum | `CANNON` (0) | `CANNON_KONA` (8) | Determines which game type is used for withdrawals |
| `OP_PROPOSER_GAME_TYPE` | Number | 0 | 8 | Proposer game type (must match respected game type) |
| `cannonPrestate` | Bytes32 | Set | Set | Must be a valid `cannon64` prestate hash |
| `cannonKonaPrestate` | Bytes32 | Set | Set | Must be a valid `cannon64-kona` prestate hash |
Kona proofs use the `kona-client` fault proof program, which is a combination of `kona-node` and `op-reth`. It replaces `op-program` (a combination of `op-node` and `op-geth`), which has reached end-of-support and does not support the Karst hardfork. See [End of Support for op-geth and op-program](/notices/archive/op-geth-deprecation). Both programs:
* Use Cannon as the FPVM, and
* Are used by the same dispute game implementation (`FaultDisputeGame.sol`).
After the cannon+kona upgrade is deployed, both game types are available:
* `CANNON` (0): `op-program` (end-of-support)
* `CANNON_KONA` (8): `kona-client`
Until you switch, the respected game type remains `CANNON` (0), meaning only those games are used for withdrawals. Because `op-program` does not support Karst, complete this switch before activating the Karst hardfork on your chain. This guide explains how to switch your chain so that Kona proofs (`CANNON_KONA`, 8) become the respected game type.
This guide assumes you have already completed the cannon+kona upgrade, including setting both `cannonPrestate` and `cannonKonaPrestate` in `OpChainConfig`, and that your fault proof infrastructure is healthy.
The high-level flow is:
* Verify that cannon+kona support is correctly deployed on-chain.
* Confirm that your off-chain infra (especially `op-challenger`) is Kona-ready.
* Switch the respected game type to `CANNON_KONA` (8).
* Update `op-proposer` to post Kona games.
* Monitor the system.
***
## How to Switch to Kona Proofs
Before changing the respected game type, verify that your chain has been upgraded to support both `CANNON` and `CANNON_KONA` games.
The required upgrade is performed via `OPCM.upgrade` with an `OpChainConfig` that sets both `cannonPrestate` and `cannonKonaPrestate`.
1. **Check `OpChainConfig` prestates**
Ensure that the `OpChainConfig` used in your most recent upgrade included:
* `cannonPrestate` pointing to a valid `cannon64` prestate.
* `cannonKonaPrestate` pointing to a valid `cannon64-kona` prestate.
Both hashes must come from the standard prestates in the superchain registry and must embed an up-to-date chain config for your chain.
2. **Verify `DisputeGameFactory` implementations**
After the cannon+kona upgrade, the `DisputeGameFactory` should have:
* A non-zero implementation for `CANNON` (0), and
* A non-zero implementation for `CANNON_KONA` (8).
Example (using [cast](https://getfoundry.sh/cast/reference/cast/)):
```bash title="cast" theme={null}
# CANNON (0) implementation
cast call "gameImpl(uint8)" 0
# CANNON_KONA (8) implementation
cast call "gameImpl(uint8)" 8
```
Both calls should return non-zero addresses.
Your challengers must be able to play both cannon and kona games before you change the respected game type.
1. **Trace types**
If you explicitly configure trace types, ensure that `cannon-kona` is included. A typical setting is:
```bash theme={null}
OP_CHALLENGER_TRACE_TYPE="cannon,cannon-kona,permissioned"
# or
--trace-type=cannon,cannon-kona,permissioned
```
2. **Prestate URLs**
Make sure `op-challenger` can fetch both cannon and cannon-kona prestates:
* If they are at the same URL, use:
```bash theme={null}
--prestates-url=
```
* If they are at different locations, use:
```bash theme={null}
--cannon-prestates-url=
--cannon-kona-prestates-url=
```
3. **Kona host binary**
For operators not using the standard OP Labs `op-challenger` Docker images, you must also provide:
```bash theme={null}
--cannon-kona-server=/path/to/kona-host
# or
OP_CHALLENGER_CANNON_KONA_SERVER=/path/to/kona-host
```
The `kona-host` binary is available from the Kona repository (for example under `bin/host`). Build it from the same release as the `cannon-kona` prestate you configured.
Once both on-chain and off-chain pieces are ready, you can switch the respected game type so that Kona proofs become the canonical path for withdrawals.
Changing the respected game type affects which games can be used to prove withdrawals. Only perform this step once you are confident in your Kona setup.
The respected game type is configured in the `AnchorStateRegistry` contract.
1. **Encode the calldata**
Use `cast calldata` to encode a call to `setRespectedGameType(uint32)` with game type `8` (which corresponds to `CANNON_KONA`):
```bash title="cast" theme={null}
CALLDATA=$(cast calldata "setRespectedGameType(uint32)" 8)
```
2. **Send the transaction**
Send the transaction from the Guardian to the `AnchorStateRegistry`:
```bash title="cast" theme={null}
cast send \
--rpc-url \
--private-key \
\
"$CALLDATA"
```
3. **Verify the new respected game type**
After the transaction confirms, verify that the respected game type is now `8`:
```bash title="cast" theme={null}
cast call \
--rpc-url \
\
"respectedGameType()(uint32)"
```
The returned value should be `8`, indicating that `CANNON_KONA` is now the respected game type.
With `CANNON_KONA` set as the respected game type, your proposers must create Kona games.
1. **Update the proposer game type**
Change your `op-proposer` configuration from game type 0 to 8:
```bash theme={null}
# Before
OP_PROPOSER_GAME_TYPE=0
# or
--game-type=0
# After switching to Kona
OP_PROPOSER_GAME_TYPE=8
# or
--game-type=8
```
This change is not required as part of the initial cannon+kona upgrade; it is only required when you actually switch the respected game type to `CANNON_KONA`.
2. **Restart proposers**
Restart your proposer processes so they pick up the new configuration. Verify in logs and metrics that they are now using game type 8.
After switching to Kona proofs, closely monitor your chain to ensure everything is functioning as expected.
1. **Run test withdrawals**
* Perform a few small test withdrawals and ensure that:
* New fault dispute games are created with game type `CANNON_KONA` (8).
* Your challengers are responding correctly using `kona-host`.
2. **Observe metrics and logs**
* Monitor `op-proposer` and `op-challenger` logs for errors.
* Watch dispute game metrics (for example, game counts and step timings) for anomalies.
***
## References
* [OP Stack Fault Proofs Explainer](https://docs.optimism.io/op-stack/fault-proofs/explainer)
* [Fault Proof Specs](https://specs.optimism.io/fault-proof/index.html)
# Join the Superchain Registry
Source: https://docs.optimism.io/chain-operators/guides/join-superchain-registry
How to add your OP Stack chain to the Superchain Registry — prerequisites, config generation, and the pull-request process.
The [Superchain Registry](https://github.com/ethereum-optimism/superchain-registry)
is the source-of-truth index of who is in the OP Stack Ecosystem and how each
chain is configured. This guide walks you through adding your chain. The
registry repo's
[operations documentation](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md)
is the canonical reference for every step below — each step links the matching
section, and if this page and that document ever disagree, the registry repo
wins.
Joining gets you more than a listing: once your chain is in the registry, its
config is embedded in downstream releases of `op-node` and `op-geth`, so nodes
can join your network with the `--network` / `--op-network` flags and can
inherit Superchain-wide hardfork activations automatically. See the
[superchain-registry explainer](/op-stack/protocol/superchain-registry) for how
that works.
## Before You Start
* **Chat with the Optimism Foundation.**
* **Your chain ID must be registered at
[ethereum-lists/chains](https://github.com/ethereum-lists/chains).** The
registry's validation suite checks against it; this is a mandatory
prerequisite
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#adding-a-chain)).
* **Standard chains must be deployed with
[op-deployer](/chain-operators/tools/op-deployer/overview).** A chain is
only considered standard in the registry if it was deployed with
op-deployer, which also produces the `state.json` file the registry
tooling consumes
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#0-standard-chains-deploy-your-chain-with-op-deployer)).
Chains deployed another way follow the [custom-chain path](#custom-chains)
below.
* **Install the tooling dependencies:** `git` (`^2`), `go` (`^1.21`), and
[`just`](https://github.com/casey/just) (`^1.28.0`)
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#1-install-dependencies)).
## Add Your Chain
You will raise a pull request from your fork to the upstream repo. Start
from a fresh, non-protected branch (not your fork's `main`), and add one
chain per branch and PR.
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#2-fork-this-repository))
From the root of the registry repo, run:
```bash theme={null}
just create-config
```
where `` is your chain's short name (for example `op`) and the
state file is the `state.json` produced by op-deployer. This writes two
files to the `.staging` directory: `.toml` and
`.json.zst`. If you deployed with custom contracts, pass your
op-deployer version as a third argument — supported values live in the
registry's `versions.json`.
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#3-generate-a-config-from-your-state-file))
Most of `.toml` is populated from your deployer state, but you
must fill in the rest yourself: `name`, `superchain` (`mainnet` or
`sepolia`), `public_rpc`, `sequencer_rpc`, `explorer`, and
`deployment_tx_hash`. Double-check the generated values while you are in
the file.
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#4-update-generated-config))
Commit to your fork and open one PR per chain. Check the box to
[allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork)
so reviewers can push to your branch. Automated checks will validate your
chain and generate a report on its compliance with the standard
blockchain charter.
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#5-commit))
A registry maintainer reviews your PR. When it is ready, the team
generates code from your PR and pushes to your fork — do not run codegen
yourself; it slows down review.
([ops.md](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#6-await-review))
## Custom Chains
If your chain was not deployed with op-deployer (for example, a heavily
modified deployment), you must write the config file by hand instead of
generating it: copy the example config into `.staging`, substitute every field
for your chain (custom chains set `superchain_level = 0` and
`governed_by_optimism = false`), and ZST-encode your genesis file with the
registry's dictionary. The full field-by-field walkthrough lives in
[Adding a custom chain](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md#adding-a-custom-chain).
## After You Are In
To receive Superchain-wide coordinated hardfork activations automatically, set
`superchain_time` in your chain's config (use `0` if your chain has activated
all Superchain forks up to its genesis time) and run your nodes with the
network flags. Details in the
[explainer](/op-stack/protocol/superchain-registry#hard-fork-activation-inheritance-behavior)
and the registry's
[hardfork activation inheritance spec](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/hardfork-activation-inheritance.md).
## Next Steps
* [The superchain-registry explainer](/op-stack/protocol/superchain-registry)
* [Deploy your chain with op-deployer](/chain-operators/tools/op-deployer/overview)
* [Registry operations documentation](https://github.com/ethereum-optimism/superchain-registry/blob/main/docs/ops.md) (canonical)
# Chain operator best practices
Source: https://docs.optimism.io/chain-operators/guides/management/best-practices
Learn some best practices for managing the OP Stack's off-chain components.
The following information has some best practices around running the OP Stack's
off-chain components.
## Correct release versions
Chain and node operators should always run the latest production releases of the OP Stack's off-chain components. The latest notes and changelogs can be found on GitHub:
* [OP monorepo releases](https://github.com/ethereum-optimism/optimism/releases) (includes `op-node` and other Go-based OP components)
* [op-geth releases](https://github.com/ethereum-optimism/op-geth/releases) (standalone repository)
* [op-contracts releases](https://github.com/ethereum-optimism/optimism/releases) (look for tags starting with `op-contracts/`)
Some guidelines when picking versions:
* **Production releases** are always tagged with `/v` for a specific OP component, for example:
* `op-node/v1.7.5` for the `op-node`
* `op-challenger/v1.0.0` for the `op-challenger`
* **Monorepo releases** typically use a simple `v` format (e.g. `v1.7.7`) to indicate that all Go-based OP Stack components (in `op-*`) have been updated together. These do **not** include new L1 contract releases.
* **Contracts releases** are tagged under `op-contracts/v` (e.g. `op-contracts/v1.6.0`) and contain updates to the Bedrock L1 contracts.
* **`op-geth` versioning** includes upstream geth's version within its semver. For example, if upstream geth is at `v1.12.0`, an `op-geth` release might be `v1.101200.0`. The geth major version is used as our minor version (left-padded if needed), and the patch version is appended.
Always consult release notes for additional details or upgrade instructions.
## Keep deployment artifacts
After deploying your contracts on Ethereum, you should keep a record of all the deployment artifacts.
This is will be all the [op-deployer](/chain-operators/tools/op-deployer/overview.mdx) artifacts, as well as the release tag and commit hash of `op-deployer` and `op-contracts`.
You will need these artifacts to add your chain to the [Superchain Registry](/op-stack/protocol/superchain-registry).
## Incremental upgrade rollouts
When upgrading your nodes, take a staggered approach. This means deploying the
upgrade gradually across your infrastructure and ensuring things work as
expected before making changes to every node.
## Isolate your sequencer
You can isolate your sequencer node, by not connecting it directly to the
internet. Instead, you could handle your ingress traffic behind a proxy. Have
the proxy forward traffic to replicas and have them gossip the transactions
internally.
## Improve reliability of peer-to-peer transactions
These flags can improve the reliability of peer-to-peer transactions from internal replica nodes and the sequencer node.
For sequencer nodes:
```
GETH_TXPOOL_JOURNAL: ""
GETH_TXPOOL_JOURNALREMOTES: "false"
GETH_TXPOOL_NOLOCALS: "true"
```
For replica nodes:
```
GETH_TXPOOL_JOURNALREMOTES: "true"
GETH_TXPOOL_LIFETIME: "1h"
GETH_TXPOOL_NOLOCALS: "true"
```
For additional information about these flags, check out our [Execution Layer Configuration Options](/node-operators/reference/op-reth-config) doc.
## Write your own runbooks
Create custom runbooks to prepare for operating an OP Stack chain.
You'll want to setup metrics endpoints and monitoring on your key components of the system and create runbooks to execute when something is not behaving as expected.
## Assumptions
### op-proposer assumes archive mode
The `op-proposer` currently assumes that `op-geth` is being run in archive
mode. This will likely be updated in a future network upgrade, but it is
necessary for L2 withdrawals at the moment.
**Running this in production**
# Fee vault operations
Source: https://docs.optimism.io/chain-operators/guides/management/fee-vaults
How to configure, monitor, and withdraw from fee vaults on your OP Stack chain.
This guide covers the operational aspects of managing fee vaults on your OP Stack chain. For a conceptual overview of how fee vaults work, see [Fee vaults](/op-stack/transactions/fee-vaults).
## Prerequisites
* Access to the **ProxyAdminOwner** account (required for configuration changes)
* An RPC endpoint for your L2 chain
* `cast` CLI tool installed ([Foundry](https://book.getfoundry.sh/getting-started/installation))
## Fee vault addresses
| Vault | Address | Collects | Introduced |
| ------------------- | -------------------------------------------- | ---------------------------- | ---------- |
| `SequencerFeeVault` | `0x4200000000000000000000000000000000000011` | Priority fees (tips) | Legacy |
| `BaseFeeVault` | `0x4200000000000000000000000000000000000019` | Base fees (not burned on L2) | Bedrock |
| `L1FeeVault` | `0x420000000000000000000000000000000000001A` | L1 data fees | Bedrock |
| `OperatorFeeVault` | `0x420000000000000000000000000000000000001B` | Operator fees | Isthmus |
## Checking vault state
### View current balance
```bash theme={null}
export L2_RPC=
export VAULT=
cast balance $VAULT --rpc-url $L2_RPC
```
### View configuration
```bash theme={null}
# Recipient address
cast call $VAULT "recipient()" --rpc-url $L2_RPC
# Minimum withdrawal amount (in wei)
cast call $VAULT "minWithdrawalAmount()" --rpc-url $L2_RPC
# Withdrawal network (0 = L1, 1 = L2)
cast call $VAULT "withdrawalNetwork()" --rpc-url $L2_RPC
# Total ETH withdrawn historically
cast call $VAULT "totalProcessed()" --rpc-url $L2_RPC
```
## Updating configuration
Configuration changes require the **ProxyAdminOwner** account. If your chain uses a multisig as the ProxyAdminOwner, these calls must be executed through the multisig.
### Set recipient
```bash theme={null}
export L2_RPC=
export VAULT=
export PK=
cast send --rpc-url $L2_RPC --private-key $PK \
$VAULT "setRecipient(address)"
```
### Set minimum withdrawal amount
```bash theme={null}
cast send --rpc-url $L2_RPC --private-key $PK \
$VAULT "setMinWithdrawalAmount(uint256)"
```
### Set withdrawal network
```bash theme={null}
# 0 = L1, 1 = L2
cast send --rpc-url $L2_RPC --private-key $PK \
$VAULT "setWithdrawalNetwork(uint8)" <0_OR_1>
```
## Withdrawing fees
Withdrawals are **permissionless** — anyone can trigger them once the vault balance meets the minimum threshold.
### Trigger a withdrawal
```bash theme={null}
cast send --rpc-url $L2_RPC --private-key $PK \
$VAULT "withdraw()"
```
This withdraws the **entire vault balance** to the configured recipient.
### Withdrawal behavior by network
* **L2 withdrawal (`withdrawalNetwork = 1`)**: Funds are transferred immediately to the recipient on L2. The transaction completes in a single step.
* **L1 withdrawal (`withdrawalNetwork = 0`)**: An L2-to-L1 withdrawal is initiated via the `L2ToL1MessagePasser`. After calling `withdraw()`, you must still:
1. Wait for the withdrawal to be included in an L2 output root
2. Prove the withdrawal on L1
3. Wait for the finalization period
4. Finalize the withdrawal on L1
For details on completing L1 withdrawals, see [Withdrawal flow](/op-stack/bridging/withdrawal-flow).
## Initial configuration during deployment
Fee vault recipients, minimum withdrawal amounts, and withdrawal networks are configured during chain deployment with `op-deployer`. For details on setting these parameters, see the [op-deployer setup guide](/chain-operators/tutorials/create-l2-rollup/op-deployer-setup).
## Monitoring
It's good practice to monitor your fee vaults regularly:
* **Vault balances**: Track balances to know when withdrawals can be triggered.
* **`totalProcessed`**: Monitor cumulative withdrawals over time to understand revenue trends.
## Next steps
* Read the [fee vaults explainer](/op-stack/transactions/fee-vaults) to understand how vaults work at the protocol level.
* See [Transaction Fees 101](/chain-operators/guides/management/transaction-fees-101) to learn how to tune fee parameters.
* Review [fee components and formulas](/op-stack/transactions/fees) for detailed fee calculations.
# Changing gas target/limit
Source: https://docs.optimism.io/chain-operators/guides/management/gas-target-limit
Learn how to change the gas target and limit on your OP Stack chain.
This guide covers how to optimize block processing performance at higher gas limits, test your infrastructure before activation, and safely change the gas target and/or limit on a live chain.
This guide assumes your OP Stack chain has the [Holocene features](/notices/archive/holocene-changes) activated.
## Recommended config for best performance
Some known ways to improve block processing performance:
* **Use NVMe for storage** instead of local SSDs or other persistent disks
* **Use full nodes where possible** over archive nodes
## Pre-activation testing
To ensure your chain and supporting infrastructure can consistently process blocks up to your gas limit, you should run some performance tests.
You'll want to run benchmarks and perform careful analysis of the results to ensure your execution clients can handle the increased gas usage.
## Changing the gas target/limit change
How to change the gas target/limit on a live chain.
### Relevant configuration parameters
The gas configuration on an OP Stack chain use the same EIP-1559 parameters as Ethereum.
Where the **gas target** = **gas limit** / **elasticity**.
These values can be found in the `SystemConfig` contract.
### Procedure
Holocene introduces the ability to change the EIP-1559 parameters `elasticity` and `denominator` via the `SystemConfig`. This will allow you to set proper values for the `elasticity` and block `gasLimit`.
**Example**: to double the *target* while keeping the block *limit* at 30Mgas/block:
Retrieve the existing `eip1559Elasticity` and `eip1559Denominator` values:
```solidity theme={null}
elasticity = SystemConfig.eip1559Elasticity() // 6
denominator = SystemConfig.eip1559Denominator() // 50
limit = SystemConfig.gasLimit() // 30_000_000 gas, 30Mgas
```
Current target = `30Mgas / 6 = 5Mgas per block`
Use value from previous step as input to set new params and reduce the `elasticity` from `6` to `3`:
```solidity theme={null}
SystemConfig.setEIP1559Params(denominator, 3)
```
New target = `30Mgas / 3 = 10Mgas per block`
## Post-activation monitoring
What to monitor after executing the gas changes.
### SystemConfig contract values
* `SystemConfig.gasLimit()`
* `SystemConfig.eip1559Denominator()`
* `SystemConfig.eip1559Elasticity()`
### Block explorer
For example, on [OP Mainnet](https://optimistic.etherscan.io/blocks):
* Any block with `gasUsed > gasTarget` should cause the base fee to increase
* Any block with `gasUsed < gasTarget` should cause the base fee to decrease
### Node performance
* Verify nodes are processing blocks faster than the block period
* Monitor CPU/memory usage and p99 tail latency of block processing
# Key management
Source: https://docs.optimism.io/chain-operators/guides/management/key-management
Understand the key management considerations for a chain's privileged roles: which keys must stay online as hot wallets, which belong in cold wallets, and where HSMs and multisigs fit.
This page explains the key management considerations behind the privileged roles on your chain.
There are certain [privileged roles](/op-stack/protocol/privileged-roles) that
need careful consideration. The privileged roles are categorized as hot wallets
or cold wallets.
## Hot wallets
The addresses for the `Batcher` and the `Proposer` need to have their private
keys online somewhere for a component of the system to work. If these addresses
are compromised, the system can be exploited.
It is up to the chain operator to make the decision on how they want to manage
these keys. One suggestion is to use a Hardware Security Module (HSM) to provide
a safer environment for key management. Cloud providers oftentimes provide
Key Management Systems (KMS) that can work with your developer operations
configurations. This can be used in conjunction with the `eth_signTransaction`
RPC method.
You can take a look at the signer client [source code](https://github.com/ethereum-optimism/optimism/blob/develop/op-service/signer/client.go)
if you're interested in what's happening under the hood.
## Cold wallets
The addresses for the cold wallets cannot be used without human intervention.
These can be set up as multisig contracts, so they can be controlled by groups
of community members and avoid a single point of failure. The signers behind a
multisig should probably also use a hardware wallet.
Refer to the [privileged roles](/op-stack/protocol/privileged-roles) documentation
for more information about these different addresses and their security concerns.
# Network Design Example
Source: https://docs.optimism.io/chain-operators/guides/management/network-architecture
This document describes an example network configuration for an OP Stack chain deployment.
This document describes an example configuration for an OP Stack network deployment, focusing on the network architecture and node configuration required for production usage.
## Sequencers
Sequencers receive transactions from the Tx Ingress Nodes via execution-layer p2p gossip and work with the batcher and proposer to create new blocks.
### Node Configuration
* **Sequencer op-reth** can be either full or archive. op-reth runs as an archive node by default; pass `--full` to run a (pruned) full node. Full nodes offer better performance but can't recover from deep L1 reorgs, so run at least one archive sequencer as a backup.
* **Sequencer op-node** should have p2p discovery disabled and only be statically peered with other internal nodes (or use [peer-management-service](https://github.com/ethereum-optimism/infra/tree/main/peer-mgmt-service) to define peering network).
* The **op-conductor** RPC can act as a leader-aware RPC proxy for op-batcher (proxies the necessary op-reth / op-node RPC methods if the node is the leader).
* Sequencers should have local transaction backup (journalling) disabled.
### op-node Configuration
```yaml theme={null}
OP_NODE_P2P_NO_DISCOVERY: "true"
OP_NODE_P2P_PEER_BANNING: "false"
OP_NODE_P2P_STATIC: ""
```
### op-reth Configuration
op-reth is configured via CLI flags rather than `GETH_*` environment variables:
```sh theme={null}
# Leave --rollup.disable-tx-pool-gossip unset so transactions are gossiped to the internal network
--txpool.disable-transactions-backup # disable local transaction backup/journalling
--txpool.lifetime 3600 # 1h, in seconds
--txpool.nolocals
--netrestrict "10.0.0.0/8" # ex: restrict p2p to internal ips
```
## Tx Ingress Nodes
These nodes receive `eth_sendRawTransaction` calls from the public and then gossip the transactions to the internal execution-layer network. This allows the Sequencer to focus on block creation while these nodes handle transaction ingress.
### Node Configuration
* These can be either full or archive nodes.
* They participate in the internal tx pool p2p network to forward transactions to sequencers.
### Configuration
```sh theme={null}
# Leave --rollup.disable-tx-pool-gossip unset so transactions are gossiped to sequencers
--txpool.disable-transactions-backup
--txpool.lifetime 3600 # 1h, in seconds
--txpool.nolocals
--netrestrict "10.0.0.0/8" # ex: restrict p2p to internal ips
```
## Archive RPC Nodes
We recommend setting up some archive nodes for internal RPC usage, primarily used by the challenger, proposer, and security monitoring tools like [monitorism](/chain-operators/tools/chain-monitoring#monitorism).
### Node Configuration
* Archive nodes are essential for accessing historical state data.
* You can also use these nodes for taking disk snapshots for disaster recovery.
### Configuration
op-reth runs as an archive node by default, so no extra flags are required — simply do **not** pass `--full` or `--minimal` (both enable pruning). op-reth stores state in MDBX plus static files, so the geth-specific `GETH_DB_ENGINE` and `GETH_STATE_SCHEME` options have no equivalent.
```sh theme={null}
# op-reth is archive by default; no pruning flags needed.
```
## Full Nodes
These nodes run as full (pruned) nodes. op-reth does not implement geth-style snap sync; it syncs using its own staged-sync pipeline, so the geth `GETH_SYNCMODE`, `GETH_DB_ENGINE`, and `GETH_STATE_SCHEME` options do not apply.
### Configuration
```sh theme={null}
--full # run a pruned full node (or --minimal for maximum pruning)
```
## P2P Bootnodes (Execution Layer)
These bootnodes facilitate peer discovery for public op-reth nodes.
### Node Configuration
* The bootnode can be an op-reth instance or the [geth bootnode tool](https://etclabscore.github.io/core-geth/core/alltools/).
* You may want to make your Full Nodes serve as your bootnodes as well.
## P2P Bootnodes (Consensus Layer)
These are the op-node p2p network bootnodes. We recommend using the [geth bootnode tool](https://etclabscore.github.io/core-geth/core/alltools/) with discovery v5 enabled.
## Public RPC
Public RPC design is not listed in the above diagram but can be implemented very similarly to Tx Ingress Nodes, with the following differences:
### Configuration Differences
* Public RPC should **not** participate in the internal tx pool p2p network.
* While it is possible to run Public RPC from the same nodes that serve Tx Ingress and participate in tx pool gossip, there have been execution-client bugs in the past that leaked tx pool details on read RPCs, so it is a possible risk to consider.
* Public RPC [proxyd](https://github.com/ethereum-optimism/infra/tree/main/proxyd) should be run in `consensus_aware` routing mode and whitelist any RPCs you want to serve from op-reth.
* Public RPC nodes should likely be archive nodes.
### About proxyd
Proxyd is an RPC request router and proxy that provides the following capabilities:
1. Whitelists RPC methods.
2. Routes RPC methods to groups of backend services.
3. Automatically retries failed backend requests.
4. Tracks backend consensus (latest, safe, finalized blocks), peer count and sync state.
5. Re-writes requests and responses to enforce consensus.
6. Load balances requests across backend services.
7. Caches immutable responses from backends.
8. Provides metrics to measure request latency, error rates, and the like.
## Next steps
* Learn more about using [proxyd](https://github.com/ethereum-optimism/infra/tree/main/proxyd) for your network.
# Rollup operations
Source: https://docs.optimism.io/chain-operators/guides/management/operations
Learn basics of rollup operations, such as how to start and stop your rollup, get your rollup config, and how to add nodes.
This guide reviews the basics of rollup operations, such as how to start your rollup, stop your rollup, get your rollup config, and add nodes.
## Stopping your rollup
An orderly shutdown is done in the reverse order to the order in which components were started:
```sh theme={null}
curl -d '{"id":0,"jsonrpc":"2.0","method":"admin_stopBatcher","params":[]}' \
-H "Content-Type: application/json" http://localhost:8548 | jq
```
This way the batcher knows to save any data it has cached to L1.
Wait until you see `Batch Submitter stopped` in batcher's output before you stop the process.
To stop the proposer, terminate the process directly. This can be done by:
* Pressing **Ctrl+C** in the terminal running the process
* Using system commands like `kill -TERM ` to stop the process gracefully
Ensure that the proposer process has terminated completely before proceeding to stop other components.
This component is stateless, so you can just stop the process.
Use **CTRL-C** (SIGINT) so the execution client shuts down gracefully and flushes its database. Killing the process abruptly can leave the database in an inconsistent state and cause problems on the next start.
## Starting your rollup
To restart the blockchain, use the same order of components you did when you initialized it.
If `op-batcher` is still running and you just stopped it using RPC, you can start it with this command:
```sh theme={null}
curl -d '{"id":0,"jsonrpc":"2.0","method":"admin_startBatcher","params":[]}' \
-H "Content-Type: application/json" http://localhost:8548 | jq
```
Start the proposer using the appropriate command. Here's an example:
```sh theme={null}
./bin/op-proposer \
--poll-interval=12s \
--rpc.port=8560 \
--rollup-rpc=http://localhost:8547 \
--l2oo-address=0xYourL2OutputOracleAddress \
--private-key=$PROPOSER_PRIVATE_KEY \
--l1-eth-rpc=$L1_RPC_URL
```
| Parameter | Description |
| ------------- | -------------------------------------------------------------- |
| poll-interval | How often to check for new output proposals (recommended: 12s) |
| rpc.port | Local RPC port for the proposer service |
| l2oo-address | The L2 Output Oracle contract address (0x-prefixed hex) |
| private-key | Private key for signing proposals |
| l1-eth-rpc | L1 network RPC endpoint URL |
Synchronization takes time
`op-batcher` might have warning messages similar to:
```
WARN [03-21|14:13:55.248] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded"
WARN [03-21|14:13:57.328] Error calculating L2 block range err="failed to get sync status: Post \"http://localhost:8547\": context deadline exceeded"
```
This means that `op-node` is not yet synchronized up to the present time.
Just wait until it is.
## Getting your rollup config
Use this tool to get your rollup config from `op-node`. This will only work if your chain is **already** in the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/chainList.json) and `op-node` has been updated to pull those changes in from the registry.
This script will NOT work for chain operators trying to generate this data in order to submit it to the registry.
You'll need to run this tool:
```
./bin/op-node networks dump-rollup-config --network=op-sepolia
{
"genesis": {
"l1": {
"hash": "0x48f520cf4ddaf34c8336e6e490632ea3cf1e5e93b0b2bc6e917557e31845371b",
"number": 4071408
},
"l2": {
"hash": "0x102de6ffb001480cc9b8b548fd05c34cd4f46ae4aa91759393db90ea0409887d",
"number": 0
},
"l2_time": 1691802540,
"system_config": {
"batcherAddr": "0x8f23bb38f531600e5d8fddaaec41f13fab46e98c",
"overhead": "0x00000000000000000000000000000000000000000000000000000000000000bc",
"scalar": "0x00000000000000000000000000000000000000000000000000000000000a6fe0",
"gasLimit": 30000000
}
},
"block_time": 2,
"max_sequencer_drift": 600,
"seq_window_size": 3600,
"channel_timeout": 300,
"l1_chain_id": 11155111,
"l2_chain_id": 11155420,
"regolith_time": 0,
"canyon_time": 1699981200,
"delta_time": 1703203200,
"ecotone_time": 1708534800,
"batch_inbox_address": "0xff00000000000000000000000000000011155420",
"deposit_contract_address": "0x16fc5058f25648194471939df75cf27a2fdc48bc",
"l1_system_config_address": "0x034edd2a225f7f429a63e0f1d2084b9e0a93b538",
"da_challenge_address": "0x0000000000000000000000000000000000000000",
"da_challenge_window": 0,
"da_resolve_window": 0,
"use_plasma": false
}
```
Ensure that you are using the appropriate flag.
The `--network=op-sepolia` flag allows the tool to pick up the appropriate data from the registry, and uses the OPChains mapping under the hood.
## Adding nodes
To add nodes to the rollup, you need to initialize `op-node` and `op-reth`, similar to what you did for the first node.
You should *not* add an `op-batcher` because there should be only one.
```bash theme={null}
~/op-reth/genesis.json
~/optimism/op-node/rollup.json
```
```bash theme={null}
cd ~/op-reth
openssl rand -hex 32 > jwt.txt
cp jwt.txt ~/optimism/op-node
```
```bash theme={null}
cd ~/op-reth
op-reth init --chain=./genesis.json --datadir=./datadir
```
If you do it this way, you won't have to wait until the transactions are written to L1.
If you already have peer to peer synchronization, add the new node to the `--p2p.static` list so it can synchronize.
**Important:** Make sure to configure the `--rollup.sequencer` flag (alias `--rollup.sequencer-http`) to point to your sequencer node. This endpoint is crucial because `op-reth` will route `eth_sendRawTransaction` calls to this URL. The OP Stack does not currently have a public mempool, so configuring this is required if you want your node to support transaction submission.
## Next steps
* See the [Consensus Client Configuration](/node-operators/guides/configuration/consensus-clients) and [Execution Client Configuration](/node-operators/guides/configuration/execution-clients) guides for additional explanation or customization.
* If you experience difficulty at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions).
# Transaction Fees 101
Source: https://docs.optimism.io/chain-operators/guides/management/transaction-fees-101
How to check and tune the fee parameters on your OP Stack chain, with example scenarios for common situations.
This guide shows how to check and adjust the fee-related parameters on your chain's `SystemConfig` contract, and when each adjustment makes sense. It applies to the Jovian and following hardforks.
* For how each fee component is calculated, see [transaction fees on OP Mainnet](https://docs.optimism.io/op-stack/transactions/fees#transaction-fees-on-op-mainnet).
* For the full parameter catalogue — types, setters, and the OP Mainnet values — see the [fee parameters reference](/chain-operators/reference/fee-parameters).
On an OP Stack chain a transaction's **Total Fee** is made of three main components:
`Total Fee = L2 Fee + L1 Fee + Operator Fee`
Fees are gathered in dedicated contract [fee vaults](/op-stack/transactions/fee-vaults) that collect the different fee components (for example `BaseFeeVault`, `SequencerFeeVault`, etc.). See the [fee vault operations guide](/chain-operators/guides/management/fee-vaults) for how to manage and withdraw from these vaults.
## Before you start
Only the [SystemConfig owner](/op-stack/protocol/privileged-roles) can change these parameters.
Export the values used by all the commands below:
```bash theme={null}
export L1_RPC=
export SYSTEM_CONFIG=
export PK=
```
## Tune the L2 fee
The L2 fee is the EVM execution cost: `gasUsed * (baseFee + priorityFee)`. The `baseFee` is computed with the EIP-1559 mechanism and is **collected in the BaseFeeVault (not burned)** on OP Stack.
You can tune L2 fee dynamics on the SystemConfig via:
* `eip1559Denominator`: EIP-1559 max-change denominator, it appears in the denominator of the per-block base fee delta. Lower = faster response, so base fee adjusts faster but results in more volatility.
* `eip1559Elasticity`: elasticity multiplier, sets the target relative to gasLimit. As `gas_target = gasLimit / elasticity`, a **smaller gas target** (larger elasticity) means the base fee will start increasing at a lower block gas usage (i.e., base fee increases sooner under load).
* `gasLimit`: max gas per block. Lower `gasLimit` = less available gas per block (less supply), which makes base fees more sensitive to demand and can cause the base fee to move more easily.
* `minBaseFee`: minimum floor on base fee, in wei (it can be 0). See [setting the minimum base fee](/chain-operators/guides/features/setting-min-base-fee).
### Check current values
```bash theme={null}
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "eip1559Denominator()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "eip1559Elasticity()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "minBaseFee()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "gasLimit()"
```
### Set values
```bash theme={null}
cast send --rpc-url $L1_RPC --private-key $PK \
$SYSTEM_CONFIG "setEIP1559Params(uint32,uint32)"
cast send --rpc-url $L1_RPC --private-key $PK \
$SYSTEM_CONFIG "setMinBaseFee(uint64)"
cast send --rpc-url $L1_RPC --private-key $PK \
$SYSTEM_CONFIG "setGasLimit(uint64)"
```
## Tune the L1 fee
The L1 fee charges for posting L2 data to L1: the transaction's estimated compressed (FastLZ) size is multiplied by a scalar-weighted L1 price. See the [fee parameters reference](/chain-operators/reference/fee-parameters#fee-formulas) for the formula.
Reasons to tweak it:
* Modify margin charged on top of expected costs
* Adjust FastLZ estimates to reflect costs
* Differentiate charges for per-byte vs per-blob costs to reflect actual L1 pricing
The knobs:
* `basefeeScalar`: scales the `l1BaseFee` contribution to the L1 data fee (per-byte).
* `blobBaseFeeScalar`: scales the `l1BlobBaseFee` contribution (per-blob).
### Check current values
```bash theme={null}
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "basefeeScalar()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "blobbasefeeScalar()"
```
### Set values
```bash theme={null}
cast send --rpc-url $L1_RPC --private-key $PK \
$SYSTEM_CONFIG "setGasConfigEcotone(uint32,uint32)"
```
## Tune the operator fee
The operator fee is a discretionary, non-standard fee charged per transaction: `(gasUsed * operatorFeeScalar * 100) + operatorFeeConstant`. See [setting the operator fee](/chain-operators/guides/features/setting-operator-fee) for a dedicated guide.
* `operatorFeeScalar`: per-gas operator margin.
* `operatorFeeConstant`: flat per-transaction operator fee, in wei.
Any non-zero operator fee makes the chain configuration [non-standard](/op-stack/protocol/superchain-registry#what-is-a-standard-chain).
### Check current values
```bash theme={null}
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "operatorFeeScalar()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "operatorFeeConstant()"
```
### Set values
```bash theme={null}
cast send --rpc-url $L1_RPC --private-key $PK \
$SYSTEM_CONFIG "setOperatorFeeScalars(uint32,uint64)"
```
## Tune the DA footprint limit
The `daFootprintGasScalar` is an in-protocol limit on estimated DA usage to prevent DA spam and priority fee auctions. See [how the DA footprint block limit works](/chain-operators/reference/da-footprint) for the concept and the [DA footprint setup guide](/chain-operators/guides/features/setting-da-footprint) for a dedicated walkthrough.
* Default value is **400**
* A value of **0** is treated as **400**
* Setting it to **1** effectively disables DA footprint limiting
### Check current value
```bash theme={null}
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "daFootprintGasScalar()"
```
### Set value
```bash theme={null}
cast send --rpc-url $L1_RPC --private-key $PK \
$SYSTEM_CONFIG "setDAFootprintGasScalar(uint16)"
```
## Example scenarios
### Normal traffic / below EIP-1559 target
**What you'll see**
* If blocks stay below the EIP-1559 target, `baseFee` trends toward the `minBaseFee` floor.
* When L1 data usage is low, the L2 execution fee (`gasUsed * (baseFee + priorityFee)`) is typically the largest cost component.
* The L1 data fee remains low/steady if calldata per transaction is small.
**What to tweak**
* Usually nothing.
* If you need a revenue floor set/increase `minBaseFee`.
**Tradeoff**
* **Pros:** `minBaseFee` prevents prolonged low base fees and provides a predictable minimum (can be left at 0 to improve UX).
* **Cons:** If set too high, it raises inclusion cost for low-value transactions and can negatively impact UX/accessibility.
***
### Busy blocks / congestion
**What you'll see**
* `baseFee` rises when blocks exceed the target: `gas_target = gasLimit / eip1559Elasticity`
* Transactions with low priority tips may experience slower inclusion during congestion.
**What to tweak**
* `eip1559Elasticity` and `eip1559Denominator` :
* To increase responsiveness: decrease `eip1559Denominator` and/or increase `eip1559Elasticity` (smaller target → baseFee increases sooner).
* To decrease volatility: increase `eip1559Denominator` and/or reduce aggressiveness in `eip1559Elasticity`.
**Tradeoff**
* **Pros:** Faster base fee response better captures demand; elasticity provides direct control over the gas target.
* **Cons:** Smaller denominator and/or aggressive elasticity increases per-block swings and increases fee volatility.
***
### Calldata-heavy transactions
**What you'll see**
* L1 data fee becomes a larger share of total cost for calldata heavy transactions (the L1 fee scales with compressed calldata size).
* Increasing `basefeeScalar` increases the per-byte L1 charge, making calldata heavy transactions more expensive.
**What to tweak**
* `basefeeScalar` and `blobBaseFeeScalar`:
* Increase them if you need to recover more L1/DA posting costs from data heavy transactions.
* Decrease them if you want to reduce user cost for calldata/blob-heavy workloads.
**Tradeoff**
* **Pros:** Enables proportional recovery of L1/DA posting costs.
* **Cons:** Penalizes calldata/blob-heavy dapps/users. Aggressive values can impact UX and reduce activity.
***
### Predictable operator revenue
**What you'll see**
* Introducing or increasing `operatorFeeScalar` raises the per-gas operator charge and increases predictable revenue per transaction.
* `operatorFeeConstant` adds a flat per-transaction fee regardless of gas usage.
**What to tweak**
* `operatorFeeScalar` and `operatorFeeConstant`:
* Use when you want direct, predictable operator revenue independent of congestion and L1 fee dynamics.
**Tradeoff**
* **Pros:** Direct and predictable revenue per transaction.
* **Cons:** May discourage low-value transactions and changes transaction economics; should be used carefully. Any non-zero operator fee makes the chain configuration non-standard.
***
### DA-heavy workloads / DA-spam resistance
**What you'll see**
* DA footprint can limit how much transaction data fits into a block through scaled accounting of compressed DA usage.
* Increasing `daFootprintGasScalar` raises the gas charged per DA-byte footprint, reducing DA capacity for the same byte footprint and deterring DA-heavy patterns.
**What to tweak**
* `daFootprintGasScalar`:
* Default is **400**, and `0` is treated as the default.
* Setting it to **1** disables the DA footprint limiting behavior.
**Tradeoff**
* **Pros:** Deters DA spam and makes DA costs explicit in block accounting.
* **Cons:** Increasing it reduces DA capacity and may throttle the batcher or exclude legitimate DA-heavy workloads if set too aggressively.
***
## Summary
1. Make sure to check your current hardfork, this doc applies to Jovian and following hardforks.
2. Check the current fee parameters on your SystemConfig contract (see the [fee parameters reference](/chain-operators/reference/fee-parameters) for the expected values on OP Mainnet).
3. Make small changes and observe: vault balances, inclusion times, dropped txs, DA throttling, etc.
4. Extensively test all changes on a staging/testnet.
5. Communicate changes to dapp/wallet teams (gas estimation will change).
***
## References
* [Fee parameters reference](/chain-operators/reference/fee-parameters)
* [Transaction fees on OP Mainnet](https://docs.optimism.io/op-stack/transactions/fees#transaction-fees-on-op-mainnet)
* [Operator fee](https://docs.optimism.io/op-stack/transactions/fees#operator-fee)
* [L1 data fee](https://docs.optimism.io/op-stack/transactions/fees#l1-data-fee)
* [SystemConfig interface](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol)
* [DA footprint](https://docs.optimism.io/notices/archive/upgrade-17#block-header-changes)
* [EIP-1559](https://docs.optimism.io/op-stack/protocol/differences#eip-1559-parameters)
* [OP Mainnet values](https://etherscan.io/address/0x229047fed2591dbec1eF1118d64F7aF3dB9EB290#readProxyContract)
# Troubleshooting chain operations
Source: https://docs.optimism.io/chain-operators/guides/management/troubleshooting
Learn solutions to common problems when troubleshooting chain operations.
This page lists common troubleshooting scenarios and solutions for chain operators.
## EvmError in contract deployment
L1 smart contract deployment fails with the following error:
```text theme={null}
EvmError: Revert
```
### Solution
The OP Stack uses deterministic smart contract deployments to guarantee that all contract addresses can be computed ahead of time based on a "salt" value that is provided at deployment time.
Each OP Stack chain must have a unique salt value to ensure that the contract addresses do not collide with other OP Stack chains.
You can avoid this error by changing the salt used when deploying the L1 smart contracts.
The salt value is set by the `IMPL_SALT` environment variable when deploying the contracts.
The `IMPL_SALT` value must be a 32 byte hex string.
You can generate a random salt value using the following command:
```bash theme={null}
export IMPL_SALT=$(openssl rand -hex 32)
```
## Failed to find the L2 Heads to start from
`op-node` fails to execute the derivation process with the following error:
```text theme={null}
WARN [02-16|21:22:02.868] Derivation process temporary error attempts=14 err="stage 0 failed resetting: temp: failed to find the L2 Heads to start from: failed to fetch L2 block by hash 0x0000000000000000000000000000000000000000000000000000000000000000: failed to determine block-hash of hash 0x0000000000000000000000000000000000000000000000000000000000000000, could not get payload: not found"
```
### Solution
This error can occur when the data directory for `op-reth` becomes corrupted (for example, as a result of a computer crash).
You will need to reinitialize the data directory.
If you are following the tutorial for [Creating Your Own L2 Rollup](/chain-operators/tutorials/create-l2-rollup), make sure to rerun the commands within the [Spin up sequencer](/chain-operators/tutorials/create-l2-rollup/op-reth-setup) section.
If you are not following the tutorial, make sure to take the following steps:
1. Stop `op-node` and `op-reth`.
2. Delete the corresponding `op-reth` data directory.
3. Reinitialize `op-reth` with the `genesis.json` file: `op-reth init --chain=./genesis.json --datadir=./datadir`.
4. Restart `op-reth` and `op-node`.
## Batcher unable to publish transaction
`op-batcher` fails to publish transactions with the following error:
```text theme={null}
INFO [03-21|14:22:32.754] publishing transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515
ERROR[03-21|14:22:32.844] unable to publish transaction service=batcher txHash=2ace6d..7eb248 nonce=2516 gasTipCap=2,340,741 gasFeeCap=172,028,434,515 err="insufficient funds for gas * price + value"
```
### Solution
You will observe this error if the `op-batcher` runs out of ETH to publish transactions to L1.
This problem can be resolved by sending additional ETH to the `op-batcher` address.
# Chain operators
Source: https://docs.optimism.io/chain-operators/index
Routes chain operators to the quickstart, guides, tutorials, tools, and reference for launching and running an OP Stack chain.
You are launching or operating your own OP Stack chain. Deploy first, then
use the guides, tools, and reference material to run it in production.
Launch your first OP Stack chain with op-deployer.
Set up the batcher, proposer, and challenger, enable features, and follow
production best practices.
Build a rollup component by component, upgrade L1 contracts, and
customize your chain step by step.
Operate op-deployer, op-conductor, chain monitoring, and the RPC
frontends that keep a chain healthy.
Check flag-level reference for the batcher, challenger, deployment
configuration, and fee parameters.
Route by role from the documentation home: app developers, node
operators, and protocol learners each have their own section.
# Choose how to run your chain
Source: https://docs.optimism.io/chain-operators/launch-paths
The spectrum between running an OP Stack chain yourself and having it operated for you, and what your team owns at each point on it.
Every team launching an OP Stack chain lands somewhere on the same spectrum. At
one end, your team runs every service and holds every key. At the other end,
someone runs them for you. The choice is not about capability: it is about which
parts of a standing operation your team wants to own.
This page describes the spectrum and what falls to your team at each point on
it. It does not pick a point for you. The pages linked below document the whole
system regardless of who operates it.
## The spectrum
| | Run it yourself | Run it with engineering support | Have it operated for you |
| ------------------- | ------------------------------ | --------------------------------------------- | ------------------------------------------ |
| Sequencer cluster | Your team | Your team, with OP Labs engineering behind it | OP Enterprise |
| Fault-proof defense | Your team | Your team, with OP Labs engineering behind it | OP Enterprise |
| Upgrade execution | Your team | Your team, with OP Labs engineering behind it | OP Enterprise |
| Incident response | Your team | Your team, with OP Labs engineering behind it | OP Enterprise |
| These docs | The reference for what you run | The reference for what you run | The reference for what runs on your behalf |
## Run it yourself
Your team runs every service, holds every key, and owns every incident. These
docs cover all of it, and every destination below is one click away.
What that ownership includes, stated the way the docs themselves state it:
### The services you run
* **The sequencer.** A single sequencer is a single point of failure for the
whole chain: when it stops, no new unsafe blocks exist for anyone. The
high-availability answer is a sequencer cluster managed by
[op-conductor](/chain-operators/tools/op-conductor), with its nodes in
separate failure domains. The design is not Byzantine fault tolerant: it
assumes every node in the cluster is honest and operated by you, so it
protects against crashes and partitions, not against a malicious cluster
member. See
[the launch guide's sequencer topology step](/use-cases/launch-a-chain-with-fault-proofs-and-ha-sequencing#step-3-design-the-sequencer-topology).
* **The defense.** The defense is a service you run and a set of decisions
your team staffs. The
[`op-challenger`](/op-stack/fault-proofs/challenger) is a service that
monitors every game, defends valid proposals, challenges invalid ones,
resolves games, and claims bonds. Starting it is not the whole job. Every
claim it posts carries a bond sent as transaction value; correct claims are
refunded, incorrect ones pay the counter-claimer, and bonds from won games
pay out only after a delay, so capital stays locked while games resolve.
How much the challenger's account holds, how quickly you can top it up
during an active dispute, and which absolute prestate it plays with are
operational answers your team owns; the challenger refuses to interact with
games whose prestate it does not have. See
[Run a fault-proof challenger](/use-cases/run-a-fault-proof-challenger).
* **The monitoring.** Watching the chain is a separate set of services from
running it. `op-dispute-mon` tracks the status of every dispute game and is
how you learn that your challenger is acting; `monitorism` carries the
onchain security monitors, whose security-integrity group exists to check
that the bridges between L2 and L1 behave as expected, including the
faultproof withdrawal monitor that watches `ProvenWithdrawals` events on
the `OptimismPortal` and flags invariant violations. Your own components
need their metrics endpoints scraped alongside that: `op-node`,
`op-batcher`, `op-proposer`, and `op-challenger` each expose one. Peer
count belongs on that list too, because an `op-node` without peers cannot
sync unsafe blocks and falls behind the sequencer. See
[Chain monitoring options](/chain-operators/tools/chain-monitoring) and
[the important node metrics](/node-operators/guides/monitoring/metrics#important-metrics).
* **The RPC endpoints.** Every service in the stack depends on RPC, and not
only on L1. Each sequencer's op-node derives the chain from an L1 RPC and
an L1 beacon endpoint, and the batcher, proposer, and challenger read and
transact against L1; `op-challenger` additionally needs an L2 archive node
(`--l2-eth-rpc`) and a rollup node with SafeDB (`--rollup-rpc`), and
`op-dispute-mon` takes a rollup RPC of its own. Redundant nodes behind a
generic load balancer are the wrong shape for any of them. Two nodes can be
at the same head, but nothing holds them there, and a service whose
consecutive requests round-robin between them reads that divergence as
blocks appearing and disappearing: reorgs that never happened. The
challenger is the least tolerant consumer and needs one trusted endpoint
that fails over deliberately rather than per request. See
[the launch guide's section on a consistent view](/use-cases/launch-a-chain-with-fault-proofs-and-ha-sequencing#give-every-service-a-consistent-view-of-l1).
### The keys and the decisions
* **The keys.** The batcher and proposer addresses need their private keys
online somewhere for the system to work, and if those addresses are
compromised, the system can be exploited. They are not the only privileged
addresses your chain has. The Proxy Admins can upgrade most of the system
contracts on L1 and L2, the System Config Owner can change the values in
the `SystemConfig` contract, the Guardian can pause withdrawal logic and
disable dispute game types from executing withdrawals, and the permissioned
Challenger role is a distinct address from the `op-challenger` service.
Which addresses hold which role, and how each key is held, whether through
an HSM or a cloud key management system, are decisions the chain operator
makes. See
[Key management](/chain-operators/guides/management/key-management) and
[Privileged roles in OP Stack chains](/op-stack/protocol/privileged-roles#privileged-roles-in-op-stack-chains)
for what every role can do and what a compromise of it means.
* **The path to permissionless proofs.** Chains deployed with `op-deployer`
start with the permissioned dispute game, in which the proposer and
challenger are specific addresses holding privileged roles. Moving to
permissionless fault proofs is a switch your team schedules after launch.
See
[the launch guide's permissionless-proofs step](/use-cases/launch-a-chain-with-fault-proofs-and-ha-sequencing#step-7-schedule-the-switch-to-permissionless-proofs).
### The work that recurs
* **The protocol upgrade cadence.** The OP Stack is being continuously
improved and it's your responsibility to keep it up to date. Network
upgrades, deprecations, security patches, and operational changes arrive as
time-bound action items in [Network Notices](/notices), with the permanent
record of each hardfork in the
[hardfork registry](/op-stack/protocol/network-upgrades), which records
activation times, the governing spec, and minimum component versions.
Tracking each notice, moving your components to the versions it names, and
executing the change on your chain recurs for as long as the chain runs.
* **The standing work.** Running current production releases, keeping the
deployment artifacts, staggering upgrade rollouts across your
infrastructure, isolating the sequencer, and writing your own runbooks are
continuous tasks rather than launch-day ones. Monitoring only helps if
someone is on the other end of it. Metrics endpoints on your key
components, and runbooks to execute when something is not behaving as
expected, are the documented practice; the alerting path, the people
reachable through it, and the incident response itself are yours to build
and staff. See
[Chain operator best practices](/chain-operators/guides/management/best-practices).
The full deployment tutorial: contracts, genesis, sequencer, batcher,
proposer, and challenger on a testnet.
The launch guide: fault proofs from day one and a sequencer topology with no
single point of failure, through failover drills.
Bond budgeting, prestate selection, the infrastructure the challenger
depends on, and how to confirm it is defending your chain.
Release selection, deployment artifacts, staggered rollouts, sequencer
isolation, and runbooks.
## Run it with engineering support
Your team still runs the chain. The difference is who sits behind the decisions
above and behind the incidents when they happen: OP Labs engineering does,
alongside your operators. Everything linked in the previous section stays the
reference for what your team is running, because it is the same system.
One capability on this path is not something your team has to stand up itself:
OP Labs can optionally run a backup sequencer for your chain, alongside the
sequencer cluster your team operates. It is opt-in rather than part of the path
by default, and whether to take it is your team's decision.
## Have it operated for you
OP Enterprise runs the chain and your team consumes it. The pages above still
describe what is running on your behalf, which is why they are worth reading on
this path too: the properties you are buying are the ones those pages document.
## What OP Enterprise is
OP Enterprise is Optimism's managed offering. It covers fully managed and
supported self-managed options, backed by an uptime SLA, priority incident
response, direct engineering support, and managed public RPC.
The
[OP Enterprise](https://optimism.io/op-enterprise?utm_source=docs\&utm_medium=docs\&utm_campaign=op-enterprise)
page states what each option includes and is the source of truth for its terms.
This page does not restate them.
## Where to go from here
Whichever point on the spectrum you choose, the
[chain operator quickstart](/chain-operators/quickstart) and the pages it leads
to describe the same chain. Start there if you have not deployed one yet.
# Chain operator quickstart
Source: https://docs.optimism.io/chain-operators/quickstart
Launch a scalable and customizable Layer 2 Rollup blockchain with Ethereum-grade security - powered by Optimism.
Launch your own OP Stack chain: an open-source, modular, Ethereum Layer 2 rollup.
This page orients you on the key components and the sequence of steps, then hands you off to the [full deployment tutorial](/chain-operators/tutorials/create-l2-rollup).
There are two ways to have an OP Stack chain: run it yourself, which is what this page and the tutorial behind it cover, or have it run for you.
[Choose how to run your chain](/chain-operators/launch-paths) sets out the spectrum between them and what your team owns at each point on it.
## Components
Before you deploy, it is important to understand the key components and how they come together to create your blockchain.
* **L1 Smart contracts**: A set of smart contracts to be deployed on Ethereum to bridge between the L1 and L2 domains and manage aspects of the rollup.
* **Sequencer**: A single privileged node that accepts and derives user transactions on the network to construct the blockchain.
* **Batcher**: A sequencer service that publishes L2 transactions onto Ethereum.
Using Ethereum as a data availability layer, the OP Stack inherits Ethereum's security properties by allowing any node to derive the state of the L2 blockchain from L1.
* **Proposer**: A service responsible for publishing the L2 state root to Ethereum which enables user withdrawals of assets.
* **Challenger**: The challenger enforces network security by disputing invalid state roots that have been posted to Ethereum.
## Deployment
The following section will walk you through the sequence of steps a chain operator will follow to begin sequencing a chain.
Using a CLI tool called [op-deployer](/chain-operators/tools/op-deployer/overview) you will configure your chain and then deploy the smart contracts on Ethereum.
After deploying the L1 smart contracts, you will use [op-deployer](/chain-operators/tools/op-deployer/overview) to generate two files necessary to run nodes on the L2 network:
* **Genesis file** (`genesis.json`): Initializes the execution client (`op-reth`)
* **Rollup configuration file** (`rollup.json`): Configures the consensus client (`op-node`)
These files contain all the essential information your services need to interact with Ethereum and the system contracts you deployed.
To begin sequencing transactions and building blocks, you will then run an **execution client** and **consensus client** that come together as your **sequencer node**.
Next you will run `op-batcher` which will publish user transactions on Ethereum.
Then you will run `op-proposer` to publish the L2 state root on Ethereum to enable withdrawals back to Ethereum.
Finally you will run `op-challenger` to monitor and dispute any invalid L2 state roots that have been posted.
Deploy a complete OP Stack testnet, component by component, with a working automated example alongside.
## Next Steps
Take a look at some of the [chain operator best practices](/chain-operators/guides/management/best-practices) to get an idea of some of the things you'll need to keep in mind.
**Running this in production**
# Batcher configuration reference
Source: https://docs.optimism.io/chain-operators/reference/batcher-configuration
Reference for all op-batcher configuration options, covering CLI flags, environment variables, and default values.
This page catalogues every configuration option for the op-batcher, the service
that posts L2 sequencer data to L1 to make it available for verifiers.
For guidance on choosing values — the batcher policy, cost tuning, multi-blob
transactions, and sequencer throttling — see the
[batcher configuration guide](/chain-operators/guides/configuration/batcher).
## Flags
Generated from [`op-batcher/v1.16.11`](https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.16.11)
flag definitions. 87 flags: 3 required, 84 optional.
### Required flags
| Flag | Description | Environment variable |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `--l1-eth-rpc` | HTTP provider URL for L1 | `OP_BATCHER_L1_ETH_RPC` |
| `--l2-eth-rpc` | HTTP provider URL for L2 execution engine. A comma-separated list enables the active L2 endpoint provider. Such a list needs to match the number of rollup-rpcs provided. | `OP_BATCHER_L2_ETH_RPC` |
| `--rollup-rpc` | HTTP provider URL for Rollup node. A comma-separated list enables the active L2 endpoint provider. Such a list needs to match the number of l2-eth-rpcs provided. | `OP_BATCHER_ROLLUP_RPC` |
### Optional flags
| Flag | Description | Default | Environment variable |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ----------------------------------------------------- |
| `--active-sequencer-check-duration` | The duration between checks to determine the active sequencer endpoint. | `5s` | `OP_BATCHER_ACTIVE_SEQUENCER_CHECK_DURATION` |
| `--altda.da-server` | HTTP address of a DA Server | — | `OP_BATCHER_ALTDA_DA_SERVER` |
| `--altda.da-service` | Use DA service type where commitments are generated by Alt-DA server | `false` | `OP_BATCHER_ALTDA_DA_SERVICE` |
| `--altda.enabled` | Enable Alt-DA mode Alt-DA Mode is a Beta feature of the MIT licensed OP Stack. While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues. | `false` | `OP_BATCHER_ALTDA_ENABLED` |
| `--altda.get-timeout` | Timeout for get requests. 0 means no timeout. | `0s` | `OP_BATCHER_ALTDA_GET_TIMEOUT` |
| `--altda.max-concurrent-da-requests` | Maximum number of concurrent requests to the DA server | `1` | `OP_BATCHER_ALTDA_MAX_CONCURRENT_DA_REQUESTS` |
| `--altda.put-timeout` | Timeout for put requests. 0 means no timeout. | `0s` | `OP_BATCHER_ALTDA_PUT_TIMEOUT` |
| `--altda.verify-on-read` | Verify input data matches the commitments from the DA storage service | `true` | `OP_BATCHER_ALTDA_VERIFY_ON_READ` |
| `--approx-compr-ratio` | The approximate compression ratio (\<= 1.0). Only relevant for ratio compressor. | `0.6` | `OP_BATCHER_APPROX_COMPR_RATIO` |
| `--batch-type` | The batch type. 0 for SingularBatch and 1 for SpanBatch. | `singular` | `OP_BATCHER_BATCH_TYPE` |
| `--check-recent-txs-depth` | Indicates how many blocks back the batcher should look during startup for a recent batch tx on L1. This can speed up waiting for node sync. It should be set to the verifier confirmation depth of the sequencer (e.g. 4). | `0` | `OP_BATCHER_CHECK_RECENT_TXS_DEPTH` |
| `--compression-algo` | The compression algorithm to use. Valid options: zlib, brotli, brotli-9, brotli-10, brotli-11 | `zlib` | `OP_BATCHER_COMPRESSION_ALGO` |
| `--compressor` | The type of compressor. Valid options: none, ratio, shadow | `"shadow"` | `OP_BATCHER_COMPRESSOR` |
| `--data-availability-type` | The data availability type to use for submitting batches to the L1. Valid options: calldata, blobs, auto | `calldata` | `OP_BATCHER_DATA_AVAILABILITY_TYPE` |
| `--fee-limit-multiplier` | The multiplier applied to fee suggestions to put a hard limit on fee increases | `5` | `OP_BATCHER_TXMGR_FEE_LIMIT_MULTIPLIER` |
| `--hd-path` | The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set. | — | `OP_BATCHER_HD_PATH` |
| `--log.color` | Color the log output if in terminal mode | `false` | `OP_BATCHER_LOG_COLOR` |
| `--log.format` | Format the log output. Supported formats: text, terminal, logfmt, logfmtms, json, jsonms | `text` | `OP_BATCHER_LOG_FORMAT` |
| `--log.level` | The lowest log level that will be output | `INFO` | `OP_BATCHER_LOG_LEVEL` |
| `--log.pid` | Show pid in the log | `false` | `OP_BATCHER_LOG_PID` |
| `--max-blocks-per-span-batch` | Maximum number of blocks to add to a span batch. Default is 0 - no maximum. | `0` | `OP_BATCHER_MAX_BLOCKS_PER_SPAN_BATCH` |
| `--max-channel-duration` | The maximum duration of L1-blocks to keep a channel open. 0 to disable. | `0` | `OP_BATCHER_MAX_CHANNEL_DURATION` |
| `--max-l1-tx-size-bytes` | The maximum size of a batch tx submitted to L1. Ignored for blobs, where max blob size will be used. | `120000` | `OP_BATCHER_MAX_L1_TX_SIZE_BYTES` |
| `--max-pending-tx` | The maximum number of pending transactions. 0 for no limit. | `1` | `OP_BATCHER_MAX_PENDING_TX` |
| `--metrics.addr` | Metrics listening address | `"0.0.0.0"` | `OP_BATCHER_METRICS_ADDR` |
| `--metrics.enabled` | Enable the metrics server | `false` | `OP_BATCHER_METRICS_ENABLED` |
| `--metrics.port` | Metrics listening port | `7300` | `OP_BATCHER_METRICS_PORT` |
| `--mnemonic` | The mnemonic used to derive the wallets for either the service | — | `OP_BATCHER_MNEMONIC` |
| `--network-timeout` | Timeout for all network operations | `10s` | `OP_BATCHER_NETWORK_TIMEOUT` |
| `--num-confirmations` | Number of confirmations which we will wait after sending a transaction | `10` | `OP_BATCHER_NUM_CONFIRMATIONS` |
| `--poll-interval` | How frequently to poll L2 for new blocks | `6s` | `OP_BATCHER_POLL_INTERVAL` |
| `--pprof.addr` | pprof listening address | `"0.0.0.0"` | `OP_BATCHER_PPROF_ADDR` |
| `--pprof.enabled` | Enable the pprof server | `false` | `OP_BATCHER_PPROF_ENABLED` |
| `--pprof.path` | pprof file path. If it is a directory, the path is \{dir}/\{profileType}.prof | — | `OP_BATCHER_PPROF_PATH` |
| `--pprof.port` | pprof listening port | `6060` | `OP_BATCHER_PPROF_PORT` |
| `--pprof.type` | pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, allocs | — | `OP_BATCHER_PPROF_TYPE` |
| `--private-key` | The private key to use with the service. Must not be used with mnemonic. | — | `OP_BATCHER_PRIVATE_KEY` |
| `--resubmission-timeout` | Duration we will wait before resubmitting a transaction to L1 | `48s` | `OP_BATCHER_RESUBMISSION_TIMEOUT` |
| `--rpc.addr` | rpc listening address | `"0.0.0.0"` | `OP_BATCHER_RPC_ADDR` |
| `--rpc.enable-admin` | Enable the admin API | `false` | `OP_BATCHER_RPC_ENABLE_ADMIN` |
| `--rpc.port` | rpc listening port | `8545` | `OP_BATCHER_RPC_PORT` |
| `--safe-abort-nonce-too-low-count` | Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation | `3` | `OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT` |
| `--sequencer-hd-path` | DEPRECATED: The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set. | — | `OP_BATCHER_SEQUENCER_HD_PATH` |
| `--signer.address` | Address the signer is signing requests for | — | `OP_BATCHER_SIGNER_ADDRESS` |
| `--signer.endpoint` | Signer endpoint the client will connect to | — | `OP_BATCHER_SIGNER_ENDPOINT` |
| `--signer.header` | Headers to pass to the remote signer. Format `key=value`. Value can contain any character allowed in a HTTP header. When using env vars, split with commas. When using flags one key value pair per flag. | — | `OP_BATCHER_SIGNER_HEADER` |
| `--signer.tls.ca` | tls ca cert path | `"tls/ca.crt"` | `OP_BATCHER_SIGNER_TLS_CA` |
| `--signer.tls.cert` | tls cert path | `"tls/tls.crt"` | `OP_BATCHER_SIGNER_TLS_CERT` |
| `--signer.tls.enabled` | Enable or disable TLS client authentication for the signer | `true` | `OP_BATCHER_SIGNER_TLS_ENABLED` |
| `--signer.tls.key` | tls key | `"tls/tls.key"` | `OP_BATCHER_SIGNER_TLS_KEY` |
| `--stopped` | Initialize the batcher in a stopped state. The batcher can be started using the admin\_startBatcher RPC | `false` | `OP_BATCHER_STOPPED` |
| `--sub-safety-margin` | The batcher tx submission safety margin (in #L1-blocks) to subtract from a channel's timeout and sequencing window, to guarantee safe inclusion of a channel on L1. | `10` | `OP_BATCHER_SUB_SAFETY_MARGIN` |
| `--target-num-frames` | The target number of frames to create per channel. Controls number of blobs per blob tx, if using Blob DA. | `1` | `OP_BATCHER_TARGET_NUM_FRAMES` |
| `--throttle.additional-endpoints` | Comma-separated list of endpoints to distribute throttling configuration to (in addition to the L2 endpoints specified with --l2-eth-rpc). | — | `OP_BATCHER_THROTTLE_ADDITIONAL_ENDPOINTS` |
| `--throttle.block-size-lower-limit` | The limit on the DA size of blocks when we are at maximum throttle intensity (linear and quadratic controllers only). 0 means no limits will ever be applied, so consider 1 the smallest effective limit. | `2000` | `OP_BATCHER_THROTTLE_BLOCK_SIZE_LOWER_LIMIT` |
| `--throttle.block-size-upper-limit` | The limit on the DA size of blocks when we are at 0 throttle intensity (applied when throttling is inactive) | `130000` | `OP_BATCHER_THROTTLE_BLOCK_SIZE_UPPER_LIMIT` |
| `--throttle.controller-type` | Type of throttle controller to use: 'step', 'linear', 'quadratic' (default) or 'pid' (EXPERIMENTAL - use with caution) | `"quadratic"` | `OP_BATCHER_THROTTLE_CONTROLLER_TYPE` |
| `--throttle.pid-integral-max` | EXPERIMENTAL: PID controller maximum integral windup. Only relevant if --throttle-controller-type is set to 'pid' | `1000` | `OP_BATCHER_THROTTLE_PID_INTEGRAL_MAX` |
| `--throttle.pid-kd` | EXPERIMENTAL: PID controller derivative gain. Only relevant if --throttle-controller-type is set to 'pid' | `0.05` | `OP_BATCHER_THROTTLE_PID_KD` |
| `--throttle.pid-ki` | EXPERIMENTAL: PID controller integral gain. Only relevant if --throttle-controller-type is set to 'pid' | `0.01` | `OP_BATCHER_THROTTLE_PID_KI` |
| `--throttle.pid-kp` | EXPERIMENTAL: PID controller proportional gain. Only relevant if --throttle-controller-type is set to 'pid' | `0.33` | `OP_BATCHER_THROTTLE_PID_KP` |
| `--throttle.pid-output-max` | EXPERIMENTAL: PID controller maximum output. Only relevant if --throttle-controller-type is set to 'pid' | `1` | `OP_BATCHER_THROTTLE_PID_OUTPUT_MAX` |
| `--throttle.pid-sample-time` | EXPERIMENTAL: PID controller sample time interval, default is 2s | `2s` | `OP_BATCHER_THROTTLE_PID_SAMPLE_TIME` |
| `--throttle.tx-size-lower-limit` | The limit on the DA size of transactions when we are at maximum throttle intensity. 0 means no limits will ever be applied, so consider 1 the smallest effective limit. | `150` | `OP_BATCHER_THROTTLE_TX_SIZE_LOWER_LIMIT` |
| `--throttle.tx-size-upper-limit` | The limit on the DA size of transactions when we are at 0+ throttle intensity (limit of the intensity as it approaches 0 from positive values). Not applied when throttling is inactive. | `20000` | `OP_BATCHER_THROTTLE_TX_SIZE_UPPER_LIMIT` |
| `--throttle.unsafe-da-bytes-lower-threshold` | The threshold on unsafe\_da\_bytes beyond which the batcher will start to throttle the block builder. Zero disables throttling. | `3200000` | `OP_BATCHER_THROTTLE_UNSAFE_DA_BYTES_LOWER_THRESHOLD` |
| `--throttle.unsafe-da-bytes-upper-threshold` | Threshold on unsafe\_da\_bytes at which throttling has the maximum intensity (linear and quadratic controllers only) | `12800000` | `OP_BATCHER_THROTTLE_UNSAFE_DA_BYTES_UPPER_THRESHOLD` |
| `--txmgr.already-published-custom-errs` | List of custom RPC error messages that indicate that a transaction has already been published. | — | `OP_BATCHER_TXMGR_ALREADY_PUBLISHED_CUSTOM_ERRS` |
| `--txmgr.blob-tip-cap-dynamic` | Use dynamic blob tip cap from the blob tip oracle instead of static tip cap for blob transactions. Regular transactions still use min-tip-cap/max-tip-cap. | `false` | `OP_BATCHER_TXMGR_BLOB_TIP_CAP_DYNAMIC` |
| `--txmgr.blob-tip-cap-percentile` | Percentile of recent blob tx tips to use for suggestion (1-100). Only used when blob-tip-cap-dynamic is enabled. | `60` | `OP_BATCHER_TXMGR_BLOB_TIP_CAP_PERCENTILE` |
| `--txmgr.blob-tip-cap-range` | Number of recent blocks to analyze for blob tip cap distribution. Only used when blob-tip-cap-dynamic is enabled. | `20` | `OP_BATCHER_TXMGR_BLOB_TIP_CAP_RANGE` |
| `--txmgr.cell-proof-time` | Enables cell proofs in blob transactions for Fusaka (EIP-7742) compatibility from the provided unix timestamp. Should be set to the L1 Fusaka time. May be left blank for Ethereum Mainnet, Sepolia, Holesky, or Hoodi L1s. | `18446744073709551615` | `OP_BATCHER_TXMGR_CELL_PROOF_TIME` |
| `--txmgr.fee-limit-threshold` | The minimum threshold (in GWei) at which fee bumping starts to be capped. Allows arbitrary fee bumps below this threshold. | `100` | `OP_BATCHER_TXMGR_FEE_LIMIT_THRESHOLD` |
| `--txmgr.max-basefee` | Enforces a maximum base fee (in GWei) to assume when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default. | `0` | `OP_BATCHER_TXMGR_MAX_BASEFEE` |
| `--txmgr.max-retries` | Maximum number of times to resubmit a transaction to L1 on a transient error. Set to 0 to disable retries. | `10` | `OP_BATCHER_TXMGR_MAX_RETRIES` |
| `--txmgr.max-tip-cap` | Enforces a maximum tip cap (in GWei) to use when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default. | `0` | `OP_BATCHER_TXMGR_MAX_TIP_CAP` |
| `--txmgr.min-basefee` | Enforces a minimum base fee (in GWei) to assume when determining tx fees. 1 GWei by default. | `1` | `OP_BATCHER_TXMGR_MIN_BASEFEE` |
| `--txmgr.min-tip-cap` | Enforces a minimum tip cap (in GWei) to use when determining tx fees. 1 GWei by default. | `1` | `OP_BATCHER_TXMGR_MIN_TIP_CAP` |
| `--txmgr.not-in-mempool-timeout` | Timeout for aborting a tx send if the tx does not make it to the mempool. | `2m0s` | `OP_BATCHER_TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT` |
| `--txmgr.rebroadcast-interval` | Interval at which a published transaction will be rebroadcasted if it has not yet been mined. Should be less than ResubmissionTimeout to have an effect. | `12s` | `OP_BATCHER_TXMGR_REBROADCAST_INTERVAL` |
| `--txmgr.receipt-query-interval` | Frequency to poll for receipts | `12s` | `OP_BATCHER_TXMGR_RECEIPT_QUERY_INTERVAL` |
| `--txmgr.retry-interval` | Duration we will wait before resubmitting a transaction to L1 on a transient error. Values \<= 0 will result in retrying immediately. Should be less than ResubmissionTimeout to have an effect. | `1s` | `OP_BATCHER_TXMGR_RETRY_INTERVAL` |
| `--txmgr.send-timeout` | Timeout for sending transactions. If 0 it is disabled. | `0s` | `OP_BATCHER_TXMGR_TX_SEND_TIMEOUT` |
| `--wait-node-sync` | Indicates if, during startup, the batcher should wait for a recent batcher tx on L1 to finalize (via more block confirmations). This should help avoid duplicate batcher txs. | `false` | `OP_BATCHER_WAIT_NODE_SYNC` |
## Throttling
The `throttle.*` flags control how the batcher limits data availability (DA)
usage when a backlog builds up. When the amount of sequenced data that has not
yet been posted to L1 (the `unsafe_da_bytes` metric) exceeds
`--throttle.unsafe-da-bytes-lower-threshold`, the batcher instructs block
builders — over the `--l2-eth-rpc` endpoints, plus any endpoints listed in
`--throttle.additional-endpoints` — to limit transaction and block DA sizes,
scaling between the configured upper and lower size limits as the backlog
grows.
`--throttle.controller-type` selects how throttling intensity ramps with the
backlog: `step`, `linear`, `quadratic` (the default), or the experimental
`pid` controller, which is tuned with the six `throttle.pid-*` flags.
For the design of the throttling subsystem, including the PID controller, see
[`op-batcher/throttling.md`](https://github.com/ethereum-optimism/optimism/blob/develop/op-batcher/throttling.md)
in the monorepo.
## Notes on selected flags
### batch-type
Span batches (`--batch-type=1`) aggregate consecutive L2 blocks into a single
batch for better compression. See the
[span batch feature page](/op-stack/features/span-batches) to learn more.
### data-availability-type
Setting this flag to `auto` allows the batcher to automatically switch
between `calldata` and `blobs` based on the current L1 gas price.
### altda.\*
The `altda.*` flags configure Alt-DA mode, a Beta feature of the OP Stack.
While it has received initial review from core contributors, it is still
undergoing testing, and may have bugs or other issues. See the
[Alt-DA mode guide](/chain-operators/guides/features/alt-da-mode-guide) for
setup instructions.
# Challenger configuration reference
Source: https://docs.optimism.io/chain-operators/reference/challenger-configuration
Reference for all op-challenger configuration options, covering CLI flags, environment variables, and default values.
This page catalogues every configuration option for the op-challenger, the
dispute game agent that monitors fault proof games on L1, challenges invalid
claims, and defends valid state transitions.
For step-by-step setup instructions — prestates, trace types, wallets, and
monitoring — see the
[challenger configuration guide](/chain-operators/guides/configuration/op-challenger-config-guide);
for how the fault proof system works, see the
[challenger explainer](/op-stack/fault-proofs/challenger).
## Flags
Generated from [`op-challenger/v1.9.3`](https://github.com/ethereum-optimism/optimism/releases/tag/op-challenger%2Fv1.9.3)
flag definitions. 88 flags: 4 required, 84 optional.
### Required flags
| Flag | Description | Environment variable |
| -------------- | ----------------------------------------------------------------------- | -------------------------- |
| `--datadir` | Directory to store data generated as part of responding to games | `OP_CHALLENGER_DATADIR` |
| `--l1-beacon` | Address of L1 Beacon API endpoint to use | `OP_CHALLENGER_L1_BEACON` |
| `--l1-eth-rpc` | HTTP provider URL for L1. | `OP_CHALLENGER_L1_ETH_RPC` |
| `--l2-eth-rpc` | URLs of L2 JSON-RPC endpoints to use (eth and debug namespace required) | `OP_CHALLENGER_L2_ETH_RPC` |
### Optional flags
| Flag | Description | Default | Environment variable |
| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | --------------------------------------------------- |
| `--additional-bond-claimants` | List of addresses to claim bonds for, in addition to the configured transaction sender | — | `OP_CHALLENGER_ADDITIONAL_BOND_CLAIMANTS` |
| `--cannon-bin` | Path to cannon executable to use when generating trace data (cannon game type only) | — | `OP_CHALLENGER_CANNON_BIN` |
| `--cannon-depset-config` | Interop dependency set config file (cannon game type only) | — | `OP_CHALLENGER_CANNON_DEPSET_CONFIG` |
| `--cannon-info-freq` | Frequency of cannon info log messages to generate in VM steps (cannon game type only) | `10000000` | `OP_CHALLENGER_CANNON_INFO_FREQ` |
| `--cannon-kona-depset-config` | Interop dependency set config file (cannon-kona game type only) | — | `OP_CHALLENGER_CANNON_KONA_DEPSET_CONFIG` |
| `--cannon-kona-l1-genesis` | Path to the L1 genesis file. Only required if the L1 is not mainnet, sepolia, holesky, or hoodi. | — | `OP_CHALLENGER_CANNON_KONA_L1_GENESIS` |
| `--cannon-kona-l2-genesis` | Paths to the op-geth genesis file (cannon-kona game type only) | — | `OP_CHALLENGER_CANNON_KONA_L2_GENESIS` |
| `--cannon-kona-prestate` | Path to absolute prestate to use when generating trace data (cannon-kona game type only) | — | `OP_CHALLENGER_CANNON_KONA_PRESTATE` |
| `--cannon-kona-prestates-url` | Base URL to absolute prestates to use when generating trace data. Prestates in this directory should be name as \.bin.gz \.json.gz or \.json (cannon-kona game type only) | — | `OP_CHALLENGER_CANNON_KONA_PRESTATES_URL` |
| `--cannon-kona-rollup-config` | Rollup chain parameters (cannon-kona game type only) | — | `OP_CHALLENGER_CANNON_KONA_ROLLUP_CONFIG` |
| `--cannon-kona-server` | Path to kona executable to use as pre-image oracle server when generating trace data (cannon-kona game type only) | — | `OP_CHALLENGER_CANNON_KONA_SERVER` |
| `--cannon-l1-genesis` | Path to the L1 genesis file. Only required if the L1 is not mainnet, sepolia, holesky, or hoodi. | — | `OP_CHALLENGER_CANNON_L1_GENESIS` |
| `--cannon-l2-genesis` | Paths to the op-geth genesis file (cannon game type only) | — | `OP_CHALLENGER_CANNON_L2_GENESIS` |
| `--cannon-prestate` | Path to absolute prestate to use when generating trace data (cannon game type only) | — | `OP_CHALLENGER_CANNON_PRESTATE` |
| `--cannon-prestates-url` | Base URL to absolute prestates to use when generating trace data. Prestates in this directory should be name as \.bin.gz \.json.gz or \.json (cannon game type only) | — | `OP_CHALLENGER_CANNON_PRESTATES_URL` |
| `--cannon-rollup-config` | Rollup chain parameters (cannon game type only) | — | `OP_CHALLENGER_CANNON_ROLLUP_CONFIG` |
| `--cannon-server` | Path to executable to use as pre-image oracle server when generating trace data (cannon game type only) | — | `OP_CHALLENGER_CANNON_SERVER` |
| `--cannon-snapshot-freq` | Frequency of cannon snapshots to generate in VM steps (cannon game type only) | `1000000000` | `OP_CHALLENGER_CANNON_SNAPSHOT_FREQ` |
| `--depset-config` | Interop dependency set config file | — | `OP_CHALLENGER_DEPSET_CONFIG` |
| `--fee-limit-multiplier` | The multiplier applied to fee suggestions to put a hard limit on fee increases | `5` | `OP_CHALLENGER_TXMGR_FEE_LIMIT_MULTIPLIER` |
| `--game-allowlist` | List of Fault Game contract addresses the challenger is allowed to play. If empty, the challenger will play all games. | — | `OP_CHALLENGER_GAME_ALLOWLIST` |
| `--game-factory-address` | Address of the fault game factory contract. | — | `OP_CHALLENGER_GAME_FACTORY_ADDRESS` |
| `--game-types` | The game types to support. Valid options: alphabet, cannon, cannon-kona, permissioned, fast, super-cannon-kona, super-permissioned, zk | `"cannon", "cannon-kona"` | `OP_CHALLENGER_GAME_TYPES` |
| `--game-window` | The time window which the challenger will look for games to progress and claim bonds. This should include a buffer for the challenger to claim bonds for games outside the maximum game duration. | `672h0m0s` | `OP_CHALLENGER_GAME_WINDOW` |
| `--hd-path` | The HD path used to derive the sequencer wallet from the mnemonic. The mnemonic flag must also be set. | — | `OP_CHALLENGER_HD_PATH` |
| `--http-poll-interval` | Polling interval for latest-block subscription when using an HTTP RPC provider. | `12s` | `OP_CHALLENGER_HTTP_POLL_INTERVAL` |
| `--l1-genesis` | Path to the L1 genesis file. Only required if the L1 is not mainnet, sepolia, holesky, or hoodi. | — | `OP_CHALLENGER_L1_GENESIS` |
| `--l1-rpc-kind` | The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus reduce costs. Valid options: alchemy, quicknode, infura, parity, nethermind, debug\_geth, erigon, basic, any, standard | `standard` | `OP_CHALLENGER_L1_RPC_KIND` |
| `--l2-experimental-eth-rpc` | L2 Address of L2 JSON-RPC endpoint to use (eth and debug namespace required with execution witness support) (cannon game type only) | — | `OP_CHALLENGER_L2_EXPERIMENTAL_ETH_RPC` |
| `--l2-genesis` | Paths to the op-geth genesis file | — | `OP_CHALLENGER_L2_GENESIS` |
| `--log.color` | Color the log output if in terminal mode | `false` | `OP_CHALLENGER_LOG_COLOR` |
| `--log.format` | Format the log output. Supported formats: text, terminal, logfmt, logfmtms, json, jsonms | `text` | `OP_CHALLENGER_LOG_FORMAT` |
| `--log.level` | The lowest log level that will be output | `INFO` | `OP_CHALLENGER_LOG_LEVEL` |
| `--log.pid` | Show pid in the log | `false` | `OP_CHALLENGER_LOG_PID` |
| `--max-concurrency` | Maximum number of threads to use when progressing games | `4` | `OP_CHALLENGER_MAX_CONCURRENCY` |
| `--max-pending-tx` | The maximum number of pending transactions. 0 for no limit. | `10` | `OP_CHALLENGER_MAX_PENDING_TX` |
| `--metrics.addr` | Metrics listening address | `"0.0.0.0"` | `OP_CHALLENGER_METRICS_ADDR` |
| `--metrics.enabled` | Enable the metrics server | `false` | `OP_CHALLENGER_METRICS_ENABLED` |
| `--metrics.port` | Metrics listening port | `7300` | `OP_CHALLENGER_METRICS_PORT` |
| `--min-update-interval` | Minimum time between scheduling update cycles based on the L1 block time. | `0s` | `OP_CHALLENGER_MIN_UPDATE_INTERVAL` |
| `--mnemonic` | The mnemonic used to derive the wallets for either the service | — | `OP_CHALLENGER_MNEMONIC` |
| `--network` | Predefined network selection. Available networks: every chain bundled from the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry) at the release; run `op-challenger --help` for the exact list | — | `OP_CHALLENGER_NETWORK` |
| `--network-timeout` | Timeout for all network operations | `10s` | `OP_CHALLENGER_NETWORK_TIMEOUT` |
| `--num-confirmations` | Number of confirmations which we will wait after sending a transaction | `3` | `OP_CHALLENGER_NUM_CONFIRMATIONS` |
| `--pprof.addr` | pprof listening address | `"0.0.0.0"` | `OP_CHALLENGER_PPROF_ADDR` |
| `--pprof.enabled` | Enable the pprof server | `false` | `OP_CHALLENGER_PPROF_ENABLED` |
| `--pprof.path` | pprof file path. If it is a directory, the path is \{dir}/\{profileType}.prof | — | `OP_CHALLENGER_PPROF_PATH` |
| `--pprof.port` | pprof listening port | `6060` | `OP_CHALLENGER_PPROF_PORT` |
| `--pprof.type` | pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, allocs | — | `OP_CHALLENGER_PPROF_TYPE` |
| `--prestates-url` | Base URL to absolute prestates to use when generating trace data. Prestates in this directory should be name as \.bin.gz \.json.gz or \.json | — | `OP_CHALLENGER_PRESTATES_URL` |
| `--private-key` | The private key to use with the service. Must not be used with mnemonic. | — | `OP_CHALLENGER_PRIVATE_KEY` |
| `--response-delay` | Delay before responding to game actions to slow down game progression. | `0s` | `OP_CHALLENGER_RESPONSE_DELAY` |
| `--response-delay-after` | Number of responses after which to start applying the delay (0 = from first response). | `0` | `OP_CHALLENGER_RESPONSE_DELAY_AFTER` |
| `--resubmission-timeout` | Duration we will wait before resubmitting a transaction to L1 | `24s` | `OP_CHALLENGER_RESUBMISSION_TIMEOUT` |
| `--rollup-config` | Rollup chain parameters | — | `OP_CHALLENGER_ROLLUP_CONFIG` |
| `--rollup-rpc` | HTTP provider URL for the rollup node | — | `OP_CHALLENGER_ROLLUP_RPC` |
| `--safe-abort-nonce-too-low-count` | Number of ErrNonceTooLow observations required to give up on a tx at a particular nonce without receiving confirmation | `3` | `OP_CHALLENGER_SAFE_ABORT_NONCE_TOO_LOW_COUNT` |
| `--selective-claim-resolution` | Only resolve claims for the configured claimants | `false` | `OP_CHALLENGER_SELECTIVE_CLAIM_RESOLUTION` |
| `--signer.address` | Address the signer is signing requests for | — | `OP_CHALLENGER_SIGNER_ADDRESS` |
| `--signer.endpoint` | Signer endpoint the client will connect to | — | `OP_CHALLENGER_SIGNER_ENDPOINT` |
| `--signer.header` | Headers to pass to the remote signer. Format `key=value`. Value can contain any character allowed in a HTTP header. When using env vars, split with commas. When using flags one key value pair per flag. | — | `OP_CHALLENGER_SIGNER_HEADER` |
| `--signer.tls.ca` | tls ca cert path | `"tls/ca.crt"` | `OP_CHALLENGER_SIGNER_TLS_CA` |
| `--signer.tls.cert` | tls cert path | `"tls/tls.crt"` | `OP_CHALLENGER_SIGNER_TLS_CERT` |
| `--signer.tls.enabled` | Enable or disable TLS client authentication for the signer | `true` | `OP_CHALLENGER_SIGNER_TLS_ENABLED` |
| `--signer.tls.key` | tls key | `"tls/tls.key"` | `OP_CHALLENGER_SIGNER_TLS_KEY` |
| `--super-cannon-kona-depset-config` | Interop dependency set config file (super-cannon-kona game type only) | — | `OP_CHALLENGER_SUPER_CANNON_KONA_DEPSET_CONFIG` |
| `--super-cannon-kona-l1-genesis` | Path to the L1 genesis file. Only required if the L1 is not mainnet, sepolia, holesky, or hoodi. | — | `OP_CHALLENGER_SUPER_CANNON_KONA_L1_GENESIS` |
| `--super-cannon-kona-l2-genesis` | Paths to the op-geth genesis file (super-cannon-kona game type only) | — | `OP_CHALLENGER_SUPER_CANNON_KONA_L2_GENESIS` |
| `--super-cannon-kona-prestates-url` | Base URL to absolute prestates to use when generating trace data. Prestates in this directory should be name as \.bin.gz \.json.gz or \.json (super-cannon-kona game type only) | — | `OP_CHALLENGER_SUPER_CANNON_KONA_PRESTATES_URL` |
| `--super-cannon-kona-rollup-config` | Rollup chain parameters (super-cannon-kona game type only) | — | `OP_CHALLENGER_SUPER_CANNON_KONA_ROLLUP_CONFIG` |
| `--supernode-rpc` | Provider URL for supernode roots | — | `OP_CHALLENGER_SUPERNODE_RPC` |
| `--txmgr.already-published-custom-errs` | List of custom RPC error messages that indicate that a transaction has already been published. | — | `OP_CHALLENGER_TXMGR_ALREADY_PUBLISHED_CUSTOM_ERRS` |
| `--txmgr.cell-proof-time` | Enables cell proofs in blob transactions for Fusaka (EIP-7742) compatibility from the provided unix timestamp. Should be set to the L1 Fusaka time. May be left blank for Ethereum Mainnet, Sepolia, Holesky, or Hoodi L1s. | `18446744073709551615` | `OP_CHALLENGER_TXMGR_CELL_PROOF_TIME` |
| `--txmgr.fee-limit-threshold` | The minimum threshold (in GWei) at which fee bumping starts to be capped. Allows arbitrary fee bumps below this threshold. | `100` | `OP_CHALLENGER_TXMGR_FEE_LIMIT_THRESHOLD` |
| `--txmgr.max-basefee` | Enforces a maximum base fee (in GWei) to assume when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default. | `0` | `OP_CHALLENGER_TXMGR_MAX_BASEFEE` |
| `--txmgr.max-retries` | Maximum number of times to resubmit a transaction to L1 on a transient error. Set to 0 to disable retries. | `10` | `OP_CHALLENGER_TXMGR_MAX_RETRIES` |
| `--txmgr.max-tip-cap` | Enforces a maximum tip cap (in GWei) to use when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default. | `0` | `OP_CHALLENGER_TXMGR_MAX_TIP_CAP` |
| `--txmgr.min-basefee` | Enforces a minimum base fee (in GWei) to assume when determining tx fees. 1 GWei by default. | `1` | `OP_CHALLENGER_TXMGR_MIN_BASEFEE` |
| `--txmgr.min-tip-cap` | Enforces a minimum tip cap (in GWei) to use when determining tx fees. 1 GWei by default. | `1` | `OP_CHALLENGER_TXMGR_MIN_TIP_CAP` |
| `--txmgr.not-in-mempool-timeout` | Timeout for aborting a tx send if the tx does not make it to the mempool. | `1m0s` | `OP_CHALLENGER_TXMGR_TX_NOT_IN_MEMPOOL_TIMEOUT` |
| `--txmgr.rebroadcast-interval` | Interval at which a published transaction will be rebroadcasted if it has not yet been mined. Should be less than ResubmissionTimeout to have an effect. | `0s` | `OP_CHALLENGER_TXMGR_REBROADCAST_INTERVAL` |
| `--txmgr.receipt-query-interval` | Frequency to poll for receipts | `12s` | `OP_CHALLENGER_TXMGR_RECEIPT_QUERY_INTERVAL` |
| `--txmgr.retry-interval` | Duration we will wait before resubmitting a transaction to L1 on a transient error. Values \<= 0 will result in retrying immediately. Should be less than ResubmissionTimeout to have an effect. | `1s` | `OP_CHALLENGER_TXMGR_RETRY_INTERVAL` |
| `--txmgr.send-timeout` | Timeout for sending transactions. If 0 it is disabled. | `2m0s` | `OP_CHALLENGER_TXMGR_TX_SEND_TIMEOUT` |
## Notes on selected flags
### Conditionally required flags
The required table above lists the flags op-challenger always checks at
startup. Additional flags become required depending on the configuration: the
chain must be identified by `--network` or by `--game-factory-address`, and
each enabled game type requires its trace-execution flags (for example the
`cannon-kona` game type requires the kona server and an absolute prestate via
`--cannon-kona-prestate` or `--cannon-kona-prestates-url`).
### network
When `--network` is set to a chain bundled from the superchain-registry, the
`--game-factory-address` is resolved automatically from the registry and does
not need to be set.
### game-types
Selects which dispute game types the challenger plays. Which type defends
your chain depends on the protocol version it runs; see the
[challenger configuration guide](/chain-operators/guides/configuration/op-challenger-config-guide)
for choosing game types, trace types, and the matching prestates.
### cannon-\* and cannon-kona-\*
The `cannon-*` flags apply only when a cannon-based game type is enabled, and
the `cannon-kona-*` variants only to the `cannon-kona` game type. Each of
these settings has a default variant plus a game-type-specific variant that
overrides it (the `( game type only)` suffix in the table above).
Note that op-program, the fault proof program the non-kona cannon game types
execute, has
[reached end of support](/notices/archive/op-geth-deprecation), so only the kona
variants are still being maintained.
## Utility subcommands
Beyond running the challenger service, the `op-challenger` binary provides
utility subcommands for inspecting and interacting with dispute games:
`list-games`, `list-claims`, `list-credits`, `create-game`, `move`,
`resolve`, `resolve-claim`, and `run-trace`. Each takes its own flags; run
`op-challenger --help` for details.
# How the DA footprint block limit works
Source: https://docs.optimism.io/chain-operators/reference/da-footprint
Understand the Data Availability (DA) footprint block limit introduced in the Jovian hardfork, how the DA footprint is calculated, and why the default gas scalar is 400.
The **Data Availability (DA) Footprint Block Limit** was introduced in the **[Jovian hardfork](https://docs.optimism.io/notices/upgrade-17)** to limit the total amount of transaction data that can fit into a block based on a scaled estimate of the compressed size (or "data availability footprint") of that data.
This page explains the problem the limit solves, how the DA footprint is calculated, and how the `daFootprintGasScalar` parameter shapes the limit.
To change the scalar on your chain, follow the [DA footprint setup guide](/chain-operators/guides/features/setting-da-footprint).
## Why a DA footprint limit exists
When an OP Stack chain receives more calldata-heavy transactions than can fit into the L1's available blob space, the [batcher can throttle the chain's throughput](https://docs.optimism.io/chain-operators/guides/configuration/batcher#batcher-sequencer-throttling).
However, continuous batcher throttling may cause the base fee to drop to the [minimum base fee](https://docs.optimism.io/chain-operators/guides/features/setting-min-base-fee),
causing unnecessary losses for the chain operator and negative user experiences such as priority fee auctions.
And without throttling, the batcher runs the risk of becoming overwhelmed with chain data to batch to the blob space.
Limiting the amount of (estimated compressed) calldata taken up by transactions in a block using their total DA footprint can reduce the need for batcher throttling and its related issues.
## How the DA footprint is calculated
For all [non-deposit transactions](https://docs.optimism.io/reference/glossary#deposited-transaction) processed by an OP Stack chain, a DA footprint value is recorded alongside the transaction's gas usage.
The DA footprint can be configured via the `daFootprintGasScalar` variable in the `SystemConfig` contract on the L1 chain.
The DA footprint is automatically calculated for every transaction first by calculating a `daUsageEstimate` for that transaction:
```python theme={null}
daUsageEstimate = max(
minTransactionSize,
(intercept + fastlzCoef * tx.fastlzSize) // 1e6
)
```
where the `minTransactionSize`, `intercept`, `fastlzCoef`, and `tx.fastlzSize` are as specified in the [Fjord specs](https://specs.optimism.io/protocol/fjord/exec-engine.html#fees).
Then the `daUsageEstimate` is multiplied by the `daFootprintGasScalar` to get the `daFootprint` for that individual transaction.
```python theme={null}
daFootprint += daUsageEstimate * daFootprintGasScalar
```
The `daFootprint` for all the transactions in a block are then added together to calculate that block's total `daFootprint`.
With the block's total `daFootprint` calculated:
* The block's total `daFootprint` must stay below its `gasLimit`.
* The `blobGasUsed` property of each block header is set to that block's `daFootprint`.
* The base fee update calculation then uses `gasMetered := max(gasUsed, blobGasUsed)` as a replacement for the `gasUsed` variable.
From Jovian, transaction receipts also record the transaction's DA Footprint in the receipt's `blobGasUsed` field, as well as the block's `daFootprintGasScalar` in a new field with the same name.
## What the DA footprint gas scalar controls
The *DA footprint gas scalar* scales the *estimated DA usage in bytes* to the gas dimension, and this scaled estimate of the DA usage is what we call the *DA footprint*.
This allows us to limit the estimated DA usage using the block's `gasLimit`: the effective limit of estimated DA usage per block is `gasLimit / daFootprintGasScalar` bytes.
So *increasing* this scalar makes DA usage more gas-heavy, so *decreases* the limit, and vice versa.
This is closely related to how the calldata (floor) cost of `40` gas per non-zero byte limits the total amount of calldata in a block.
A DA footprint gas scalar of `400` effectively limits *incompressible* calldata by a factor of `10` compared to its limit without a DA footprint block limit.
As such, the feature can be seen as an extension of the existing calldata limit.
But instead of repricing the calldata (floor) gas cost, the limit is accounted in parallel to EVM execution gas, and is based on the more relevant FastLZ-based DA usage estimate instead of simply counting zero and non-zero bytes.
## Why the default value is 400
The default scalar of `400` was chosen so that it protects chains in worst-case DA spam scenarios, but has negligible to no impact during normal operation.
Careful [analyses](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/da-footprint-block-limit.md) have been done to estimate the impact on current OP Stack chains and pick the right default.
Only high-throughput chains would occasionally even see a small impact from the resulting DA footprint limit (as slightly faster rising base fees). The DA footprint limit is mostly invisible.
On mid to low-throughput chains, the feature is expected to have no impact under normal usage conditions.
It acts more like an insurance to protect against worst-case *incompressible* DA spam.
A `daFootprintGasScalar` value of `0` in the `SystemConfig` is treated as the default of `400`. To effectively disable the limit, set the scalar to a very low value such as `1`.
## Next steps
* To set or change the scalar on your chain, follow [Set the DA Footprint Gas Scalar](/chain-operators/guides/features/setting-da-footprint).
* For the full set of fee-related `SystemConfig` parameters, see the [fee parameters reference](/chain-operators/reference/fee-parameters).
## References
* [DA Footprint Configuration Spec](https://specs.optimism.io/protocol/jovian/system-config.html#da-footprint-configuration)
* [Jovian Upgrade Spec](https://specs.optimism.io/protocol/jovian/overview.html)
* [SystemConfig Contract Spec](https://specs.optimism.io/protocol/system-config.html)
* [Design Doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/da-footprint-block-limit.md)
# Fee parameters
Source: https://docs.optimism.io/chain-operators/reference/fee-parameters
Reference for the fee-related SystemConfig parameters on an OP Stack chain — what each parameter controls, its setter and getter, and the OP Mainnet values.
This page catalogues the fee-related parameters a chain operator can set on the `SystemConfig` contract, from the Jovian hardfork onward.
For how fees are calculated and charged, see [transaction fees on OP Mainnet](https://docs.optimism.io/op-stack/transactions/fees#transaction-fees-on-op-mainnet).
For when and how to adjust these parameters, see the [fee tuning guide](/chain-operators/guides/management/transaction-fees-101).
Only the [SystemConfig owner](/op-stack/protocol/privileged-roles) can call the setter methods listed below.
## Fee formulas
On an OP Stack chain a transaction's **Total Fee** is made of three main components:
`Total Fee = L2 Fee + L1 Fee + Operator Fee`
| Component | Formula |
| ------------ | ----------------------------------------------------------- |
| L2 fee | `gasUsed * (baseFee + priorityFee)` |
| L1 fee | `estimatedSizeScaled * l1FeeScaled / 1e12` (see below) |
| Operator fee | `(gasUsed * operatorFeeScalar * 100) + operatorFeeConstant` |
The L1 fee charges for posting L2 data to L1, based on the transaction's estimated compressed (FastLZ) size:
```ts theme={null}
l1FeeScaled =
baseFeeScalar * 16 * l1BaseFee +
blobBaseFeeScalar * l1BlobBaseFee
estimatedSizeScaled =
max(
minTransactionSize * 1e6,
intercept + fastlzCoef * fastlzSize
)
l1Fee = estimatedSizeScaled * l1FeeScaled / 1e12
```
**Pre-Jovian (Isthmus) operator fee formula:** `operatorFee = (gasUsed * operatorFeeScalar / 1e6) + operatorFeeConstant`
## Parameters
All parameters live on the `SystemConfig` contract on L1 (see the [`ISystemConfig` interface](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol)).
Each getter below is a `view` method with the same name as the parameter.
| Parameter | Type | What it controls | Setter | OP Mainnet value |
| ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------- |
| `eip1559Denominator` | `uint32` | EIP-1559 max-change denominator: appears in the denominator of the per-block base fee delta. Lower = faster base fee response, more volatility. | `setEIP1559Params(uint32,uint32)` | `250` |
| `eip1559Elasticity` | `uint32` | Elasticity multiplier: sets the gas target relative to the gas limit (`gas_target = gasLimit / elasticity`). Larger elasticity = smaller target, so base fee increases sooner under load. | `setEIP1559Params(uint32,uint32)` | `2` |
| `gasLimit` | `uint64` | Maximum gas per block. Lower limit = less supply per block, making base fees more sensitive to demand. | `setGasLimit(uint64)` | `40,000,000` |
| `minBaseFee` | `uint64` | Minimum floor on the base fee, in wei (can be 0). See [setting the minimum base fee](/chain-operators/guides/features/setting-min-base-fee). | `setMinBaseFee(uint64)` | `0` |
| `basefeeScalar` | `uint32` | Scales the `l1BaseFee` (per-byte) contribution to the L1 data fee. | `setGasConfigEcotone(uint32,uint32)` | `5227` |
| `blobbasefeeScalar` | `uint32` | Scales the `l1BlobBaseFee` (per-blob) contribution to the L1 data fee. | `setGasConfigEcotone(uint32,uint32)` | `1014213` |
| `operatorFeeScalar` | `uint32` | Per-gas operator margin. See [setting the operator fee](/chain-operators/guides/features/setting-operator-fee). | `setOperatorFeeScalars(uint32,uint64)` | `0` |
| `operatorFeeConstant` | `uint64` | Flat per-transaction operator fee, in wei. | `setOperatorFeeScalars(uint32,uint64)` | `0` |
| `daFootprintGasScalar` | `uint16` | Scales estimated DA usage to the gas dimension to limit DA-heavy blocks. See [how the DA footprint block limit works](/chain-operators/reference/da-footprint). | `setDAFootprintGasScalar(uint16)` | `0` (treated as `400`) |
* Any non-zero operator fee makes the chain configuration [non-standard](/op-stack/protocol/superchain-registry#what-is-a-standard-chain).
* A `daFootprintGasScalar` of `0` is treated as the default of `400`; set it to `1` to effectively disable the DA footprint limit.
The OP Mainnet values can be verified against the [OP Mainnet SystemConfig contract on Etherscan](https://etherscan.io/address/0x229047fed2591dbec1eF1118d64F7aF3dB9EB290#readProxyContract).
## Fee vault recipients
Fees are gathered in dedicated contract [fee vaults](/op-stack/transactions/fee-vaults). See the [fee vault operations guide](/chain-operators/guides/management/fee-vaults) for how to manage and withdraw from these vaults.
| Fee component | Recipient vault |
| ------------- | ------------------- |
| `baseFee` | `BaseFeeVault` |
| `priorityFee` | `SequencerFeeVault` |
| `l1Fee` | `L1FeeVault` |
| `operatorFee` | `OperatorFeeVault` |
## Reading current values
Each parameter has a getter with the same name on the `SystemConfig` contract:
```bash theme={null}
export L1_RPC=
export SYSTEM_CONFIG=
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "eip1559Denominator()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "eip1559Elasticity()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "gasLimit()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "minBaseFee()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "basefeeScalar()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "blobbasefeeScalar()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "operatorFeeScalar()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "operatorFeeConstant()"
cast call --rpc-url $L1_RPC $SYSTEM_CONFIG "daFootprintGasScalar()"
```
## References
* [Transaction fees on OP Mainnet](https://docs.optimism.io/op-stack/transactions/fees#transaction-fees-on-op-mainnet)
* [Operator fee](https://docs.optimism.io/op-stack/transactions/fees#operator-fee)
* [L1 data fee](https://docs.optimism.io/op-stack/transactions/fees#l1-data-fee)
* [`ISystemConfig` interface](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol)
* [DA footprint](https://docs.optimism.io/notices/archive/upgrade-17#block-header-changes)
* [EIP-1559 parameters](https://docs.optimism.io/op-stack/protocol/differences#eip-1559-parameters)
# OP Contracts Manager
Source: https://docs.optimism.io/chain-operators/reference/opcm
Understand what OP Contracts Manager is, why it exists, and how it deploys and upgrades the L1 contracts for OP Stack chains in a single transaction.
The OP Contracts Manager is a contract that deploys the L1 contracts for an OP Stack chain in a single transaction. It provides a minimal set of user-configurable parameters to ensure that the resulting chain meets the standard configuration requirements. Additionally, as of [Upgrade 13](https://gov.optimism.io/t/upgrade-proposal-13-opcm-and-incident-response-improvements/9739), instances of OPCM can upgrade existing OP Stack chains.
The version deployed is always a governance-approved contract release. The set of governance approved contract releases can be found on the Optimism Monorepo releases page, and is the set of releases named `op-contracts/vX.Y.Z`. It deploys the [Fault Proof System](/op-stack/fault-proofs/explainer), using the [PermissionedDisputeGame](/op-stack/protocol/smart-contracts#proposals-and-fault-proofs).
## Purpose
OPCM simplifies the L1 contract deployments for new OP Stack chains. For each smart contract release there will be a new OPCM instance. It addresses three aspects of deploying the OP Stack's L1 contracts:
1. **Deploy Shared Contracts.** Shared contracts are used between many OP chains, so this occurs only occasionally in production.
2. **Deploy Shared Implementation Contracts.** This occurs once per contracts release in production.
3. **Deploy OP Chain Contracts.** This occurs for every OP chain deployment in production.
Additionally, after the Upgrade 13 network upgrade, OPCM instances will be used to upgrade existing OP Stack chains.
## Learn more
* Checkout the [OPCM specs](https://specs.optimism.io/experimental/op-contracts-manager.html?utm_source=op-docs\&utm_medium=docs)
* Checkout the [OPCM design document](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/op-contracts-manager-arch.md)
# Rollup deployment configuration
Source: https://docs.optimism.io/chain-operators/reference/rollup-deployment-configuration
Reference for the OP Stack rollup deployment configuration values.
This page is the reference for the values in the `DeployConfig` — the flat JSON
configuration that sets the L1 contract parameters and the L2 genesis state when
an OP Stack chain is deployed.
The schema tables below are generated directly from the `DeployConfig` Go
struct tree in the monorepo (`op-chain-ops/genesis`) at a finalized op-deployer
release, so the field list, JSON keys, types, and descriptions cannot silently
fall behind the source. Recommended values and standard-configuration
requirements are policy rather than code, and are maintained by hand in the
[guidance section](#guidance-and-standard-configuration-requirements) below.
**The recommended way to deploy an OP Stack chain is [OP Deployer](/chain-operators/tools/op-deployer/overview).**
OP Deployer takes a declarative [intent file](/chain-operators/tools/op-deployer/usage/init) (`intent.toml`)
rather than a hand-written `DeployConfig` JSON, and derives the `DeployConfig`
documented here from that intent as part of its deployment pipeline. Most chain
operators set the intent file and never edit a `DeployConfig` directly.
This page documents the underlying `DeployConfig` surface: the exhaustive set of
values a deployment ultimately resolves to. Use it to understand what an intent
setting maps to, to interpret the `DeployConfig` that OP Deployer can emit for an
applied deployment (via `op-deployer inspect deploy-config`), or as background for
the lower-level [custom deployments](/chain-operators/tools/op-deployer/usage/custom-deployments)
OP Deployer also supports.
Deploy-config values are largely immutable after a chain is deployed, so this page targets the values you set at genesis.
The output-oracle proposal fields (the [`l2OutputOracle*` values](#legacy-output-oracle)) describe the legacy `L2OutputOracle` proposal system and apply only to chains still on that system. Chains using [permissionless fault proofs](/op-stack/fault-proofs/explainer) — the standard configuration for new OP Stack chains — set the [fault-proof deployment values](#fault-proofs) instead: `useFaultProofs` together with the `faultGame*`, `preimageOracle*`, `proofMaturityDelaySeconds`, `disputeGameFinalityDelaySeconds`, and `respectedGameType` fields.
Standard configuration is the set of requirements for an OP Stack chain to be
considered a Standard Chain within the OP Stack. These requirements are
currently a draft, pending governance approval. For more details, please see
this [governance thread](https://gov.optimism.io/t/season-6-draft-standard-rollup-charter/8135)
and the actual requirements in the [OP Stack Configurability Specification](https://specs.optimism.io/protocol/configurability.html?utm_source=op-docs\&utm_medium=docs).
## Configuration values
Generated from the [`DeployConfig` struct](https://github.com/ethereum-optimism/optimism/blob/op-deployer%2Fv0.7.1/op-chain-ops/genesis/config.go)
in `op-chain-ops/genesis` at [`op-deployer/v0.7.1`](https://github.com/ethereum-optimism/optimism/releases/tag/op-deployer%2Fv0.7.1):
122 values in 20 groups. The JSON key for each value is its `json:"..."` struct
tag there; descriptions are the Go doc comments. When this page and the source
disagree, the source wins.
### Development accounts
| JSON key | Type | Description |
| ----------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `fundDevAccounts` | boolean | FundDevAccounts configures whether to fund the dev accounts. This should only be used during devnet deployments. |
### L2 genesis block
| JSON key | Type | Description |
| ----------------------------- | -------------------------------- | ----------- |
| `l2GenesisBlockNonce` | number (hex-encoded) | — |
| `l2GenesisBlockGasLimit` | number (hex-encoded) | — |
| `l2GenesisBlockDifficulty` | number (hex-encoded big integer) | — |
| `l2GenesisBlockMixHash` | 32-byte hash | — |
| `l2GenesisBlockNumber` | number (hex-encoded) | — |
| `l2GenesisBlockGasUsed` | number (hex-encoded) | — |
| `l2GenesisBlockParentHash` | 32-byte hash | — |
| `l2GenesisBlockBaseFeePerGas` | number (hex-encoded big integer) | — |
### Ownership
OwnershipDeployConfig defines the ownership of an L2 chain deployment. This excludes superchain-wide contracts.
| JSON key | Type | Description |
| ------------------ | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `proxyAdminOwner` | address | ProxyAdminOwner represents the owner of the ProxyAdmin predeploy on L2. |
| `finalSystemOwner` | address | FinalSystemOwner is the owner of the system on L1. Any L1 contract that is ownable has this account set as its owner. |
### Fee vaults
| JSON key | Type | Description |
| ------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `baseFeeVaultRecipient` | address | BaseFeeVaultRecipient represents the recipient of fees accumulated in the BaseFeeVault. Can be an account on L1 or L2, depending on the BaseFeeVaultWithdrawalNetwork value. |
| `l1FeeVaultRecipient` | address | L1FeeVaultRecipient represents the recipient of fees accumulated in the L1FeeVault. Can be an account on L1 or L2, depending on the L1FeeVaultWithdrawalNetwork value. |
| `sequencerFeeVaultRecipient` | address | SequencerFeeVaultRecipient represents the recipient of fees accumulated in the SequencerFeeVault. Can be an account on L1 or L2, depending on the SequencerFeeVaultWithdrawalNetwork value. |
| `operatorFeeVaultRecipient` | address | OperatorFeeVaultRecipient represents the recipient of fees accumulated in the OperatorFeeVault. |
| `baseFeeVaultMinimumWithdrawalAmount` | number (hex-encoded big integer) | BaseFeeVaultMinimumWithdrawalAmount represents the minimum withdrawal amount for the BaseFeeVault. |
| `l1FeeVaultMinimumWithdrawalAmount` | number (hex-encoded big integer) | L1FeeVaultMinimumWithdrawalAmount represents the minimum withdrawal amount for the L1FeeVault. |
| `sequencerFeeVaultMinimumWithdrawalAmount` | number (hex-encoded big integer) | SequencerFeeVaultMinimumWithdrawalAmount represents the minimum withdrawal amount for the SequencerFeeVault. |
| `operatorFeeVaultMinimumWithdrawalAmount` | number (hex-encoded big integer) | OperatorFeeVaultMinimumWithdrawalAmount represents the minimum withdrawal amount for the OperatorFeeVault. |
| `baseFeeVaultWithdrawalNetwork` | string: "remote" (withdraw to L1) or "local" (withdraw to L2); legacy 0/1 accepted | BaseFeeVaultWithdrawalNetwork represents the withdrawal network for the BaseFeeVault. |
| `l1FeeVaultWithdrawalNetwork` | string: "remote" (withdraw to L1) or "local" (withdraw to L2); legacy 0/1 accepted | L1FeeVaultWithdrawalNetwork represents the withdrawal network for the L1FeeVault. |
| `sequencerFeeVaultWithdrawalNetwork` | string: "remote" (withdraw to L1) or "local" (withdraw to L2); legacy 0/1 accepted | SequencerFeeVaultWithdrawalNetwork represents the withdrawal network for the SequencerFeeVault. |
| `operatorFeeVaultWithdrawalNetwork` | string: "remote" (withdraw to L1) or "local" (withdraw to L2); legacy 0/1 accepted | OperatorFeeVaultWithdrawalNetwork represents the withdrawal network for the OperatorFeeVault. |
### Governance token
GovernanceDeployConfig is exclusive to OP-Mainnet and the testing of OP-Mainnet-like chains.
| JSON key | Type | Description |
| ----------------------- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `enableGovernance` | boolean | EnableGovernance configures whether or not include governance token predeploy. |
| `governanceTokenSymbol` | string | GovernanceTokenSymbol represents the ERC20 symbol of the GovernanceToken. |
| `governanceTokenName` | string | GovernanceTokenName represents the ERC20 name of the GovernanceToken |
| `governanceTokenOwner` | address | GovernanceTokenOwner represents the owner of the GovernanceToken. Has the ability to mint and burn tokens. |
### Gas price oracle
GasPriceOracleDeployConfig configures the GasPriceOracle L2 predeploy.
| JSON key | Type | Description |
| ----------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gasPriceOracleOverhead` | number | GasPriceOracleOverhead represents the initial value of the gas overhead in the GasPriceOracle predeploy. Deprecated: Since Ecotone, this field is superseded by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar. |
| `gasPriceOracleScalar` | number | GasPriceOracleScalar represents the initial value of the gas scalar in the GasPriceOracle predeploy. Deprecated: Since Ecotone, this field is superseded by GasPriceOracleBaseFeeScalar and GasPriceOracleBlobBaseFeeScalar. |
| `gasPriceOracleBaseFeeScalar` | number | GasPriceOracleBaseFeeScalar represents the value of the base fee scalar used for fee calculations. |
| `gasPriceOracleBlobBaseFeeScalar` | number | GasPriceOracleBlobBaseFeeScalar represents the value of the blob base fee scalar used for fee calculations. |
| `gasPriceOracleOperatorFeeScalar` | number | GasPriceOracleOperatorFeeScalar represents the value of the operator fee scalar used for fee calculations. |
| `gasPriceOracleOperatorFeeConstant` | number | GasPriceOracleOperatorFeeConstant represents the value of the operator fee constant used for fee calculations. |
### Custom gas token
GasTokenDeployConfig configures the optional custom gas token functionality.
| JSON key | Type | Description |
| ---------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `useCustomGasToken` | boolean | UseCustomGasToken is a flag to indicate that a custom gas token should be used |
| `gasPayingTokenName` | string | GasPayingTokenName represents the custom gas token name. |
| `gasPayingTokenSymbol` | string | GasPayingTokenSymbol represents the custom gas token symbol. |
| `nativeAssetLiquidityAmount` | number (hex-encoded big integer) | NativeAssetLiquidityAmount represents the amount of liquidity to pre-fund the NativeAssetLiquidity contract with. |
| `liquidityControllerOwner` | address | LiquidityControllerOwner represents the owner of the LiquidityController. |
### Operator addresses
OperatorDeployConfig configures the hot-key addresses for operations such as sequencing and batch-submission.
| JSON key | Type | Description |
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `p2pSequencerAddress` | address | P2PSequencerAddress is the address of the key the sequencer uses to sign blocks on the P2P layer. |
| `batchSenderAddress` | address | BatchSenderAddress represents the initial sequencer account that authorizes batches. Transactions sent from this account to the batch inbox address are considered valid. |
### EIP-1559 fee market
EIP1559DeployConfig configures the EIP-1559 parameters of the chain.
| JSON key | Type | Description |
| -------------------------- | ------ | --------------------------------------------------------------------------------------------- |
| `eip1559Elasticity` | number | EIP1559Elasticity is the elasticity of the EIP1559 fee market. |
| `eip1559Denominator` | number | EIP1559Denominator is the denominator of EIP1559 base fee market. |
| `eip1559DenominatorCanyon` | number | EIP1559DenominatorCanyon is the denominator of EIP1559 base fee market when Canyon is active. |
### Network upgrade (hardfork) activations
UpgradeScheduleDeployConfig configures when network upgrades activate.
| JSON key | Type | Description |
| --------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `l2GenesisRegolithTimeOffset` | number (hex-encoded), nullable | L2GenesisRegolithTimeOffset is the number of seconds after genesis block that Regolith hard fork activates. Set it to 0 to activate at genesis. Nil to disable Regolith. |
| `l2GenesisCanyonTimeOffset` | number (hex-encoded), nullable | L2GenesisCanyonTimeOffset is the number of seconds after genesis block that Canyon hard fork activates. Set it to 0 to activate at genesis. Nil to disable Canyon. |
| `l2GenesisDeltaTimeOffset` | number (hex-encoded), nullable | L2GenesisDeltaTimeOffset is the number of seconds after genesis block that Delta hard fork activates. Set it to 0 to activate at genesis. Nil to disable Delta. |
| `l2GenesisEcotoneTimeOffset` | number (hex-encoded), nullable | L2GenesisEcotoneTimeOffset is the number of seconds after genesis block that Ecotone hard fork activates. Set it to 0 to activate at genesis. Nil to disable Ecotone. |
| `l2GenesisFjordTimeOffset` | number (hex-encoded), nullable | L2GenesisFjordTimeOffset is the number of seconds after genesis block that Fjord hard fork activates. Set it to 0 to activate at genesis. Nil to disable Fjord. |
| `l2GenesisGraniteTimeOffset` | number (hex-encoded), nullable | L2GenesisGraniteTimeOffset is the number of seconds after genesis block that Granite hard fork activates. Set it to 0 to activate at genesis. Nil to disable Granite. |
| `l2GenesisHoloceneTimeOffset` | number (hex-encoded), nullable | L2GenesisHoloceneTimeOffset is the number of seconds after genesis block that the Holocene hard fork activates. Set it to 0 to activate at genesis. Nil to disable Holocene. |
| `l2GenesisIsthmusTimeOffset` | number (hex-encoded), nullable | L2GenesisIsthmusTimeOffset is the number of seconds after genesis block that the Isthmus hard fork activates. Set it to 0 to activate at genesis. Nil to disable Isthmus. |
| `l2GenesisJovianTimeOffset` | number (hex-encoded), nullable | L2GenesisJovianTimeOffset is the number of seconds after genesis block that the Jovian hard fork activates. Set it to 0 to activate at genesis. Nil to disable Jovian. |
| `l2GenesisKarstTimeOffset` | number (hex-encoded), nullable | L2GenesisKarstTimeOffset is the number of seconds after genesis block that the Karst hard fork activates. Set it to 0 to activate at genesis. Nil to disable Karst. |
| `l2GenesisInteropTimeOffset` | number (hex-encoded), nullable | L2GenesisInteropTimeOffset is the number of seconds after genesis block that the Interop hard fork activates. Set it to 0 to activate at genesis. Nil to disable Interop. |
| `l2GenesisPectraBlobScheduleTimeOffset` | number (hex-encoded), nullable | L2GenesisPectraBlobScheduleTimeOffset is the number of seconds after genesis block that the PectraBlobSchedule fix activates. Set it to 0 to activate at genesis. Nil to disable the PectraBlobSchedule fix. |
| `l1CancunTimeOffset` | number (hex-encoded), nullable | When Cancun activates. Relative to L1 genesis. |
| `l1PragueTimeOffset` | number (hex-encoded), nullable | When Prague activates. Relative to L1 genesis. |
| `l1OsakaTimeOffset` | number (hex-encoded), nullable | When Osaka activates. Relative to L1 genesis. |
| `l1BPO1TimeOffset` | number (hex-encoded), nullable | When BPO1 activates. Relative to L1 genesis. |
| `l1BPO2TimeOffset` | number (hex-encoded), nullable | When BPO2 activates. Relative to L1 genesis. |
| `l1BPO3TimeOffset` | number (hex-encoded), nullable | When BPO3 activates. Relative to L1 genesis. |
| `l1BPO4TimeOffset` | number (hex-encoded), nullable | When BPO4 activates. Relative to L1 genesis. |
| `l1BlobScheduleConfig` | object (go-ethereum blob schedule) | Blob schedule config. |
### Core protocol parameters
L2CoreDeployConfig configures the core protocol parameters of the chain.
| JSON key | Type | Description |
| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `l1ChainID` | number | L1ChainID is the chain ID of the L1 chain. |
| `l2ChainID` | number | L2ChainID is the chain ID of the L2 chain. |
| `l2BlockTime` | number | L2BlockTime is the number of seconds between each L2 block. |
| `finalizationPeriodSeconds` | number | FinalizationPeriodSeconds represents the number of seconds before an output is considered finalized. This impacts the amount of time that withdrawals take to finalize and is generally set to 1 week. |
| `maxSequencerDrift` | number | MaxSequencerDrift is the number of seconds after the L1 timestamp of the end of the sequencing window that batches must be included, otherwise L2 blocks including deposits are force included. |
| `sequencerWindowSize` | number | SequencerWindowSize is the number of L1 blocks per sequencing window. |
| `channelTimeout` | number | ChannelTimeoutBedrock is the number of L1 blocks that a frame stays valid when included in L1. |
| `batchInboxAddress` | address | BatchInboxAddress is the L1 account that batches are sent to. |
| `systemConfigStartBlock` | number | SystemConfigStartBlock represents the block at which the op-node should start syncing from. It is an override to set this value on legacy networks where it is not set by default. It can be removed once all networks have this value set in their storage. |
### Fee market limits
| JSON key | Type | Description |
| ---------------------- | ------ | --------------------------------------------------------------------------------- |
| `minBaseFee` | number | MinBaseFee is the minimum base applied to each block. |
| `daFootprintGasScalar` | number | DAFootprintGasScalar is the scalar used to compute the DAFootprint of each block. |
### Alt-DA mode
AltDADeployConfig configures optional AltDA functionality.
| JSON key | Type | Description |
| ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `useAltDA` | boolean | UseAltDA is a flag that indicates if the system is using op-alt-da |
| `daCommitmentType` | string | DACommitmentType specifies the allowed commitment |
| `daChallengeWindow` | number | DAChallengeWindow represents the block interval during which the availability of a data commitment can be challenged. |
| `daResolveWindow` | number | DAResolveWindow represents the block interval during which a data availability challenge can be resolved. |
| `daBondSize` | number | DABondSize represents the required bond size to initiate a data availability challenge. |
| `daResolverRefundPercentage` | number | DAResolverRefundPercentage represents the percentage of the resolving cost to be refunded to the resolver such as 100 means 100% refund. |
### Development L1 genesis
DevL1DeployConfig is used to configure a L1 chain for development/testing purposes. A production L2 deployment does not utilize this configuration, except of a L1BlockTime sanity-check (set this to 12 for L1 Ethereum).
| JSON key | Type | Description |
| ----------------------------- | -------------------------------- | ----------- |
| `l1BlockTime` | number | — |
| `l1GenesisBlockTimestamp` | number (hex-encoded) | — |
| `l1GenesisBlockNonce` | number (hex-encoded) | — |
| `l1GenesisBlockGasLimit` | number (hex-encoded) | — |
| `l1GenesisBlockDifficulty` | number (hex-encoded big integer) | — |
| `l1GenesisBlockMixHash` | 32-byte hash | — |
| `l1GenesisBlockCoinbase` | address | — |
| `l1GenesisBlockNumber` | number (hex-encoded) | — |
| `l1GenesisBlockGasUsed` | number (hex-encoded) | — |
| `l1GenesisBlockParentHash` | 32-byte hash | — |
| `l1GenesisBlockBaseFeePerGas` | number (hex-encoded big integer) | — |
| `l1GenesisBlockExcessBlobGas` | number (hex-encoded), nullable | — |
| `l1GenesisBlockblobGasUsed` | number (hex-encoded), nullable | — |
### L1 starting block
| JSON key | Type | Description |
| -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `l1StartingBlockTag` | L1 block number, tag, or hash | L1StartingBlockTag anchors the L2 at an L1 block. The timestamp of the block referenced by l1StartingBlockTag is used in the L2 genesis block, rollup-config, and L1 output-oracle contract. The Output oracle deploy script may use it if the L2 starting timestamp is nil, assuming the L2 genesis is set up with this. The L2 genesis timestamp does not affect the initial L2 account state: the storage of the L1Block contract at genesis is zeroed, since the adoption of the L2-genesis allocs-generation through solidity script. |
### Superchain configuration
SuperchainL1DeployConfig configures parameters of the superchain-wide deployed contracts to L1. This deployment is global, and can be reused between L2s that target the same superchain.
| JSON key | Type | Description |
| -------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `superchainConfigGuardian` | address | SuperchainConfigGuardian represents the GUARDIAN account in the SuperchainConfig. Has the ability to pause withdrawals. |
### Legacy output oracle
OutputOracleDeployConfig configures the legacy OutputOracle deployment to L1. This is obsoleted with Fault Proofs. See FaultProofDeployConfig.
| JSON key | Type | Description |
| ----------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `l2OutputOracleSubmissionInterval` | number | L2OutputOracleSubmissionInterval is the number of L2 blocks between outputs that are submitted to the L2OutputOracle contract located on L1. |
| `l2OutputOracleStartingTimestamp` | number | L2OutputOracleStartingTimestamp is the starting timestamp for the L2OutputOracle. MUST be the same as the timestamp of the L2OO start block. |
| `l2OutputOracleStartingBlockNumber` | number | L2OutputOracleStartingBlockNumber is the starting block number for the L2OutputOracle. Must be greater than or equal to the first Bedrock block. The first L2 output will correspond to this value plus the submission interval. |
| `l2OutputOracleProposer` | address | L2OutputOracleProposer is the address of the account that proposes L2 outputs. |
| `l2OutputOracleChallenger` | address | L2OutputOracleChallenger is the address of the account that challenges L2 outputs. |
### Fault proofs
FaultProofDeployConfig configures the fault-proof deployment to L1.
| JSON key | Type | Description |
| --------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `useFaultProofs` | boolean | UseFaultProofs is a flag that indicates if the system is using fault proofs instead of the older output oracle mechanism. |
| `faultGameAbsolutePrestate` | 32-byte hash | FaultGameAbsolutePrestate is the absolute prestate of Cannon. This is computed by generating a proof from the 0th -> 1st instruction and grabbing the prestate from the output JSON. All honest challengers should agree on the setup state of the program. |
| `faultGameMaxDepth` | number | FaultGameMaxDepth is the maximum depth of the position tree within the fault dispute game. `2^{FaultGameMaxDepth}` is how many instructions the execution trace bisection game supports. Ideally, this should be conservatively set so that there is always enough room for a full Cannon trace. |
| `faultGameClockExtension` | number | FaultGameClockExtension is the amount of time that the dispute game will set the potential grandchild claim's, clock to, if the remaining time is less than this value at the time of a claim's creation. |
| `faultGameMaxClockDuration` | number | FaultGameMaxClockDuration is the maximum amount of time that may accumulate on a team's chess clock before they may no longer respond. |
| `faultGameGenesisBlock` | number | FaultGameGenesisBlock is the block number for genesis. |
| `faultGameGenesisOutputRoot` | 32-byte hash | FaultGameGenesisOutputRoot is the output root for the genesis block. |
| `faultGameSplitDepth` | number | FaultGameSplitDepth is the depth at which the fault dispute game splits from output roots to execution trace claims. |
| `faultGameWithdrawalDelay` | number | FaultGameWithdrawalDelay is the number of seconds that users must wait before withdrawing ETH from a fault game. |
| `preimageOracleMinProposalSize` | number | PreimageOracleMinProposalSize is the minimum number of bytes that a large preimage oracle proposal can be. |
| `preimageOracleChallengePeriod` | number | PreimageOracleChallengePeriod is the number of seconds that challengers have to challenge a large preimage proposal. |
| `proofMaturityDelaySeconds` | number | ProofMaturityDelaySeconds is the number of seconds that a proof must be mature before it can be used to finalize a withdrawal. |
| `disputeGameFinalityDelaySeconds` | number | DisputeGameFinalityDelaySeconds is an additional number of seconds a dispute game must wait before it can be used to finalize a withdrawal. |
| `respectedGameType` | number | RespectedGameType is the dispute game type that the OptimismPortal contract will respect for finalizing withdrawals. |
### L1 dependency addresses
L1DependenciesConfig is the set of addresses that affect the L2 genesis construction, and is dependent on prior deployment of contracts to L1. This is generally not configured in deploy-config JSON, but rather merged in through a L1 deployments JSON file.
| JSON key | Type | Description |
| ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `l1StandardBridgeProxy` | address | L1StandardBridgeProxy represents the address of the L1StandardBridgeProxy on L1 and is used as part of building the L2 genesis state. |
| `l1CrossDomainMessengerProxy` | address | L1CrossDomainMessengerProxy represents the address of the L1CrossDomainMessengerProxy on L1 and is used as part of building the L2 genesis state. |
| `l1ERC721BridgeProxy` | address | L1ERC721BridgeProxy represents the address of the L1ERC721Bridge on L1 and is used as part of building the L2 genesis state. |
| `systemConfigProxy` | address | SystemConfigProxy represents the address of the SystemConfigProxy on L1 and is used as part of the derivation pipeline. |
| `optimismPortalProxy` | address | OptimismPortalProxy represents the address of the OptimismPortalProxy on L1 and is used as part of the derivation pipeline. |
| `daChallengeProxy` | address | DAChallengeProxy represents the L1 address of the DataAvailabilityChallenge contract. |
### Legacy fields
LegacyDeployConfig retains legacy DeployConfig attributes. The genesis generation may log warnings, do a best-effort support attempt, or ignore these attributes completely.
| JSON key | Type | Description |
| ----------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `deploymentWaitConfirmations` | number | DeploymentWaitConfirmations is the number of confirmations to wait during deployment. This is DEPRECATED and should be removed in a future PR. |
| `channelTimeoutGranite` | number | — |
## Guidance and standard-configuration requirements
The tables above are the exhaustive schema. This section carries the
hand-maintained guidance for the values chain operators most often need to
reason about: recommended values, validation constraints, and the
standard-configuration requirements from the draft
[Standard Rollup Charter](https://gov.optimism.io/t/season-6-draft-standard-rollup-charter/8135).
### Hardfork activation guidance
* The standard configuration requires network upgrades (hardforks) to be
activated. New chains set each `l2GenesisTimeOffset` to `"0x0"` so
every governance-approved upgrade is active at genesis; a nil (omitted)
offset disables the fork.
* Later forks cannot activate before earlier ones, and two forks cannot
activate at the same post-genesis time.
* The interoperability hardfork activation offset is a non-standard feature:
interoperability is still [experimental](https://specs.optimism.io/interop/overview.html?utm_source=op-docs\&utm_medium=docs).
### Roles and ownership requirements
* `finalSystemOwner`: must not be `address(0)`. It is recommended to have a
single admin address to retain a common security model. Standard
configuration: must be the Chain Governor or Servicer; however, the L1
ProxyAdmin owner must be held by the Optimism Security Council. At the
moment, the L1 ProxyAdmin owner is transferred from the Chain Governor or
Servicer to [0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A](https://etherscan.io/address/0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A).
* `proxyAdminOwner`: owns the `ProxyAdmin` predeploy on L2, which owns all of
the Proxy contracts for every predeployed contract in the range
`0x42...0000` to `0x42...2048`, making predeploys upgradeable. Must not be
`address(0)`; a single admin address is recommended.
* `superchainConfigGuardian`: standard configuration requires
[0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2](https://etherscan.io/address/0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2),
a 1/1 Safe owned by the Security Council Safe, with the
[Deputy Pause Module](https://specs.optimism.io/protocol/deputy-pause-module.html?utm_source=op-docs\&utm_medium=docs)
enabled to allow the Optimism Foundation to act as Pause Deputy.
* `p2pSequencerAddress` and `batchSenderAddress`: hot-key addresses you
control — the sequencer's block-signing key and the batcher account whose
transactions to the batch inbox are considered valid. Neither may be
`address(0)`. `batchSenderAddress` can be updated later via the
`SystemConfig` contract on L1. No standard requirement.
* The [L1 dependency addresses](#l1-dependency-addresses)
(`l1StandardBridgeProxy`, `l1CrossDomainMessengerProxy`,
`l1ERC721BridgeProxy`, `systemConfigProxy`, `optimismPortalProxy`): filled
in from the L1 deployment rather than hand-configured; none of them may be
`address(0)`. Standard
configuration requires the implementation contracts to be the most
up-to-date, governance-approved version of the OP Stack codebase — and, if
the chain has been upgraded in the past, that the previous versions were a
standard release of the codebase.
### Sequencing and batching guidance
* `l2BlockTime`: must be nonzero, a whole number, and less than the L1 block
time (12 seconds on Ethereum mainnet and Sepolia). Standard configuration:
1 or 2 seconds.
* `maxSequencerDrift`: must be nonzero. 1800 (30 minutes) is the constant
that takes effect with the
[Fjord activation](/op-stack/protocol/network-upgrades#activations).
* `sequencerWindowSize`: must be nonzero. Standard configuration: 3\_600 base
layer blocks (12 hours for an L2 on Ethereum, assuming 12 second L1
blocktime). This is an important value for constraining the sequencer's
ability to re-order transactions; higher values would pose a risk to user
protections.
* `channelTimeout`: the default of 50 was introduced in the
[Granite network upgrade](/op-stack/protocol/network-upgrades#activations).
* `batchInboxAddress`: standard configuration convention is
`versionByte || keccak256(bytes32(chainId))[:19]`, where `||` denotes
concatenation, `versionByte` is `0x00`, and `chainId` is a `uint256`. This
covers the full range of chain IDs, up to the full `uint256` size:
```solidity theme={null}
bytes32 hash = keccak256(abi.encodePacked(bytes32(uint256(chainId))));
// take the first 19 bytes of the hash, then prepend a version byte of 0x00
// batchInboxAddress = 0x00{hash[:19]}
```
* `systemConfigStartBlock`: standard configuration: the block where the
`SystemConfig` was initialized.
* `l1StartingBlockTag`: it is generally recommended to use a finalized L1
block to avoid issues with reorgs.
### Chain ID requirements
* `l1ChainID`: must be nonzero. 1 for Ethereum mainnet, 11155111 for the
Sepolia test network; see [chainlist](https://chainlist.org/?testnets=true)
for other networks. Standard configuration: 1 (Ethereum).
* `l2ChainID`: must be nonzero and, for security reasons, unique. Standard
configuration: a Foundation-approved, globally unique value. Chains should
add their chain IDs to
[ethereum-lists/chains](https://github.com/ethereum-lists/chains).
### Gas and fee guidance
* `l2GenesisBlockGasLimit`: must be nonzero and greater than
`MaxResourceLimit + SystemTxMaxGas` (the gas a deposit plus the system
transaction can use). Standard configuration: no higher than 200\_000\_000
gas; chain operators are driven to maintain a stable and reliable chain,
so careful deliberation is necessary when considering a change.
* `l2GenesisBlockBaseFeePerGas`: cannot be nil.
* `gasPriceOracleBaseFeeScalar` and `gasPriceOracleBlobBaseFeeScalar`: should
not be 0. Standard configuration: set such that the fee margin is between
0 and 50%. See [transaction fees](/op-stack/transactions/fees) for how the
scalars enter the fee calculation.
* `eip1559Elasticity` and `eip1559Denominator`: must be nonzero.
`eip1559DenominatorCanyon` must be nonzero if Canyon is activated; 250 is
the recommended value.
* `minBaseFee`: an absolute minimum base fee in wei, to help shorten the
length of priority fee auctions; 0 disables it (the default). 100000 wei
is the recommended value; setting the minimum too high may make
transactions harder to get included for users. Standard configuration:
must not be set higher than 10000000000 wei. See the
[minimum base fee specs](https://specs.optimism.io/protocol/jovian/exec-engine.html#minimum-base-fee)
for more detail.
* `daFootprintGasScalar`: multiplied by the DA usage estimate to limit the
total estimated compressed transaction data that can fit into a block; 400
is the recommended value. As of Jovian, the base fee update calculation
uses `gasMetered := max(gasUsed, blobGasUsed)` in place of `gasUsed`, so
blocks with high DA usage may cause the base fee to increase in subsequent
blocks. See the
[DA footprint block limit specs](https://specs.optimism.io/protocol/jovian/exec-engine.html#da-footprint-block-limit)
for more detail.
### Fee vault guidance
* The `*FeeVaultRecipient` addresses must not be `address(0)`; a single
admin address is recommended to retain a common security model.
* The `*FeeVaultWithdrawalNetwork` values choose where each vault withdraws:
`"remote"` sends fees to the recipient address on L1, `"local"` on L2.
Withdrawals to L1 are more expensive. Note that when the config is
re-emitted (for example by `op-deployer inspect deploy-config`), these
values marshal in the legacy numeric form: `0` for `"remote"`, `1` for
`"local"`.
* The `*FeeVaultMinimumWithdrawalAmount` values exist because withdrawals to
L1 are expensive: the minimum prevents the overhead cost of continuous
tiny withdrawals that would cost more to execute than they return.
### Withdrawal finality guidance
* `finalizationPeriodSeconds`: must be nonzero. 12 seconds is recommended on
test networks, seven days on production ones. Standard configuration: 7
days — a high-security, excessively safe upper bound that leaves enough
time to consider social-layer solutions to a hack if necessary, and allows
other network participants to challenge the integrity of the corresponding
output root.
### Legacy output-oracle guidance
These apply only to chains still on the legacy `L2OutputOracle` proposal
system; new chains use [fault proofs](#fault-proofs) instead.
* `l2OutputOracleSubmissionInterval`: must be nonzero; 120 blocks (4
minutes) is suggested.
* `l2OutputOracleStartingBlockNumber`: should be 0 for new chains; may be
non-zero for networks upgraded from a legacy system (like OP Mainnet).
* `l2OutputOracleStartingTimestamp`: MUST be the timestamp corresponding to
the block defined by `l1StartingBlockTag`.
* `l2OutputOracleProposer`: must not be `address(0)`. No standard
requirement. This role is only active when the `OptimismPortal` respected
game type is `PERMISSIONED_CANNON`. The L1 ProxyAdmin sets the
implementation of the `PERMISSIONED_CANNON` game type, and thus determines
the proposer configuration of the permissioned dispute game.
* `l2OutputOracleChallenger`: must not be `address(0)`; a single admin
address is recommended to retain a common security model.
### Fault-proof value guidance
You should understand the implications of running a fault-proof chain before
setting `useFaultProofs`; see the
[fault proofs explainer](/op-stack/fault-proofs/explainer).
`proofMaturityDelaySeconds` and `disputeGameFinalityDelaySeconds` should not
be 0. `faultGameMaxDepth` should be conservatively set so that there is always
enough room for a full Cannon trace.
### Alt-DA guidance
Alt-DA mode is a non-standard feature. It enables integration of Data
Availability layers into the OP Stack regardless of their commitment type; see
[Alt-DA mode](/op-stack/features/experimental/alt-da-mode).
* `daCommitmentType`: must be either `KeccakCommitment` or
`GenericCommitment` (recommended); `KeccakCommitment` will be deprecated.
* `daChallengeWindow` and `daResolveWindow`: must be nonzero when using
Alt-DA mode with Keccak commitments.
* `daChallengeProxy`: must not be `address(0)` when using Alt-DA mode with
Keccak commitments, and must be `address(0)` with generic commitments.
### Governance and development settings
* `enableGovernance`: false is recommended; the governance token predeploy
is exclusive to OP Mainnet and the testing of OP-Mainnet-like chains.
* `fundDevAccounts` and the [development L1 genesis](#development-l1-genesis)
values are for devnet deployments only; a production L2 deployment does
not use them, except for the `l1BlockTime` sanity check (12 for Ethereum).
# Chain monitoring options
Source: https://docs.optimism.io/chain-operators/tools/chain-monitoring
Learn about onchain and offchain monitoring options for your OP Stack chain.
This explainer covers the basics of onchain and offchain monitoring options for your OP Stack chain. Onchain monitoring services allow chain operators to monitor the overall system and onchain events.
Offchain monitoring lets chain operators to monitor the operation and behavior of nodes and other offchain components.
## Onchain monitoring services
Onchain monitoring services provide insights into the overall system, helping chain operators track and monitor on-chain events. Some examples of onchain monitoring services include `monitorism` and `dispute-mon`.
### `monitorism`
Monitorism is a tooling suite that supports monitoring and active remediation actions for the OP Stack chain. Monitorism uses monitors as passive security providing automated monitoring for the OP Stack. They are used to monitor the OP stack and alert on specific events that could be a sign of a security incident.
Currently, the list of monitors includes:
Security integrity monitors: These are monitors necessary for making sure Bridges between L2 and L1 are safe and work as expected. These monitors are divided in two subgroups:
* Pre-Faultproof Chain Monitors:
* Fault Monitor: checks for changes in output roots posted to the L2OutputOracle contract. When a change is detected, it reconstructs the output root from a trusted L2 source and looks for a match.
* Withdrawals Monitor: checks for new withdrawals that have been proven to the OptimismPortal contract. Each withdrawal is checked against the `L2ToL1MessagePasser` contract.
* Faultproof chain monitors:
* Faultproof Withdrawal: The Faultproof Withdrawal component monitors `ProvenWithdrawals` events on the `OptimismPortal` contract and performs checks to detect any violations of invariant conditions on the chain. If a violation is detected, the issue is logged, and a Prometheus metric is set for the event. This component is designed to work exclusively with chains that are already utilizing the Fault Proofs system. This is a new version of the deprecated `chain-mon`, `faultproof-wd-mon`. For detailed information on how the component works and the algorithms used, please refer to the component README.
Security monitors: Those tools monitor other aspects of several contracts used in optimism:
* Global Events Monitor: made for taking YAML rules as configuration and monitoring the events that are emitted on the chain.
* Liveness Expiration Monitor: monitors the liveness expiration on Safes.
* Balances Monitor: emits a metric reporting the balances for the configured accounts.
* Multisig Monitor: The multisig monitor reports the paused status of the OptimismPortal contract. If set, reports the latest nonce of the configured Safe address and the latest presigned nonce stored in One Password.. The latest presigned nonce is identified by looking for items in the configured vault that follow a `ready-.json` name. The highest nonce of this item name format is reported.
* Drippie Monitor: tracks the execution and executability of drips within a Drippie contract.
* Secrets Monitor: takes a Drippie contract as a parameter and monitors for any drips within that contract that use the `CheckSecrets` dripcheck contract. `CheckSecrets` is a dripcheck that allows a drip to begin once a specific secret has been revealed (after a delay period) and cancels the drip if a second secret is revealed. Monitoring these secrets is important, as their revelation may indicate that the secret storage platform has been compromised and someone is attempting to exfiltrate the ETH controlled by the drip.
For more information on these monitors and how to use them, [check out the repo](https://github.com/ethereum-optimism/monitorism?tab=readme-ov-file#monitorism).
### `dispute-mon`
Chain operators should consider running `op-dispute-mon`. It's an essential security monitoring service that tracks game statuses, providing visibility over the last 28 days.
`dispute-mon` is set up and built the same way as `op-challenger`. This means that you can run it the same way (run `make op-dispute-mon` in the directory).
A basic configuration option would look like this:
```
OP_DISPUTE_MON_LOG_FORMAT=logfmt
OP_DISPUTE_MON_METRICS_ENABLED=true
OP_DISPUTE_MON_METRICS_ADDR=0.0.0.0
OP_DISPUTE_MON_METRICS_PORT=7300
OP_DISPUTE_MON_L1_ETH_RPC=..
OP_DISPUTE_MON_ROLLUP_RPC=..
OP_DISPUTE_MON_GAME_FACTORY_ADDRESS=..
OP_DISPUTE_MON_HONEST_ACTORS=..
```
`OP_DISPUTE_MON_HONEST_ACTORS` is a CSV (no spaces) list of addresses that are used for the honest `op-challenger` instances.
Additional flags:
* `OP_DISPUTE_MON_GAME_WINDOW`: This is the window of time to report on games. It should leave a buffer beyond the max game duration for bond claiming. If Fault Proof game parameters are not changes (e.g. MAX\_CLOCK\_DURATION), it is recommended to leave this as the default.
* `OP_DISPUTE_MON_MONITOR_INTERVAL`: The interval at which to check for new games. Defaults to 30 seconds currently.
* `OP_DISPUTE_MON_MAX_CONCURRENCY`: The max thread count. Defaults to 5 currently.
You can find more info on `op-dispute-mon` on [the repo](https://github.com/ethereum-optimism/optimism/tree/develop/op-dispute-mon).
## Offchain component monitoring
Offchain monitoring allows chain operators to monitor the operation and behavior of nodes and other offchain components. Some of the more common components that you'll likely want to monitor include `op-node`, `op-geth`, `op-proposer`, `op-batcher`, and `op-challenger`.
The general steps for enabling offchain monitoring are pretty consistent for all the OP components:
1. Expose the monitoring port by enabling the `--metrics.enabled` flag
2. Customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively
3. Use [Prometheus](https://prometheus.io/) to scrape data from the metrics port
4. Save the data in `influxdb`
5. Share the data with [Grafana](https://grafana.com/) to build your custom dashboard
### `op-node`
`op-node` metrics and monitoring is detailed in the [Node Metrics and Monitoring](/node-operators/guides/monitoring/metrics) guide. To enable metrics, pass the `--metrics.enabled` flag to `op-node` and follow the steps above for customization options.
See [this curated list](/operators/node-operators/management/metrics#important-metrics) for important metrics to track specifically for `op-node`.
### `op-geth`
To enable metrics, pass the `--metrics.enabled` flag to the op-geth. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively.
### `op-proposer`
To enable metrics, pass the `--metrics.enabled` flag to the op-proposer. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively.
You can find more information about these flags in our [Proposer configuration doc](/operators/chain-operators/configuration/proposer#metricsenabled).
### `op-batcher`
To enable metrics, pass the `--metrics.enabled` flag to the op-batcher. You can customize the metrics port and address via the `--metrics.port` and `--metrics.addr` flags, respectively.
You can find more information about these flags in our [Batcher configuration doc](/operators/chain-operators/configuration/proposer#metricsenabled).
### `op-challenger`
The `op-challenger` operates as the *honest actor* in the fault dispute system and defends the chain by securing the `OptimismPortal` and ensuring the game always resolves to the correct state of the chain.
For verifying the legitimacy of claims, `op-challenger` relies on a synced, trusted rollup node as well as a trace provider (e.g., [Cannon](/op-stack/fault-proofs/cannon)). See the [OP-Challenger Explainer](/op-stack/fault-proofs/challenger) for more information on this service.
To enable metrics, pass the `--metrics.enabled` flag to `op-challenger` and follow the steps above for customization options.
```
--metrics.addr value (default: "0.0.0.0") ($OP_CHALLENGER_METRICS_ADDR)
Metrics listening address
--metrics.enabled (default: false) ($OP_CHALLENGER_METRICS_ENABLED)
Enable the metrics server
--metrics.port value (default: 7300) ($OP_CHALLENGER_METRICS_PORT)
Metrics listening port
```
## Next steps
* If you encounter difficulties at any stage of this process, please reach out to [developer support](https://github.com/ethereum-optimism/developers/discussions).
# Blockscout block explorer
Source: https://docs.optimism.io/chain-operators/tools/explorer
Blockscout an open source block explorer for the OP Stack.
[Blockscout](https://www.blockscout.com/) is an open source block explorer that supports OP Stack chains.
Keep reading for a quick overview on how to deploy Blockscout for your OP Stack chain.
Check out the [Blockscout documentation](https://docs.blockscout.com) for up-to-date information on how to deploy and maintain a Blockscout instance.
## Dependencies
* [Docker](https://docs.docker.com/get-docker/)
## Create an archive node
Blockscout needs access to an [archive node](https://www.alchemy.com/overviews/archive-nodes#archive-nodes) for your OP Stack chain to properly index transactions, blocks, and internal interactions.
If using `op-geth`, you can run a node in archive mode with the `--gcmode=archive` flag.
Archive nodes take up significantly more disk space than full nodes.
You may need to have 2-4 terabytes of disk space available (ideally SSD) if you intend to run an archive node for a production OP Stack chain.
1-200 gigabytes of disk space may be sufficient for a development chain.
## Installation
Blockscout can be started from its source code on GitHub.
```sh theme={null}
git clone https://github.com/blockscout/blockscout.git -b production-optimism
cd blockscout/docker-compose
```
## Configuration
Review the configuration files within the `envs` directory and make any necessary changes.
In particular, make sure to review `envs/common-blockscout.env` and `envs/common-frontend.env`.
## Starting Blockscout
Start Blockscout with the following command:
```sh theme={null}
DOCKER_REPO=blockscout-optimism docker compose -f geth.yml up
```
## Usage
### Explorer
After Blockscout is started, browse to [http://localhost](http://localhost) to view the user interface.
Note that this URL may differ if you have changed the Blockscout configuration.
### API
Blockscout provides both a REST API and a GraphQL API.
Refer to the [API documentation](https://docs.blockscout.com/for-users/api) for more information.
# OP Conductor
Source: https://docs.optimism.io/chain-operators/tools/op-conductor
Understand how op-conductor keeps an OP Stack sequencer highly available, the guarantees it provides, and how its Raft-based design works.
This page explains what the `op-conductor` service is and how it works at a
high level. To add it to an existing network, follow the
[setup guide](/chain-operators/tools/op-conductor/setup). For the full flag and
RPC catalogue, see the
[configuration and RPC reference](/chain-operators/tools/op-conductor/reference).
## Enhancing sequencer reliability and availability
The [op-conductor](https://github.com/ethereum-optimism/optimism/tree/develop/op-conductor)
is an auxiliary service designed to enhance the reliability and availability of
a sequencer within high-availability setups. By minimizing the risks
associated with a single point of failure, the op-conductor ensures that the
sequencer remains operational and responsive.
### Assumptions
It is important to note that the `op-conductor` does not incorporate Byzantine
fault tolerance (BFT). This means the system operates under the assumption that
all participating nodes are honest and act correctly.
### Summary of guarantees
The design of the `op-conductor` provides the following guarantees:
* **No Unsafe Reorgs**
* **No Unsafe Head Stall During Network Partition**
* **100% Uptime with No More Than 1 Node Failure**
## Design
**On a high level, `op-conductor` serves the following functions:**
### Raft consensus layer participation
* **Leader determination:** Participates in the Raft consensus algorithm to
determine the leader among sequencers.
* **State management:** Stores the latest unsafe block ensuring consistency
across the system.
### RPC request handling
* **Admin RPC:** Provides administrative RPCs for manual recovery scenarios,
including, but not limited to: stopping the leadership vote and removing itself
from the cluster.
* **Health RPC:** Offers health RPCs for the `op-node` to determine whether it
should allow the publishing of transactions and unsafe blocks.
### Sequencer health monitoring
* Continuously monitors the health of the sequencer (op-node) to ensure
optimal performance and reliability.
### Control loop management
* Implements a control loop to manage the status of the sequencer (op-node),
including starting and stopping operations based on different scenarios and
health checks.
## Conductor state transition
The following is a state machine diagram of how the op-conductor manages the
sequencers Raft consensus.
**Helpful tips:** To better understand the graph, focus on one node at a time,
understand what can be transitioned to this current state and how it can
transition to other states. This way you could understand how we handle the
state transitions.
## Next steps
* Follow the [setup guide](/chain-operators/tools/op-conductor/setup) to add
op-conductor to an existing multi-sequencer network without downtime.
* Look up flags and admin RPCs in the
[configuration and RPC reference](/chain-operators/tools/op-conductor/reference).
* Checkout [op-conductor-mon](https://github.com/ethereum-optimism/infra):
which monitors multiple op-conductor instances and provides a unified interface
for reporting metrics.
* Get familiar with [op-conductor-ops](https://github.com/ethereum-optimism/infra/tree/main/op-conductor-ops) to interact with op-conductor.
# Configuration and RPCs
Source: https://docs.optimism.io/chain-operators/tools/op-conductor/reference
Reference for op-conductor configuration flags, environment variables, and conductor namespace RPC methods.
This page catalogues op-conductor's configuration options and its admin RPC
methods. For how the service works, see
[OP Conductor](/chain-operators/tools/op-conductor); to add it to a running
network, follow the [setup guide](/chain-operators/tools/op-conductor/setup).
## Configuration options
Generated from [`op-conductor/v0.9.4`](https://github.com/ethereum-optimism/optimism/releases/tag/op-conductor%2Fv0.9.4)
flag definitions. 61 flags: 9 required, 52 optional.
### Required flags
| Flag | Description | Environment variable |
| ------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------ |
| `--consensus.addr` | Address (excluding port) to listen for consensus connections. | `OP_CONDUCTOR_CONSENSUS_ADDR` |
| `--consensus.port` | Port to listen for consensus connections. May be 0 to let the system select a port. | `OP_CONDUCTOR_CONSENSUS_PORT` |
| `--execution.rpc` | HTTP provider URL for execution layer | `OP_CONDUCTOR_EXECUTION_RPC` |
| `--healthcheck.interval` | Interval between health checks | `OP_CONDUCTOR_HEALTHCHECK_INTERVAL` |
| `--healthcheck.min-peer-count` | Minimum number of peers required to be considered healthy | `OP_CONDUCTOR_HEALTHCHECK_MIN_PEER_COUNT` |
| `--healthcheck.unsafe-interval` | Interval allowed between unsafe head and now measured in seconds | `OP_CONDUCTOR_HEALTHCHECK_UNSAFE_INTERVAL` |
| `--node.rpc` | HTTP provider URL for op-node | `OP_CONDUCTOR_NODE_RPC` |
| `--raft.server.id` | Unique ID for this server used by raft consensus | `OP_CONDUCTOR_RAFT_SERVER_ID` |
| `--raft.storage.dir` | Directory to store raft data | `OP_CONDUCTOR_RAFT_STORAGE_DIR` |
### Optional flags
| Flag | Description | Default | Environment variable |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------- |
| `--consensus.advertised` | Full address (host and port) for other peers to contact the consensus server. Optional: if left empty, the local address is advertised. | — | `OP_CONDUCTOR_CONSENSUS_ADVERTISED` |
| `--healthcheck.execution-p2p-check-api` | Type of EL P2P check to perform. If not set, the default `net` type will be used corresponding to the `net_peerCount` RPC call. | `"net"` | `OP_CONDUCTOR_HEALTHCHECK_EXECUTION_P2P_CHECK_API` |
| `--healthcheck.execution-p2p-enabled` | Whether to enable EL P2P checks | `false` | `OP_CONDUCTOR_HEALTHCHECK_EXECUTION_P2P_ENABLED` |
| `--healthcheck.execution-p2p-min-peer-count` | Minimum number of EL P2P peers required to be considered healthy | `0` | `OP_CONDUCTOR_HEALTHCHECK_EXECUTION_P2P_MIN_PEER_COUNT` |
| `--healthcheck.execution-p2p-rpc-url` | URL override for the execution layer RPC client for the sake of p2p healthcheck. If not set, the execution RPC URL will be used. | — | `OP_CONDUCTOR_HEALTHCHECK_EXECUTION_P2P_RPC_URL` |
| `--healthcheck.rollup-boost-partial-healthiness-tolerance-interval-seconds` | The time frame within which rollup-boost partial healthiness tolerance is evaluated | `0` | `OP_CONDUCTOR_HEALTHCHECK_ROLLUP_BOOST_PARTIAL_HEALTHINESS_TOLERANCE_INTERVAL_SECONDS` |
| `--healthcheck.rollup-boost-partial-healthiness-tolerance-limit` | Sets the count of rollup-boost partial healthiness failures to occur before marking op-conducto as unhealthy. Default is 0 with which a single occurrence of rollup-boost partial healthiness is enough to set op-conductor as unhealthy | `0` | `OP_CONDUCTOR_HEALTHCHECK_ROLLUP_BOOST_PARTIAL_HEALTHINESS_TOLERANCE_LIMIT` |
| `--healthcheck.safe-enabled` | Whether to enable safe head progression checks | `false` | `OP_CONDUCTOR_HEALTHCHECK_SAFE_ENABLED` |
| `--healthcheck.safe-interval` | Interval between safe head progression measured in seconds | `1200` | `OP_CONDUCTOR_HEALTHCHECK_SAFE_INTERVAL` |
| `--log.color` | Color the log output if in terminal mode | `false` | `OP_CONDUCTOR_LOG_COLOR` |
| `--log.format` | Format the log output. Supported formats: text, terminal, logfmt, logfmtms, json, jsonms | `text` | `OP_CONDUCTOR_LOG_FORMAT` |
| `--log.level` | The lowest log level that will be output | `INFO` | `OP_CONDUCTOR_LOG_LEVEL` |
| `--log.pid` | Show pid in the log | `false` | `OP_CONDUCTOR_LOG_PID` |
| `--metrics.addr` | Metrics listening address | `"0.0.0.0"` | `OP_CONDUCTOR_METRICS_ADDR` |
| `--metrics.enabled` | Enable the metrics server | `false` | `OP_CONDUCTOR_METRICS_ENABLED` |
| `--metrics.port` | Metrics listening port | `7300` | `OP_CONDUCTOR_METRICS_PORT` |
| `--network` | Predefined network selection. Available networks: every chain bundled from the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry) at the release; run `op-conductor --help` for the exact list | — | `OP_CONDUCTOR_NETWORK` |
| `--override.canyon` | Manually specify the canyon fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_CANYON` |
| `--override.delta` | Manually specify the delta fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_DELTA` |
| `--override.ecotone` | Manually specify the ecotone fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_ECOTONE` |
| `--override.fjord` | Manually specify the fjord fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_FJORD` |
| `--override.granite` | Manually specify the granite fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_GRANITE` |
| `--override.holocene` | Manually specify the holocene fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_HOLOCENE` |
| `--override.interop` | Manually specify the interop fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_INTEROP` |
| `--override.isthmus` | Manually specify the isthmus fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_ISTHMUS` |
| `--override.jovian` | Manually specify the jovian fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_JOVIAN` |
| `--override.karst` | Manually specify the karst fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_KARST` |
| `--override.pectrablobschedule` | Manually specify the pectrablobschedule fork timestamp, overriding the bundled setting | `0` | `OP_CONDUCTOR_OVERRIDE_PECTRABLOBSCHEDULE` |
| `--paused` | Whether the conductor is paused | `false` | `OP_CONDUCTOR_PAUSED` |
| `--pprof.addr` | pprof listening address | `"0.0.0.0"` | `OP_CONDUCTOR_PPROF_ADDR` |
| `--pprof.enabled` | Enable the pprof server | `false` | `OP_CONDUCTOR_PPROF_ENABLED` |
| `--pprof.path` | pprof file path. If it is a directory, the path is \{dir}/\{profileType}.prof | — | `OP_CONDUCTOR_PPROF_PATH` |
| `--pprof.port` | pprof listening port | `6060` | `OP_CONDUCTOR_PPROF_PORT` |
| `--pprof.type` | pprof profile type. One of cpu, heap, goroutine, threadcreate, block, mutex, allocs | — | `OP_CONDUCTOR_PPROF_TYPE` |
| `--raft.bootstrap` | If this node should bootstrap a new raft cluster | `false` | `OP_CONDUCTOR_RAFT_BOOTSTRAP` |
| `--raft.heartbeat-timeout` | Heartbeat interval timeout | `1s` | `OP_CONDUCTOR_RAFT_HEARTBEAT_TIMEOUT` |
| `--raft.lease-timeout` | Leader lease timeout | `500ms` | `OP_CONDUCTOR_RAFT_LEASE_TIMEOUT` |
| `--raft.round-robin-leader-transfer` | Enable deterministic round-robin leader transfer instead of Raft's default log-based selection. Must be enabled on all conductors in the cluster. | `false` | `OP_CONDUCTOR_RAFT_ROUND_ROBIN_LEADER_TRANSFER` |
| `--raft.snapshot-interval` | The interval to check if a snapshot should be taken. | `2m0s` | `OP_CONDUCTOR_RAFT_SNAPSHOT_INTERVAL` |
| `--raft.snapshot-threshold` | Number of logs to trigger a snapshot | `8192` | `OP_CONDUCTOR_RAFT_SNAPSHOT_THRESHOLD` |
| `--raft.trailing-logs` | Number of logs to keep after a snapshot | `10240` | `OP_CONDUCTOR_RAFT_TRAILING_LOGS` |
| `--rollup-boost.enabled` | Enable the rollup-boost healthcheck that uses HTTP status codes (200/206/503). Healthchecks are performed against execution.rpc + '/healthz' (path appended automatically). Mutually exclusive with rollup-boost.next-enabled. | `false` | `OP_CONDUCTOR_ROLLUP_BOOST_ENABLED` |
| `--rollup-boost.healthcheck-timeout` | Timeout for rollup-boost healthchecks (applies to both standard and next) | `5s` | `OP_CONDUCTOR_ROLLUP_BOOST_HEALTHCHECK_TIMEOUT` |
| `--rollup-boost.next-enabled` | Enable rollup-boost healthcheck using JSON response parsing. Requires rollup-boost.next-healthcheck-url. Mutually exclusive with rollup-boost.enabled. | `false` | `OP_CONDUCTOR_ROLLUP_BOOST_NEXT_ENABLED` |
| `--rollup-boost.next-healthcheck-url` | Full URL including path for the rollup-boost health endpoint (e.g., '[http://localhost:8080/healthz](http://localhost:8080/healthz)'). Required when rollup-boost.next-enabled is true. | — | `OP_CONDUCTOR_ROLLUP_BOOST_NEXT_HEALTHCHECK_URL` |
| `--rollup.config` | Rollup chain parameters | — | `OP_CONDUCTOR_ROLLUP_CONFIG` |
| `--rollupboost.ws-url` | WebSocket URL for the rollup boost to listen for payload streams. | — | `OP_CONDUCTOR_ROLLUPBOOST_WS_URL` |
| `--rpc.addr` | rpc listening address | `"0.0.0.0"` | `OP_CONDUCTOR_RPC_ADDR` |
| `--rpc.enable-admin` | Enable the admin API | `false` | `OP_CONDUCTOR_RPC_ENABLE_ADMIN` |
| `--rpc.enable-proxy` | Enable the RPC proxy to underlying sequencer services | `true` | `OP_CONDUCTOR_RPC_ENABLE_PROXY` |
| `--rpc.port` | rpc listening port | `8545` | `OP_CONDUCTOR_RPC_PORT` |
| `--websocket.server-port` | Port for the conductor to run a WebSocket server that pushes payload streams out. | `8546` | `OP_CONDUCTOR_WEBSOCKET_SERVER_PORT` |
## Notes on selected flags
### raft.bootstrap
For bootstrapping a new cluster. This should only be used on the sequencer
that is currently active and can only be started once with this flag,
otherwise the flag has to be removed or the raft log must be deleted before
re-bootstrapping the cluster.
### paused
There is no configuration state, so if you unpause via RPC and then restart,
it will start paused again.
## RPCs
Conductor exposes [admin RPCs](https://github.com/ethereum-optimism/optimism/blob/develop/op-conductor/rpc/api.go#L17)
on the `conductor` namespace.
### conductor\_overrideLeader
`OverrideLeader` is used to override the leader status, this is only used to
return true for `Leader()` & `LeaderWithID()` calls. It does not impact the
actual raft consensus leadership status. It is supposed to be used when the
cluster is unhealthy and the node is the only one up, to allow batcher to
be able to connect to the node so that it could download blocks from the
manually started sequencer.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_overrideLeader","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_overrideLeader --rpc-url http://127.0.0.1:8547
```
### conductor\_pause
`Pause` pauses op-conductor.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_pause","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_pause --rpc-url http://127.0.0.1:8547
```
### conductor\_resume
`Resume` resumes op-conductor.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_resume","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_resume --rpc-url http://127.0.0.1:8547
```
### conductor\_paused
Paused returns true if the op-conductor is paused.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_paused","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_paused --rpc-url http://127.0.0.1:8547
```
### conductor\_stopped
Stopped returns true if the op-conductor is stopped.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_stopped","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_stopped --rpc-url http://127.0.0.1:8547
```
### conductor\_sequencerHealthy
SequencerHealthy returns true if the sequencer is healthy.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_sequencerHealthy","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_sequencerHealthy --rpc-url http://127.0.0.1:8547
```
### conductor\_leader
API related to consensus.
Leader returns true if the server is the leader.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_leader","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_leader --rpc-url http://127.0.0.1:8547
```
### conductor\_leaderWithID
API related to consensus.
LeaderWithID returns the current leader's server info.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_leaderWithID","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_leaderWithID --rpc-url http://127.0.0.1:8547
```
### conductor\_addServerAsVoter
API related to consensus.
AddServerAsVoter adds a server as a voter to the cluster.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_addServerAsVoter","params":[, , ],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_addServerAsVoter --rpc-url http://127.0.0.1:8547
```
### conductor\_addServerAsNonvoter
API related to consensus.
AddServerAsNonvoter adds a server as a non-voter to the cluster. non-voter
The non-voter will not participate in the leader election.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_addServerAsNonvoter","params":[, , ],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_addServerAsNonvoter --rpc-url http://127.0.0.1:8547
```
### conductor\_removeServer
API related to consensus.
RemoveServer removes a server from the cluster.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_removeServer","params":[, ],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_removeServer --rpc-url http://127.0.0.1:8547
```
### conductor\_transferLeader
API related to consensus.
TransferLeader transfers leadership to another server (resigns).
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_transferLeader","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_transferLeader --rpc-url http://127.0.0.1:8547
```
### conductor\_transferLeaderToServer
API related to consensus.
TransferLeaderToServer transfers leadership to a specific server.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_transferLeaderToServer","params":[, , ],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_transferLeaderToServer --rpc-url http://127.0.0.1:8547
```
### conductor\_clusterMembership
ClusterMembership returns the current cluster membership configuration.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_clusterMembership","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_clusterMembership --rpc-url http://127.0.0.1:8547
```
### conductor\_active
API called by `op-node`.
Active returns true if the op-conductor is active (not paused or stopped).
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_active","params":[],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
cast rpc conductor_active --rpc-url http://127.0.0.1:8547
```
### conductor\_commitUnsafePayload
API called by `op-node`.
CommitUnsafePayload commits an unsafe payload (latest head) to the consensus
layer. This method is typically called by the op-node to commit execution payload envelopes.
```sh theme={null}
curl -X POST -H "Content-Type: application/json" --data \
'{"jsonrpc":"2.0","method":"conductor_commitUnsafePayload","params":[{
"executionPayload": {
"parentHash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"feeRecipient": "0x4200000000000000000000000000000000000019",
"stateRoot": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"receiptsRoot": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"prevRandao": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"blockNumber": "0x64",
"gasLimit": "0x1c9c380",
"gasUsed": "0x5208",
"timestamp": "0x12345678",
"extraData": "0x",
"baseFeePerGas": "0x7",
"blockHash": "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba",
"transactions": []
}
}],"id":1}' \
http://127.0.0.1:8547
```
```sh theme={null}
# Example with basic payload structure
cast rpc conductor_commitUnsafePayload \
'{"executionPayload":{"parentHash":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","feeRecipient":"0x4200000000000000000000000000000000000019","stateRoot":"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890","receiptsRoot":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef","blockNumber":"0x64","gasLimit":"0x1c9c380","gasUsed":"0x5208","timestamp":"0x12345678","extraData":"0x","baseFeePerGas":"0x7","blockHash":"0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba","transactions":[]}}' \
--rpc-url http://127.0.0.1:8547
```
# Setup
Source: https://docs.optimism.io/chain-operators/tools/op-conductor/setup
Add op-conductor to an existing multi-sequencer OP Stack network without downtime.
For how op-conductor works and the guarantees it provides, see
[OP Conductor](/chain-operators/tools/op-conductor). The flags and RPC methods
used below are catalogued in the
[configuration and RPC reference](/chain-operators/tools/op-conductor/reference).
At OP Labs, op-conductor is deployed as a kubernetes statefulset because it
requires a persistent volume to store the raft log. This guide describes
setting up conductor on an existing network without incurring downtime.
You can utilize the [op-conductor-ops](https://github.com/ethereum-optimism/infra/tree/main/op-conductor-ops) tool to confirm the conductor status between the steps.
## Assumptions
This setup guide has the following assumptions:
* 3 deployed sequencers (sequencer-0, sequencer-1, sequencer-2) that are all
in sync and in the same vpc network
* sequencer-0 is currently the active sequencer
* You can execute a blue/green style sequencer deployment workflow that
involves no downtime (described below)
* conductor and sequencers are running in k8s or some other container
orchestrator (vm-based deployment may be slightly different and not covered
here)
## Spin up op-conductor
Deploy a conductor instance per sequencer with sequencer-1 as the raft cluster
bootstrap node:
* suggested conductor configs:
```yaml theme={null}
OP_CONDUCTOR_CONSENSUS_ADDR: '0.0.0.0'
OP_CONDUCTOR_CONSENSUS_ADVERTISED: ''
OP_CONDUCTOR_CONSENSUS_PORT: '50050'
OP_CONDUCTOR_EXECUTION_RPC: ':8545'
OP_CONDUCTOR_HEALTHCHECK_INTERVAL: '1'
OP_CONDUCTOR_HEALTHCHECK_MIN_PEER_COUNT: '2' # set based on your internal p2p network peer count
OP_CONDUCTOR_HEALTHCHECK_UNSAFE_INTERVAL: '5' # recommend a 2-3x multiple of your network block time to account for temporary performance issues
OP_CONDUCTOR_LOG_FORMAT: logfmt
OP_CONDUCTOR_LOG_LEVEL: info
OP_CONDUCTOR_METRICS_ADDR: 0.0.0.0
OP_CONDUCTOR_METRICS_ENABLED: 'true'
OP_CONDUCTOR_METRICS_PORT: '7300'
OP_CONDUCTOR_NETWORK: ''
OP_CONDUCTOR_NODE_RPC: ':8545'
OP_CONDUCTOR_RAFT_SERVER_ID: 'unique raft server id'
OP_CONDUCTOR_RAFT_STORAGE_DIR: /conductor/raft
OP_CONDUCTOR_RPC_ADDR: 0.0.0.0
OP_CONDUCTOR_RPC_ENABLE_ADMIN: 'true'
OP_CONDUCTOR_RPC_ENABLE_PROXY: 'true'
OP_CONDUCTOR_RPC_PORT: '8547'
```
* sequencer-1 op-conductor extra config:
```yaml theme={null}
OP_CONDUCTOR_PAUSED: "true"
OP_CONDUCTOR_RAFT_BOOTSTRAP: "true"
```
Pause `sequencer-0` &` sequencer-2` conductors with [conductor\_pause](/chain-operators/tools/op-conductor/reference#conductor_pause) RPC request.
Deploy an `op-node` config update to all sequencers that enables conductor. Use
a blue/green style deployment workflow that switches the active sequencer to
`sequencer-1`:
* all sequencer op-node configs:
```yaml theme={null}
OP_NODE_CONDUCTOR_ENABLED: "true" # this is what commits unsafe blocks to the raft logs
OP_NODE_RPC_ADMIN_STATE: "" # this flag can't be used with conductor
```
Confirm `sequencer-1` is active and successfully producing unsafe blocks.
Because `sequencer-1` was the raft cluster bootstrap node, it is now committing
unsafe payloads to the raft log.
Add voting nodes to cluster using [conductor\_AddServerAsVoter](/chain-operators/tools/op-conductor/reference#conductor_addserverasvoter)
RPC request to the leader conductor (`sequencer-1`)
Confirm cluster membership and sequencer state:
* `sequencer-0` and `sequencer-2`:
1. raft cluster follower
2. sequencer is stopped
3. conductor is paused
4. conductor enabled in op-node config
* `sequencer-1`
1. raft cluster leader
2. sequencer is active
3. conductor is paused
4. conductor enabled in op-node config
Resume all conductors with [conductor\_resume](/chain-operators/tools/op-conductor/reference#conductor_resume) RPC request to
each conductor instance.
Confirm all conductors successfully resumed with [conductor\_paused](/chain-operators/tools/op-conductor/reference#conductor_paused)
Trigger leadership transfer to `sequencer-0` using [conductor\_transferLeaderToServer](/chain-operators/tools/op-conductor/reference#conductor_transferleadertoserver)
Confirm cluster membership and sequencer state:
**`sequencer-1` and `sequencer-2`:**
1. raft cluster follower
2. sequencer is stopped
3. conductor is active
4. conductor enabled in op-node config
**`sequencer-0`:**
1. raft cluster leader
2. sequencer is active
3. conductor is active
4. conductor enabled in op-node config
Deploy a config change to `sequencer-1` conductor to remove the
`OP_CONDUCTOR_PAUSED: true` flag and `OP_CONDUCTOR_RAFT_BOOTSTRAP` flag.
### Blue/green deployment
In order to ensure there is no downtime when setting up conductor, you need to
have a deployment script that can update sequencers without network downtime.
An example of this workflow might look like:
1. Query current state of the network and determine which sequencer is
currently active (referred to as "original" sequencer below).
From the other available sequencers, choose a candidate sequencer.
2. Deploy the change to the candidate sequencer and then wait for it to sync
up to the original sequencer's unsafe head. You may want to check peer counts
and other important health metrics.
3. Stop the original sequencer using `admin_stopSequencer` which returns the
last inserted unsafe block hash. Wait for candidate sequencer to sync with
this returned hash in case there is a delta.
4. Start the candidate sequencer at the original's last inserted unsafe block
hash.
1. Here you can also execute additional check for unsafe head progression
and decide to roll back the change (stop the candidate sequencer, start the
original, rollback deployment of candidate, etc.)
5. Deploy the change to the original sequencer, wait for it to sync to the
chain head. Execute health checks.
### Post-conductor launch deployments
After conductor is live, a similar canary style workflow is used to ensure
minimal downtime in case there is an issue with deployment:
1. Choose a candidate sequencer from the raft-cluster followers
2. Deploy to the candidate sequencer. Run health checks on the candidate.
3. Transfer leadership to the candidate sequencer using
`conductor_transferLeaderToServer`. Run health checks on the candidate.
4. Test if candidate is still the leader using `conductor_leader` after some
grace period (ex: 30 seconds)
1. If not, then there is likely an issue with the deployment. Roll back.
5. Upgrade the remaining sequencers, run healthchecks.
# Install op-deployer
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/installation
Learn how to install op-deployer from pre-built binaries or from source.
OP Deployer can be installed both from pre-built binaries and from source. This guide will walk you through both
methods.
## Install From Binaries
Installing OP Deployer from pre-built binaries is the easiest and most preferred way to get started. To install from
binaries, download the latest release from the [releases page](https://github.com/ethereum-optimism/optimism/releases?q=op-deployer\&expanded=true) and extract the binary to a directory in your
`$PATH`.
## Install From Source
To install from source, you will need Go, `just`, and `git`. Then, run the following:
```shell theme={null}
git clone git@github.com:ethereum-optimism/optimism.git # you can skip this if you already have the repo
cd optimism/op-deployer
just build
cp ./bin/op-deployer /usr/local/bin/op-deployer # or any other directory in your $PATH
```
# Known Limitations
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/known-limitations
Known limitations and workarounds for OP Deployer.
OP Deployer is subject to some known limitations which we're working on addressing in future releases.
## Tagged Releases on New Chains
**Fixed in all versions after v0.0.11.**
It is not currently possible to deploy chains using tagged contract locators (i.e., those starting with `tag://`)
anywhere except Sepolia and Ethereum mainnet. If you try to, you'll see an error like this:
```
####################### WARNING! WARNING WARNING! #######################
You are deploying a tagged release to a chain with no pre-deployed OPCM.
Due to a quirk of our contract version system, this can lead to deploying
contracts containing unaudited or untested code. As a result, this
functionality is currently disabled.
We will fix this in an upcoming release.
This process will now exit.
####################### WARNING! WARNING WARNING! #######################
```
Like the error says, this is due to a quirk of how we version our smart contracts. We currently follow a process
like this:
1. We tag a release, like op-contracts/v1.8.0.
2. We update the [release notes](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0) to reference which contracts are updated in that release.
3. We manually deploy the updated contract implementations.
4. We manually deploy a new OPCM to reference the newly-deployed implementations, as well as existing implementations
for any contracts that have not been updated.
There's a flaw in this strategy, however. The release only includes the contracts that explicitly changed during
that release. **This means that any contract not referenced as "updated" in the release notes is "in-development," and
has not been audited or approved by governance.** Deploying all contracts from the release tag will therefore deploy a
combination of prod-ready and in-development code. To get the version of the contract that will *actually* run in prod,
OP Deployer would have to reference all previous releases to get the correct combination of contracts.
For example, to deploy on Holesky you will need to deploy contracts from versions `op-contracts/v1.8.0`, `op-contracts/v1.6.0`, and `op-contracts/v1.3.0`. On
Sepolia and mainnet, we've been incrementally deploying implementation contracts so we just use the existing
implementations to work around this issue.
We plan on addressing this in our next release. In the meantime, as a workaround you can use a non-tagged locator for
development chains, or use Sepolia or Ethereum mainnet as your L1.
# OP Deployer
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/overview
A CLI tool for deploying and upgrading smart contracts for OP Stack chains.
OP Deployer is a CLI tool that simplifies deploying and upgrading smart contracts for OP Stack chains. It also
exposes a suite of libraries that allow developers to easily manage smart contracts from their applications.
## Goals
### Declarative
With OP Deployer, developers define their chain's desired configuration in a declarative configuration file. The tool
then makes the minimum number of smart contract calls required to make the deployment match the configuration. This
ensures that the implementation details of the deployment are abstracted away, and allows complex configurations to be
expressed cleanly without concern for the underlying deployment process.
### Portable
OP Deployer is designed to be small, portable, and easily installed. As such it is distributed as a standalone binary
with no additional dependencies. This allows it to be used in a variety of contexts, including as a CLI tool, in CI
pipelines, and as part of local development environments like [Kurtosis](https://github.com/ethpandaops/optimism-package).
### Standard, But Extensible
OP Deployer aims to make doing the right thing easy, and doing dangerous things hard. As such its configuration and
API are optimized for deploying and upgrading Standard OP Chains. However, it also exposes a lower-level set of
primitives and configuration directives which users can use to deploy more complex configurations if the need arises.
## Development Status
OP Deployer is undergoing active development and has been used for several mainnet deployments. It is considered
production-ready. However, please keep in mind that **OP Deployer has not been audited** and that any chains
deployed using OP Deployer should be checked thoroughly for correctness prior to launch.
## Next Steps
* [Install op-deployer](/chain-operators/tools/op-deployer/installation) - Install from pre-built binaries or from source
* [Bootstrap](/chain-operators/tools/op-deployer/usage/bootstrap) - Deploy global singletons and implementation contracts
* [Architecture](/chain-operators/tools/op-deployer/reference/architecture/overview) - Understand OP Deployer's architecture
# Scripting Engine
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/reference/architecture/engine
Understand OP Deployer's in-memory EVM scripting engine: what it enables, why on-chain interactions run through Solidity scripts, and how Go code communicates with scripts inside the simulated EVM.
One of OP Deployer's most powerful features is its in-memory EVM scripting engine. The scripting engine provides
similar capabilities to Forge:
* It runs all on-chain calls in a simulated environment first, which allows the effects of on-chain calls to be
validated before they cost gas.
* It exposes Foundry cheatcodes, which allow for deep instrumentation and customization of the EVM environment. These
cheatcodes in turn allow OP Deployer to call into Solidity scripts.
The scripting engine is really the heart of OP Deployer. Without it, OP Deployer would be nothing more than a thin
wrapper over Forge. The scripting engine enables:
* Easy integration with existing Solidity-based tooling.
* Detailed stack traces when deployments fail.
* Fast feedback loops that prevent sending on-chain transactions that may fail.
* Live chain forking.
For these reasons and more, the scripting engine is a critical part of OP Deployer's architecture. You will see that
almost all on-chain interactions initiated by OP Deployer use the scripting engine to call into a Solidity script.
The script then uses `vm.broadcast` to signal a transaction that should be sent on-chain.
## Why Use Solidity Scripts?
Solidity scripts are much more ergonomic than Go code for complex on-chain interactions. They allow for:
* Easy integration with existing Solidity-based tooling and libraries.
* Simple ABI encoding/decoding.
* Clear separation of concerns between inter-contract calls, and the underlying RPC calls that drive them.
The alternative is to encode all on-chain interactions in Go code. This is possible, but it is much more verbose and
requires writing bindings between Go and the Solidity ABI. These bindings are error-prone and difficult to maintain.
## Engine Implementation
The scripting engine is implemented in the `op-chain-ops/script` package. It extends Geth's EVM implementation with
Forge cheatcodes, and defines some tools that allow Go structs to be etched into the EVM's memory. Geth exposes
hooks that drive most of the engine's behavior. The best way to understand these further is to read the code.
## Using the Engine
OP Deployer uses the etching tooling described above to communicate between OP Deployer and the scripting engine.
Most Solidity scripts define an input contract, an output contract, and the script itself. The script reads data
from fields on the input contract, then sets fields on the output contract as it runs. OP Deployer defines the input
and output contracts as Go structs, like this:
```go theme={null}
package foo_script
type FooInput struct {
Number uint64
Bytes []byte
}
type FooOutput struct {
Result uint64
Bytes []byte
}
```
The input and output contracts are then "etched" into the EVM's memory, like this:
```go theme={null}
package foo_script
// ... struct defs elided
func Run(host *script.Host, input FooInput) (FooOutput, error) {
// Create a variable to hold our output
var output FooOutput
// Make new addresses for our input/output contracts
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()
// Inject the input/output contracts into the EVM as precompiles
cleanupInput, err := script.WithPrecompileAtAddress[*FooInput](host, inputAddr, &input)
if err != nil {
return output, fmt.Errorf("failed to insert input precompile: %w", err)
}
defer cleanupInput()
cleanupOutput, err := script.WithPrecompileAtAddress[*FooOutput](host, outputAddr, &output,
script.WithFieldSetter[*FooOutput])
if err != nil {
return output, fmt.Errorf("failed to insert output precompile: %w", err)
}
defer cleanupOutput()
// ... do stuff with the input/output contracts ...
}
```
The script engine will automatically generate getters and setters for the fields on the input and output contracts.
You can use the `evm:` struct tag to customize the behavior of these getters and setters.
Finally, the script itself gets etched into the EVM's memory and executed, like this:
```go theme={null}
package foo_script
type FooScript struct {
Run func(input, output common.Address) error
}
func Run(host *script.Host, input FooInput) (FooOutput, error) {
// .. see implementation above...
deployScript, cleanupDeploy, err := script.WithScript[FooScript](host, "FooScript.s.sol", "FooScript")
if err != nil {
return output, fmt.Errorf("failed to load %s script: %w", scriptFile, err)
}
defer cleanupDeploy()
if err := deployScript.Run(inputAddr, outputAddr); err != nil {
return output, fmt.Errorf("failed to run %s script: %w", scriptFile, err)
}
return output, nil
}
```
You may notice that the script is loaded from a file. To run the scripting engine, contract artifacts (**not**
source code) must exist somewhere on disk for the scripting engine to use. For more information on that, see the
[Artifacts Locators](/chain-operators/tools/op-deployer/reference/artifacts-locators) page.
# Architecture
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/reference/architecture/overview
Understand OP Deployer's architecture and internals.
This section details OP Deployer's architecture and internals. Unless you're contributing directly to OP Deployer,
you don't need to read this.
* [Deployment Pipeline](/chain-operators/tools/op-deployer/reference/architecture/pipeline): Describes the stages of the deployment pipeline.
* [Scripting Engine](/chain-operators/tools/op-deployer/reference/architecture/engine): Describes the scripting engine that OP Deployer uses to interact
with the EVM.
*Full architecture diagram ([source](https://www.figma.com/board/bbp16y6ZwIkxzOoKhDu9kk/op-deployer-architecture?node-id=0-1\&p=f\&t=1Eg9JBP0RuVmdtsM-0))*
# Deployment Pipeline
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/reference/architecture/pipeline
Understand how OP Deployer's pipeline is architected: the intent and state files it consumes and produces, and what each deployment stage is responsible for.
This page explains how OP Deployer is architected: a pipeline in which each stage is responsible for a single piece of the deployment process.
The pipeline consumes a configuration called an *intent* which describes the desired state of the chain, and
produces a file called the *state* which describes the current state of the chain during and after the deployment.
The steps of the pipeline are:
1. Initialization
2. Shared Contracts Deployment
3. Implementations Deployment
4. OP Chain Deployment
5. Alt-DA Deployment
6. Dispute Game Deployment
7. L2 Genesis Generation
8. Setting Start Block
State is written to disk after each state. This allows the pipeline to be restarted from any point in the event of a
recoverable error.
We'll cover each of these stages in more detail below.
## Initialization
During this step, OP Deployer sets initial values for the pipeline based on the user's intent. These values will be
used by downstream stages. For example, if the user is deploying using an existing set of shared contracts,
those contracts will be inserted into the state during this step.
## Shared Contracts/Implementations Deployment
Next, the base contracts for the chain are deployed. This includes shared management contracts like
`SuperchainConfig`, as well as implementation contracts that will be used for the OP Chain
deployment in the future like the OP Contracts Manager (OPCM).
Most chains will be configured to use existing implementations. In this case, these steps will be skipped.
## OP Chain Deployment
The OP Chain itself is deployed during this step. Multiple chains will be deployed if they are specified in the
intent. The deployment works by calling into the OPCM, which will emit an event for each successfully-deployed chain.
## Customizations Deployment
The next two steps (Alt-DA and Dispute Game) deploy customizations. As their names imply, they deploy Alt-DA and
additional dispute game contracts. Typically, these steps will be skipped as they are mostly useful in testing.
## L2 Genesis Generation
This step generates the L2 Genesis file which is used to initialize the chain. This file is generated by calling
into `L2Genesis.sol`, and dumping the outputted state.
## Setting Start Block
Lastly, the start block is set to the current block number on L1. This is done last to ensure that the start block
is relatively recent, since the deployment process can take arbitrarily long.
# Artifacts Locators
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/reference/artifacts-locators
Learn how OP Deployer uses artifacts locators to point to contract artifacts.
OP Deployer calls into precompiled contract artifacts. To make this work, OP Deployer uses artifacts locators to
point to the location of contract artifacts. While locators are nothing more than URLs, they do encode some
additional behaviors which are described here.
## Locator Types
Locators can be one of three types:
* `tag://` locators, which point to a versioned contracts release. These resolve to a known URL. Artifacts
downloaded using a tagged locator are validated against a hardcoded checksum in the OP Deployer implementation.
This prevents tampering with the contract artifacts once they have been tagged. Additionally, tagged locators are
cached on disk to avoid repeated downloads.
* ex: `tag://op-contracts/v1.8.0-rc.4`
* `https://` locators, which point to a tarball of contract artifacts somewhere on the web. HTTP locators are cached
just like tagged locators are, but they are not validated against a checksum.
* ex: `https://`
* `file://` locators, which point to a directory on local disk containing the artifacts.
* ex: `file:///packages/contracts-bedrock/forge-artifacts`
# op-deployer versioning and releases
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/reference/releases
Reference for where to find OP Deployer releases and how each OP Deployer version maps to a supported contract release.
## Latest Releases
The latest version of OP Deployer is always available at [releases](https://github.com/ethereum-optimism/optimism/releases?q=op-deployer\&expanded=true). You should search for `op-deployer` to
exclude other packages.
## Versioning
For all releases after `v0.0.11`, each minor version of OP Deployer will support a single release of the
governance-approved smart contracts. If you want to deploy an earlier version of the contracts (which may be
dangerous!), you should use an earlier version of OP Deployer. This setup allows our smart contract developers to make
breaking changes on `develop`, while still allowing new chains to be deployed and upgraded using production-ready smart
contracts.
If you deploy from an HTTPS or file [locator](/chain-operators/tools/op-deployer/reference/artifacts-locators), the deployment behavior will match the
contract's tag. For example, if version `v0.2.0` supports `v2.0.0` then the deployment will work as if you were
deploying `op-contracts/v2.0.0`. Typically, errors like `unknown selector: ` imply that you're using the wrong
version of OP Deployer for your contract artifacts. If this happens, we recommend trying different versions until you
get one that works. Note that this workflow is **not recommended** for production chains.
## Contributor Workflows
Step-by-step procedures for backporting fixes onto earlier OP Deployer versions and adding support for new contract
versions live in [Release Workflows](/chain-operators/tools/op-deployer/usage/release-workflows).
# Apply Command
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/usage/apply
Learn how to deploy your OP Chain based on the intent file.
Once you have [initialized](/chain-operators/tools/op-deployer/usage/init) your intent and state files, you can use the `apply` command to perform the
deployment.
## Usage
You can call the `apply` command like this:
```shell theme={null}
op-deployer apply \
--workdir \
<... additional arguments ...>
```
You will need to specify additional arguments depending on what you're trying to do. See below for a reference of each
supported CLI arg.
## CLI Arguments
### `--deployment-target`
**Default:** `live`
`--deployment-target` specifies where each chain should be deployed to. It can be one of the following values:
* `live`: Deploys to a live L1. Concretely, this means that OP Deployer will send transactions identified by
`vm.broadcast` calls to L1. `--l1-rpc-url` and `--private-key` must be specified when using this target.
* `genesis`: Deploys to an L1 genesis file. This is useful for testing or local development purposes. You do not need to
specify any additional arguments when using this target.
* `calldata`: Deploys to a calldata file. This is useful for generating inputs to multisig wallets for future execution.
* `noop`: Doesn't deploy anything. This is useful for performing a dry-run of the deployment process prior to another
deployment target.
### `--l1-rpc-url`
Defines the RPC URL of the L1 chain to deploy to.
### `--private-key`
Defines the private key to use for signing transactions. This is only required for deployment targets that involve
sending live transactions. Note that ownership over each L2 is transferred to the proxy admin owner specified in the
intent after the deployment completes, so it's OK to use a hot key for this purpose.
# Bootstrap Commands
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/usage/bootstrap
Learn how to deploy global singletons and implementation contracts for new OP Stack deployments.
If you are joining an existing OP Stack deployment, you can skip to the [`init`](/chain-operators/tools/op-deployer/usage/init) and [`apply`](/chain-operators/tools/op-deployer/usage/apply) commands to create your L2 chain(s).
Bootstrap commands are used to deploy global singletons and implementation contracts for new OP Stack deployments.
The deployed contracts can then be used with future invocations of `apply` so that new L2 chains can join that deployment.
Most users won't need to use these commands, since `op-deployer apply` will automatically use standard predeployed contracts for the L1/settlement-layer you are deploying on. However, you will need to use bootstrap commands if you're creating a new standalone deployment.
There are several bootstrap commands available, which you can view by running `op-deployer bootstrap --help`. We'll
focus on the most important ones, which should be run in the sequence listed below.
**It is safe to call these commands from a hot wallet.** None of the contracts deployed by these command are "ownable,"
so the deployment address has no further control over the system.
## Bootstrap Shared Contracts
```shell theme={null}
op-deployer bootstrap superchain \
--l1-rpc-url="" \
--private-key="" \
--outfile="./.deployer/bootstrap_superchain.json" \
--superchain-proxy-admin-owner="" \
--guardian=""
```
### CLI Arguments
#### `--superchain-proxy-admin-owner`, `--guardian`
In a dev environment, these can all be hot wallet EOAs. In a production environment, `--guardian` should be an HSM (hardware security module) protected hot wallet and `--superchain-proxy-admin-owner` should be a multisig cold-wallet (e.g. Gnosis Safe).
### Output
This command will deploy several contracts, and output a JSON like the one below:
```json theme={null}
{
"proxyAdminAddress": "0x269b95a33f48a9055b82ce739b0c105a83edd64a",
"superchainConfigImplAddress": "0x2f4c87818d67fc3c365ea10051b94f98893f3c64",
"superchainConfigProxyAddress": "0xd0c74806fa114c0ec176c0bf2e1e84ff0a8f91a1"
}
```
## Bootstrap Implementations
```shell theme={null}
op-deployer bootstrap implementations \
--l1-rpc-url="" \
--outfile="./.deployer/bootstrap_implementations.json" \
--private-key="" \
--superchain-config-proxy="" \
--superchain-proxy-admin="" \
--challenger="" \
--upgrade-controller=""
```
### Output
This command will deploy implementations, blueprints, and the OPCM. Deployments are (for the most part)
deterministic, so contracts will only be deployed once per chain as long as the implementation and constructor args
remain the same. This applies to the `op-deployer apply` pipeline - that is, if someone else ran `op-deployer bootstrap implementations`
at some point on a given L1 chain, then the `apply` pipeline will re-use those implementations.
The command will output a JSON like the one below:
```json theme={null}
{
"opcmAddress": "0x82879934658738b6d5e8f781933ae7bbae05ba31",
"opcmContractsContainerAddress": "0x1e8de1574a2e085b7a292c760d90cf982d3c1a11",
"opcmGameTypeAdderAddress": "0xcab868d42d9088b86598a96d010db5819c19b847",
"opcmDeployerAddress": "0xf8b6718b28fa36b430334e78adaf97174fed818c",
"opcmUpgraderAddress": "0xa4d0a44890fafce541bdc4c1ca36fca1b5d22f56",
"opcmInteropMigratorAddress": "0xf0fca53bb450dd2230c7eb58a39a5dbfc8492fb6",
"opcmStandardValidatorAddress": "0x1364a02f64f03cd990f105058b8cc93a9a0ab2a1",
"delayedWETHImplAddress": "0x570da3694c06a250aea4855b4adcd09505801f9a",
"optimismPortalImplAddress": "0x1aa1d3fc9b39d7edd7ca69f54a35c66dcf1168f1",
"ethLockboxImplAddress": "0xe6e51fa10d481002301534445612c61bae6b3258",
"preimageOracleSingletonAddress": "0x1fb8cdfc6831fc866ed9c51af8817da5c287add3",
"mipsSingletonAddress": "0x7a8456ba22df0cb303ae1c93d3cf68ea3a067006",
"systemConfigImplAddress": "0x9f2b1fffd8a7aeef7aeeb002fd8477a4868e7e0a",
"l1CrossDomainMessengerImplAddress": "0x085952eb0f0c3d1ca82061e20e0fe8203cdd630a",
"l1ERC721BridgeImplAddress": "0xbafd2cae054ddf69af27517c6bea912de6b7eb8f",
"l1StandardBridgeImplAddress": "0x6abaa7b42b9a947047c01f41b9bcb8684427bf24",
"optimismMintableERC20FactoryImplAddress": "0xdd0b293b8789e9208481cee5a0c7e78f451d32bf",
"disputeGameFactoryImplAddress": "0xe7ab0c07ee92aae31f213b23a132a155f5c2c7cc",
"anchorStateRegistryImplAddress": "0xda4f46fad0e38d763c56da62c4bc1e9428624893",
"superchainConfigImplAddress": "0xdaf60e3c5ef116810779719da88410cce847c2a4"
}
```
# Custom Deployments
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/usage/custom-deployments
Learn how to manage custom deployments with OP Deployer.
While OP Deployer was designed primarily for use with chains that are governed by Optimism, it also supports managing
custom deployments. This is particularly common for RaaS providers, whose customers often request deployments with
custom L1s (or L2s, in the case of L3s) or governance. This guide will walk you through the process of managing these
chains using OP Deployer.
Chains deployed in this way are not subject to Optimism Governance. They may be running customized or unaudited
code. Use at your own risk.
## Bootstrapping
The first step to deploying a custom OP Stack is to bootstrap it onto an L1. This process will:
* Deploy shared management contracts like `SuperchainConfig` and `SuperchainVersions`.
* Deploy contract implementations that will be shared among all OP Chains in this deployment.
* Set up ownership so that you can control the deployment.
You will use the [`bootstrap`](/chain-operators/tools/op-deployer/usage/bootstrap) family of commands on `op-deployer` to do this.
### Bootstrap Shared Contracts
Every OP Chain belongs to a logical deployment group. This group consists of a set of shared contracts that control the behavior
of a group of OP Chains. This includes:
* Pausing bridges
* Signaling which protocol versions are required and recommended
You can deploy a new set of shared contracts for each OP Chain, or you can deploy them once and share across multiple OP Chains.
The choice is up to the deployer. Note that you **cannot** share a set of shared contracts across multiple L1s.
To begin, bootstrap the shared contracts onto your chosen L1 with the following command:
```shell theme={null}
op-deployer bootstrap superchain \
--l1-rpc-url="" \
--private-key="" \
--artifacts-locator="" \
--outfile="" \
--superchain-proxy-admin-owner="" \
--guardian=""
```
This will output a JSON file containing the addresses of the relevant contracts. Keep track of this file, as you will
need it in subsequent steps.
We recommend the following these best practices when bootstrapping:
1. Use Gnosis SAFEs for ownership roles like `guardian` and `superchain-proxy-admin-owner`. The owner **must** be a
smart contract to support future upgrades, so a SAFE is a sensible default.
2. Use a regular EOA as the deployer. It will not have any control over the deployment once the deployment completes.
3. Use a standard contracts tag (e.g. `tag://op-contracts/v2.0.0`). This will make upgrading easier.
### Bootstrapping Implementations
The smart contracts for an OP Chain are deployed using a factory that points to a set of predeployed implementations.
You must deploy the factory and the implementations every time you deploy to a new L1 and whenever new smart
contract versions are released. Implementations are deployed using `CREATE2`, so addresses may be reused if they
already exist on the L1.
You may need to use different versions of OP Deployer depending on which contracts version you are deploying. See the
[releases guide](/chain-operators/tools/op-deployer/reference/releases) for more information on picking the right release.
To deploy the implementations, use the following command:
```shell theme={null}
op-deployer bootstrap implementations \
--artifacts-locator="" \
--l1-rpc-url="" \
--outfile="" \
--mips-version="2" \
--private-key="" \
--superchain-config-proxy="" \
--upgrade-controller=""
```
Similar to the `bootstrap superchain` command, this will output a JSON file containing the addresses of the relevant
contracts. Again, keep track of this file.
The most important address in the implementations file is the OPCM, or OP Contracts Manager. This contract is the
factory that will deploy all the OP Chains belonging to this deployment group. It is also responsible for upgrading between
different contracts versions. Please keep the following **very important** invariants in mind with the OPCM:
* There is a one-to-one mapping between each OPCM, and contracts version.
* Each OPCM is associated with **exactly one** deployment group. This means that you **must** deploy a new OPCM using the
`bootstrap implementations` command for each new deployment group you manage.
## Deploying
After bootstrapping the contracts and implementations, you can deploy your L2 chains with the `apply` command. You
will need to specify a `configType` of `standard-overrides` and set the `opcmAddress` field in your intent to the
address of the OPCM above. **Make sure you call the right OPCM.** Failing to call the right OPCM might lead to
deploying incorrect contract versions, or associating your chain with the wrong deployment.
Make sure that you use the same `l1ContractsLocator` and `l2ContractsLocator` as the ones used in the bootstrap
commands. Otherwise, you may run into deployment errors.
See the following config for an example:
```toml theme={null}
configType = "standard-overrides"
l1ChainID = 11155420
opcmAddress = "0x..."
l1ContractsLocator = "tag://..." # must match the one used in bootstrap
l2ContractsLocator = "tag://..."
[[chains]]
# Chain configs...
```
Once `apply` completes successfully, you can use the `inspect` family of commands to download your chain's L2
genesis and rollup config files.
## Upgrading
The `op-deployer upgrade` command supports upgrades up to `op-contracts/v5.0.0` only. It does **not** support
upgrading from `op-contracts/v5.0.0` to `op-contracts/v6.0.0`. For upgrades beyond v5.0.0, use
[superchain-ops](/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide) or interact with the
OPCM directly. See the [notice](/notices/archive/op-deployer-upgrade-deprecation) for details.
# Init Command
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/usage/init
Learn how to initialize intent and state files for your OP Stack deployment.
The `init` command is used to create a new intent and state file in the specified directory. This command is the
starting point of each new deployment.
## Usage
The `init` command is used like this:
```shell theme={null}
op-deployer init \
--l1-chain-id \
--l2-chain-ids \
--outdir \
--intent-type
```
You should then see the following files appear in your output directory:
```
outdir
├── intent.toml
└── state.json
```
The `intent.toml` file is where you specify the configuration for your deployment. The `state.json` file is where OP
Deployer will output the current state of the deployment after each stage of the deployment.
## Intent File
Your intent should look something like this:
```toml theme={null}
configType = "standard"
l1ChainID = 11155420
fundDevAccounts = false
useInterop = false
l1ContractsLocator = "tag://op-contracts/v1.8.0-rc.4"
l2ContractsLocator = "tag://op-contracts/v1.7.0-beta.1+l2-contracts"
[superchainRoles]
proxyAdminOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
guardian = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
[[chains]]
id = "0x0000000000000000000000000000000000000000000000000000000000002390"
baseFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
l1FeeVaultRecipient = "0x0000000000000000000000000000000000000000"
sequencerFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
operatorFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
eip1559DenominatorCanyon = 250
eip1559Denominator = 50
eip1559Elasticity = 6
[chains.roles]
l1ProxyAdminOwner = "0x0000000000000000000000000000000000000000"
l2ProxyAdminOwner = "0x0000000000000000000000000000000000000000"
systemConfigOwner = "0x0000000000000000000000000000000000000000"
unsafeBlockSigner = "0x0000000000000000000000000000000000000000"
batcher = "0x0000000000000000000000000000000000000000"
proposer = "0x0000000000000000000000000000000000000000"
challenger = "0x0000000000000000000000000000000000000000"
```
Before you can use your intent file for a deployment, you will need to update all zero values to whatever is
appropriate for your chain. For dev environments, it is ok to use all EOAs/hot-wallets.
## Production Setup
In production environments, you should use a more secure setup with cold-wallet multisigs (e.g. Gnosis Safes) for the following:
* `baseFeeVaultRecipient`
* `l1FeeVaultRecipient`
* `sequencerFeeVaultRecipient`
* `operatorFeeVaultRecipient`
* `l1ProxyAdminOwner`
* `l2ProxyAdminOwner`
* `systemConfigOwner`
HSMs (hardware security modules) are recommended for the following hot-wallets:
* `unsafeBlockSigner`
* `batcher`
* `proposer`
* `challenger`
# Release Workflows
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/usage/release-workflows
Learn how to backport fixes onto earlier OP Deployer versions and add support for new contract versions.
This page is for people developing OP Deployer itself. If you only need to pick the right OP Deployer release
for your contract version, see the [releases reference](/chain-operators/tools/op-deployer/reference/releases).
## Backport a Fix to an Earlier Version
From time to time, we may backport bugfixes from develop onto earlier versions of OP Deployer. The process for this is
as follows:
1. If one doesn't exist already, make a new branch for the version lineage you're patching (e.g. `v0.2.x`). This branch
should be protected (not deletable) and should be based on the latest release of that lineage. The branch should be
named as follows:
`backports/op-deployer/`.
2. Open a PR with the backport against that branch. Be sure to reference the original commit in the backport.
3. Make and push a new tag on that lineage.
Example for backporting fix(es) from `develop` to create a new release `op-deployer/v0.2.1`:
```
git checkout -b backports/op-deployer/v0.2.0 op-deployer/v0.2.0
git push origin backports/op-deployer/v0.2.0
git checkout -b fixes/deployer-v0.2.0 backports/op-deployer/v0.2.0
git cherry-pick
git push origin fixes/deployer-v0.2.0
1. open pr from fixes/deployer-v0.2.0 targeting backports/op-deployer/v0.2.0
2. merge the pr
3. push a new tag for op-deployer/v0.2.1 on backports/op-deployer/v0.2.0 branch (goreleaser will create the release)
```
## Add Support for a New Contract Version
Adding support for a new contract version is a multi-step process. Here's a high-level overview. For the sake of
simplicity we will assume you are adding support for a new `rc` release.
### Step 1: Add Support on Develop
First, you need to add support for the new contract version on the `develop` branch. This means ensuring that the
deployment pipeline supports whatever changes are required for the new version. Typically, this means passing in new
deployment variables, and responding to ABI changes in the Solidity scripts/OPCM.
### Step 2: Add the Published Artifacts
Run the following from the root of the monorepo:
```bash theme={null}
cd packages/contracts-bedrock
just clean
just build
bash scripts/ops/calculate-checksum.sh
# copy the outputted checksum
cd ../../op-deployer
just calculate-artifacts-hash
```
This will calculate the checksum of your artifacts as well as the hash of the artifacts tarball. OP Deployer uses
these values to download and verify tagged contract locators.
Now, update `standard/standard.go` with these values so that the new artifacts tarball can be downloaded:
```go theme={null}
// Add a new const for your release
const ContractsVXTag = "op-contracts/vX.Y.Z"
var taggedReleases = map[string]TaggedRelease{
// Other releases...
ContractsVXTag: {
ArtifactsHash: common.HexToHash(""),
ContentHash: common.HexToHash(""),
},
}
// Update the L1/L2 versions accordingly
func IsSupportedL1Version(tag string) bool {
return tag == ContractsVXTag
}
```
### Step 3: Update the SR With the New Release
Add the new RC to the [standard versions](https://github.com/ethereum-optimism/superchain-registry/tree/main/validation/standard) in the superchain-registry.
### Step 4: Update the Validation Package
The SR is pulled into OP Deployer via the `validation` package. Update it by running the following command from the
root of the monorepo:
```shell theme={null}
go get -u github.com/ethereum-optimism/superchain-registry/validation@
```
That should be it!
# Verify Command
Source: https://docs.optimism.io/chain-operators/tools/op-deployer/usage/verify
Learn how to verify deployed contract source code on block explorers.
Once you have deployed contracts via [`bootstrap`](/chain-operators/tools/op-deployer/usage/bootstrap) or [`apply`](/chain-operators/tools/op-deployer/usage/apply), you can use the `verify` command to verify the source code on block explorers like Etherscan or Blockscout. The command uses the `forge verify-contract` binary, which automatically handles constructor argument detection and source code verification.
## Usage
You can call the `verify` command like this:
```shell theme={null}
op-deployer verify \
--l1-rpc-url \
--input-file \
--verifier-api-key \
--artifacts-locator \
--verifier etherscan
```
For Blockscout verification (uses default URLs for mainnet/sepolia, no API key required):
```shell theme={null}
op-deployer verify \
--l1-rpc-url \
--input-file \
--artifacts-locator \
--verifier blockscout
```
For custom block explorer verification (Etherscan v2-compatible, API key may be required):
```shell theme={null}
op-deployer verify \
--l1-rpc-url \
--input-file \
--artifacts-locator \
--verifier custom \
--verifier-url
```
## CLI Arguments
### `--l1-rpc-url`
Defines the RPC URL of the L1 chain to deploy to (currently only supports mainnet and sepolia).
### `--input-file`
The full filepath to the input file. This can be either:
* A simple JSON file with contract name/address pairs (output from `bootstrap superchain|implementations`)
* A complete `state.json` file (output from `apply`)
The verifier automatically detects the file format and extracts all contracts. Unless the `--contract-name` flag is passed, all contracts in the input file will be verified.
Example:
```json theme={null}
{
"opcmAddress": "0x437d303c20ea12e0edba02478127b12cbad54626",
"opcmContractsContainerAddress": "0xf89d7ce62fc3a18354b37b045017d585f7e332ab",
"opcmGameTypeAdderAddress": "0x9aa4b6c0575e978dbe6d6bc31b7e4403ea8bd81d",
"opcmDeployerAddress": "0x535388c15294dc77a287430926aba5ba5fe6016a",
"opcmUpgraderAddress": "0x68a7a93750eb56dd043f5baa41022306e6cd50fa",
"delayedWETHImplAddress": "0x33ddc90167c923651e5aef8b14bc197f3e8e7b56",
"optimismPortalImplAddress": "0x54b75cb6f44e36768912e070cd9cb995fc887e6c",
"ethLockboxImplAddress": "0x05484deeb3067a5332960ca77a5f5603df878ced",
"preimageOracleSingletonAddress": "0xfbcd4b365f97cb020208b5875ceaf6de76ec068b",
"mipsSingletonAddress": "0xcc50288ad0d79278397785607ed675292dce37b1",
"systemConfigImplAddress": "0xfb24aa6d99824b2c526768e97b23694aa3fe31d6",
"l1CrossDomainMessengerImplAddress": "0x957c0bf84fe541efe46b020a6797fb1fb2eaa6ac",
"l1ERC721BridgeImplAddress": "0x62786d16978436f5d85404735a28b9eb237e63d0",
"l1StandardBridgeImplAddress": "0x6c9b377c00ec7e6755aec402cd1cfff34fa75728",
"optimismMintableERC20FactoryImplAddress": "0x3842175f3af499c27593c772c0765f862b909b93",
"disputeGameFactoryImplAddress": "0x70ed1725abb48e96be9f610811e33ed8a0fa97f9",
"anchorStateRegistryImplAddress": "0xce2206af314e5ed99b48239559bdf8a47b7524d4",
"superchainConfigImplAddress": "0x77008cdc99fb1cf559ac33ca3a67a4a2f04cc5ef"
}
```
### `--contract-name` (optional)
Specifies a single contract name, matching a contract key within the input file, to verify. If not provided, all contracts in the input file will be verified.
### `--artifacts-locator`
The locator to forge-artifacts containing the output of the `forge build` command (i.e. compiled bytecode and solidity source code). This can be a local path (with a `file://` prefix), remote URL (with a `http://` or `https://` prefix), or standard contracts tag (with a `tag://op-contracts/v` prefix).
### `--verifier`
The block explorer(s) to use for verification. Supports multiple verifiers separated by commas.
Options:
* `etherscan` (default): Uses Etherscan for mainnet/sepolia
* `blockscout`: Uses default Blockscout URLs for mainnet/sepolia
* `custom`: For custom Etherscan v2-compatible instances (requires `--verifier-url`)
Examples:
* Single verifier: `--verifier etherscan`
* Multiple verifiers: `--verifier etherscan,blockscout` (verifies on both)
### `--verifier-url`
The verifier API URL. Usage varies by verifier type:
* `etherscan`: Ignored (automatically determined from chain ID)
* `blockscout`: Optional (defaults to standard Blockscout URLs for mainnet/sepolia)
* `custom`: Required. Example: `https://etherscanv2.compat-api.example.com/api`
## Output
Output logs will be printed to the console and look something like the following. If the final results show `numFailed=0`, all contracts were verified successfully.
```sh theme={null}
INFO [03-05|15:56:55.900] Formatting etherscan verify request name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:56:55.900] Opening artifact path=Proxy.sol/Proxy.json name=superchainConfigProxyAddress
INFO [03-05|15:56:55.905] contractName name=src/universal/Proxy.sol:Proxy
INFO [03-05|15:56:55.905] Extracting constructor args from initcode address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A argSlots=1
INFO [03-05|15:56:56.087] Contract creation tx hash txHash=0x71b377ccc11304afc32e1016c4828a34010a0d3d81701c7164fb19525ba4fbc4
INFO [03-05|15:56:56.494] Successfully extracted constructor args address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:56:56.683] Verification request submitted name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:57:02.035] Verification complete name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:57:07.971] --- COMPLETE ---
INFO [03-05|15:57:07.971] final results numVerified=2 numSkipped=1 numFailed=0
```
## Automatic Verification
You can automatically verify contracts after deployment by using the `--verify` flag with `apply` or `bootstrap` commands:
```shell theme={null}
op-deployer apply \
--workdir ./.deployer \
--l1-rpc-url \
--private-key \
--verify \
--verifier-api-key
```
This will verify all deployed contracts at the end of the deployment process.
### Multi-Verifier Deployment
You can verify on multiple block explorers simultaneously:
```shell theme={null}
op-deployer bootstrap superchain \
--l1-rpc-url \
--private-key \
--outfile ./superchain.json \
--superchain-proxy-admin-owner \
--guardian \
--verify \
--verifier etherscan,blockscout \
--verifier-api-key
```
This will:
1. Deploy the shared contracts
2. Verify on Etherscan (using the API key)
3. Verify on Blockscout (no API key required)
4. Report combined results from both verifiers
## Supported Contract Bundles
The verify command now supports all contract bundles:
* **Shared** contracts (from `bootstrap superchain`)
* **Implementations** contracts (from `bootstrap implementations`)
* **OpChain** contracts (from `apply` - including all chain-specific contracts)
When using a `state.json` file from `apply`, the verifier automatically extracts and verifies contracts from all deployment stages.
## Block Explorer Support
The verification command supports both Etherscan and Blockscout block explorers through the forge binary, alongside any Etherscan v2 compatible APIs.
# OP Interop Filter
Source: https://docs.optimism.io/chain-operators/tools/op-interop-filter
Learn how op-interop-filter validates interop executing messages so the execution layer can reject invalid cross-chain transactions before they reach the sequencer.
OP Stack interop is in active development.
op-interop-filter is the service chain operators will run to validate interop transactions for the execution layer, and the interfaces described here may continue to evolve as the rollout progresses.
*op-interop-filter* is a lightweight service that validates interop executing messages so that an execution layer (EL) client — op-reth — can reject invalid cross-chain transactions before they reach the sequencer's transaction pool.
It is the service a chain operator runs to answer the `interop_checkAccessList` RPC that the EL calls on every interop transaction.
## What op-interop-filter does
An interop transaction carries an *access list* of references to initiating messages on other chains in the dependency set.
For the transaction to be valid, every referenced initiating message must exist on its source chain at the safety level the executing chain requires.
The EL cannot answer that question on its own.
It calls a configured interop verification endpoint over JSON-RPC (`interop_checkAccessList`) and lets that service decide whether the transaction is admissible.
op-interop-filter is one such service.
It connects to the L2 RPCs of every chain in the dependency set, ingests their logs into a local database, and answers `interop_checkAccessList` against that database.
When the answer is no — or when the filter has lost confidence in its own state — the EL rejects the transaction.
## How it fits into an interop deployment
op-interop-filter sits between an interop chain's EL and the other chains in its dependency set.
```mermaid theme={null}
graph LR
classDef filter fill:#FFE
classDef transparent fill:none,stroke:none
EL["Execution layer
(op-reth)"]
Filter["op-interop-filter"]
L2A["L2 RPC
Chain A"]
L2B["L2 RPC
Chain B"]
L2N["L2 RPC
Chain N"]
EL -- "interop_checkAccessList" --> Filter
Filter --> L2A
Filter --> L2B
Filter --> L2N
class Filter filter
```
The EL points at the filter through op-reth's `--rollup.interop-http` flag and calls the filter's `interop_checkAccessList` method to validate each interop transaction.
This is a different job from what [op-supernode](/op-stack/interop/supernode) does.
The supernode operates at the consensus layer (CL): it verifies that a block's cross-chain dependencies have been reproduced from L1, then promotes the block to *safe* through the chain's own CL.
op-interop-filter works at transaction-admission time instead: it is the separate service the execution layer consults to decide whether an interop transaction may enter the pool in the first place.
A chain operator running an interop chain runs both.
## Failsafe behavior
op-interop-filter has a *failsafe* that, while active, rejects every `interop_checkAccessList` request with `ErrFailsafeEnabled`.
The EL gates interop transactions on that check, so while failsafe is active no interop transactions are admitted.
Regular transactions that carry no interop access-list entries are unaffected and keep flowing normally.
Failsafe activates in one of two ways:
* **Automatically**, when any chain ingester reports an error — a reorg, a database conflict, data corruption, or an invalid executing message — or when the cross-validator reports an error.
* **Manually**, when an operator enables it over the admin RPC with `admin_setFailsafeEnabled`.
How failsafe clears depends on why it triggered:
* A **manual** failsafe clears when the operator calls `admin_setFailsafeEnabled` with `false`.
* A **reorg-triggered** failsafe clears automatically only when `--reorg-recovery-enabled` is set: the filter rewinds each affected chain's logs database to its finalized block and clears the error.
* Any failsafe that is neither manual nor auto-resolved — a reorg with `--reorg-recovery-enabled` off, a database conflict, data corruption, an invalid executing message, or a cross-validation failure — has no admin RPC to clear it. Recover by wiping the filter's data directory and restarting the service.
Disabling the manual override with `admin_setFailsafeEnabled` does not clear an error-triggered failsafe; the underlying error has to be resolved first.
## Configuration
op-interop-filter is configured via CLI flags or matching `OP_INTEROP_FILTER_*` environment variables.
The full flag set is defined in [`op-interop-filter/flags/flags.go`](https://github.com/ethereum-optimism/optimism/blob/develop/op-interop-filter/flags/flags.go).
### Required flags
| Flag | Description |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--l2-rpcs` | L2 RPC endpoints to connect to. The filter queries the chain ID from each endpoint and matches it against a loaded rollup config. |
| `--networks` *or* `--rollup-configs` | At least one is required. `--networks` loads rollup configs by name from the superchain registry; `--rollup-configs` loads custom JSON files for dev or test chains. |
### Key optional flags
| Flag | Default | Description |
| -------------------------- | --------------------- | -------------------------------------------------------------------------------------------------- |
| `--data-dir` | (temporary directory) | Directory for the LogsDB. Use a persistent path in production so backfill state survives restarts. |
| `--backfill-duration` | `24h` | How far back to backfill on startup. |
| `--message-expiry-window` | `168h` (7 days) | Messages older than this window are treated as expired. |
| `--poll-interval` | `2s` | How often to poll L2 RPCs for new blocks. |
| `--reorg-recovery-enabled` | off | If set, the filter automatically clears reorg-triggered failsafe by rewinding to finalized. |
| `--admin.rpc.addr` | (disabled) | Bind address for the JWT-protected admin RPC. When set, `--admin.jwt-secret` is also required. |
### Use-with-caution flags
Two flags are marked DANGEROUS in source and should only be used with a deliberate operational reason:
| Flag | What it does |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dangerously-enable-passthrough` | Lets every transaction through without interop filtering. Disables all executing-message validation. |
| `--support-legacy-check-access-list-format` | Accepts `interop_checkAccessList` requests that omit the executing chain ID. Intended only for compatibility with legacy clients; access-list source-chain validation still runs. |
## RPC surface
op-interop-filter exposes two HTTP RPC endpoints, configured separately.
### Public RPC
Bound by `--rpc.addr` (default `0.0.0.0`) and `--rpc.port` (default `8545`).
Exposes the methods an EL or other consumer needs:
* `interop_checkAccessList` — validates an executing-message access list at a minimum safety level. This is the method the EL calls on every interop transaction.
* `interop_getBlockHashByNumber` — returns the latest ingested block hash for a chain, or the hash at a specific height.
* `admin_getFailsafeEnabled` — read-only check of whether failsafe is currently active.
### Admin RPC
Disabled by default.
Bound by `--admin.rpc.addr` and `--admin.rpc.port` (default `8546`), and protected by a JWT secret loaded from `--admin.jwt-secret`.
The admin RPC exposes the operator-only controls:
* `admin_setFailsafeEnabled` — manually enable or disable failsafe.
* `admin_getFailsafeEnabled` — the authenticated read of the same state available on the public RPC.
Admin RPC is intended for operator tooling and should not be exposed publicly.
## Where to go next
* Read the [interop explainer](/op-stack/interop/explainer) for how cross-chain messaging and executing-message access lists work at the protocol level.
* Read the [op-supernode page](/op-stack/interop/supernode) for the consensus-layer side of an interop deployment — block-safety promotion and the supernode topology.
* Read [interop reorg awareness](/op-stack/interop/reorg) for how reorgs interact with cross-chain message safety.
* For implementation detail, see the [op-interop-filter source](https://github.com/ethereum-optimism/optimism/tree/develop/op-interop-filter) in the monorepo.
# OP Txproxy
Source: https://docs.optimism.io/chain-operators/tools/op-txproxy
A passthrough proxy service that can apply additional constraints on transactions prior to reaching the sequencer.
A [passthrough proxy](https://github.com/ethereum-optimism/infra/tree/main/op-txproxy) for the execution engine endpoint. This proxy does not forward all RPC traffic and only exposes a specific set of methods. Operationally, the ingress router should only re-route requests for these specific methods.
[proxyd](./proxyd) as an ingress router supports the mapping of specific methods to unique backends.
## Methods
### **eth\_sendRawTransactionConditional**
To safely expose this endpoint publicly, additional stateless constraints are applied. These constraints help scale validation rules horizontally and preemptively reject conditional transactions before they reach the sequencer.
Various metrics are emitted to guide necessary adjustments.
#### Runtime shutoff
This service can be configured with a flag or environment variable to reject conditional transactions without needing to interrupt the execution engine. This feature is useful for diagnosing issues.
`--sendRawTxConditional.enabled (default: true) ($OP_TXPROXY_SENDRAWTXCONDITIONAL_ENABLED)`
When disabled, requests will fail with the `-32003` (transaction rejected) json rpc error code with a message stating that the method is disabled.
#### Rate limits
Even though the op-geth implementation of this endpoint includes rate limits, it is instead applied here to terminate these requests early.
`--sendRawTxConditional.ratelimit (default: 5000) ($OP_TXPROXY_SENDRAWTXCONDITIONAL_RATELIMIT)`
#### Stateless validation
* Conditional cost is below the max
* Conditional values are valid (i.e min \< max)
* Transaction targets are only 4337 Entrypoint contracts
The motivating factor for this endpoint is to enable permissionless 4337 mempools, hence the restricted usage of this method to just [Entrypoint](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol) transactions.
Please open up an issue if you'd like this restriction to be optional via configuration to broaden usage of this endpoint.
When the request passes validation, it is passed through to the configured backend URL
`--sendRawTxConditional.backend ($OP_TXPROXY_SENDRAWTXCONDITIONAL_BACKENDS)`
Per the [specification](/op-stack/features/send-raw-transaction-conditional), conditional transactions are not gossiped between peers. Thus, if you use replicas in an active/passive sequencer setup, this request must be broadcasted to all replicas.
[proxyd](./proxyd) as an egress router for this method supports this broadcasting functionality.
## How it works
To start using `op-txproxy`, follow these steps:
1. Run the following command to build the binary
```bash theme={null}
make build
```
2. This will build and output the binary under `/bin/op-txproxy`.
The image for this binary is also available as a [docker artifact](https://us-docker.pkg.dev/oplabs-tools-artifacts/images/op-txproxy).
The binary accepts configuration through CLI flags, which also settable via environment variables. Either set the flags explicitly when starting the binary or set the environment variables of the host starting the proxy.
See [methods](#methods) on the configuration options available for each method.
start the service with the following command
```bash theme={null}
op-txproxy // ... with flags if env variables are not set
```
# Run proxyd
Source: https://docs.optimism.io/chain-operators/tools/proxyd
Learn how to build, configure, and run proxyd, the OP Stack RPC request router and proxy.
`proxyd` is an important RPC request router and proxy used within the OP Stack infrastructure. It enables operators to efficiently route and manage RPC requests across multiple backend services, ensuring performance, fault tolerance, and security.
## Key features
* RPC method whitelisting
* Backend request routing
* Automatic retries for failed backend requests
* Consensus tracking (latest, safe, and finalized blocks)
* Request/response rewriting to enforce consensus
* Load balancing across backend services
* Caching of immutable responses
* Metrics for request latency, error rates, and backend health
## How it works
To start using `proxyd`, follow these steps:
* Run the following command to build the `proxyd` binary:
```bash theme={null}
make proxyd
```
* This will build the `proxyd` binary. No additional dependencies are required.
* Create a configuration file to define your proxy backends and routing rules.
* Refer to [example.config.toml](https://github.com/ethereum-optimism/infra/blob/main/proxyd/example.config.toml) for a full list of options with commentary.
Once the configuration file is ready, start the `proxyd` service using the following command:
```bash theme={null}
proxyd
```
## Consensus awareness
Version 4.0.0 and later include consensus awareness to minimize chain reorganizations.
Set `consensus_aware` to `true` in the configuration to enable:
* Polling backends for consensus data (latest block, safe block, peer count, etc.).
* Resolving consensus groups based on healthiest backends
* Enforcing consensus state across client requests
## Caching and metrics
### Cacheable methods
Certain immutable methods, such as `eth_chainId` and `eth_getBlockByHash`, can be cached using Redis to optimize performance.
### Metrics
Extensive metrics are available to monitor request latency, error rates, backend health, and more. These can be configured via `metrics.port` and `metrics.host` in the configuration file.
## Next steps
* Read about the [OP Stack chain architecture](/chain-operators/guides/management/network-architecture).
* Find out how you can support [snap sync](/chain-operators/guides/features/snap-sync).
on your chain.
* Find out how you can utilize [blob space](/chain-operators/guides/features/blobs)
to reduce the transaction fee cost on your chain.
# Generating absolute prestate and preimage files
Source: https://docs.optimism.io/chain-operators/tutorials/absolute-prestate
Generate, verify, and configure the kona-client absolute prestate for permissionless fault proofs.
# Overview
The absolute prestate is the on-chain commitment to a specific build of `kona-client`. The matching preimage (a gzipped binary) is what `op-challenger` runs at dispute time. This guide is the minimal reproducer.
As of [Upgrade 19](/notices/archive/upgrade-19), `CANNON_KONA` (game type `8`) is the respected game type for permissionless fault proofs. If your chain is not yet in the public Superchain Registry, follow [Generating a custom kona-client absolute prestate](/chain-operators/tutorials/kona-custom-prestate) instead.
## Prerequisites
* [Docker](https://docs.docker.com/engine/install/) running
* [`just`](https://github.com/casey/just) installed
## Generate and verify the prestate
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
git checkout kona-node/v
```
```bash theme={null}
just reproducible-prestate-kona
```
The build runs in Docker and writes artifacts to `rust/kona/prestate-artifacts-cannon/`.
```bash theme={null}
jq -r .pre rust/kona/prestate-artifacts-cannon/prestate-proof.json
```
Match the printed hash against the `cannon64-kona` entry for your release tag in [`standard-prestates.toml`](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml). A match confirms your build environment is honest.
## Configure op-challenger
Upload the hash-named file `rust/kona/prestate-artifacts-cannon/0x.bin.gz` (produced by the build) to a location reachable by your `op-challenger` instances, then add the kona-specific env vars to your existing challenger config:
```bash theme={null}
OP_CHALLENGER_TRACE_TYPE=cannon-kona,permissioned
OP_CHALLENGER_CANNON_KONA_PRESTATES_URL=
```
The challenger appends `/0x.bin.gz` to `*_PRESTATES_URL` to resolve the right binary per dispute. Your existing `OP_CHALLENGER_ROLLUP_CONFIG`, `OP_CHALLENGER_L2_GENESIS`, and `OP_CHALLENGER_GAME_FACTORY_ADDRESS` continue to apply unchanged.
## Next Steps
* [Generating a custom kona-client absolute prestate](/chain-operators/tutorials/kona-custom-prestate) — for chains not yet in the Superchain Registry.
* [Migrating to permissionless fault proofs](/chain-operators/tutorials/migrating-permissionless).
* [Fault proofs explainer](/op-stack/fault-proofs/explainer).
# Adding attributes to the derivation function
Source: https://docs.optimism.io/chain-operators/tutorials/adding-derivation-attributes
Learn how to modify the derivation function for an OP Stack chain to track the amount of ETH being burned on L1.
OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use.
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks. Be prepared to get your hands dirty and to work without support.
## Overview
In this tutorial, you'll modify the Bedrock Rollup. Although there are many ways to modify the OP Stack, you're going to spend this tutorial modifying the Derivation function. Specifically, you're going to update the Derivation function to track the amount of ETH being burned on L1! Who's gonna tell [ultrasound.money](http://ultrasound.money) that they should replace their backend with an OP Stack chain?
## Getting the idea
Let's quickly recap what you're about to do. The `op-node` is responsible for generating the Engine API payloads that trigger `op-geth` to produce blocks and transactions. The `op-node` already generates a "system transaction" for every L1 block that relays information about the current L1 state to the L2 chain. You're going to modify the `op-node` to add a new system transaction that reports the total burn amount (the base fee multiplied by the gas used) in each block.
Although it might sound like a lot, the whole process only involves deploying a single smart contract, adding one new file to `op-node`, and modifying one existing file inside `op-node`. It'll be painless. Let's go!
## Deploy the burn contract
You're going to use a smart contract on your Rollup to store the reports that the `op-node` makes about the L1 burn. Here's the code for your smart contract:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title L1Burn
* @notice L1Burn keeps track of the total amount of ETH burned on L1.
*/
contract L1Burn {
/**
* @notice Total amount of ETH burned on L1.
*/
uint256 public total;
/**
* @notice Mapping of block numbers to total burn.
*/
mapping (uint64 => uint256) public reports;
/**
* @notice Allows the system address to submit a report.
*
* @param _blocknum L1 block number the report corresponds to.
* @param _burn Amount of ETH burned in the block.
*/
function report(uint64 _blocknum, uint64 _burn) external {
require(
msg.sender == 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001,
"L1Burn: reports can only be made from system address"
);
total += _burn;
reports[_blocknum] = total;
}
/**
* @notice Tallies up the total burn since a given block number.
*
* @param _blocknum L1 block number to tally from.
*
* @return Total amount of ETH burned since the given block number;
*/
function tally(uint64 _blocknum) external view returns (uint256) {
return total - reports[_blocknum];
}
}
```
Deploy this smart contract to your L2 (using any tool you find convenient). Make a note of the address that the contract is deployed to because you'll need it in a minute. Simple!
## Add the burn transaction
Now you need to add logic to the `op-node` to automatically submit a burn report whenever an L1 block is produced. Since this transaction is very similar to the system transaction that reports other L1 block info (found in [l1\_block\_info.go](https://github.com/ethereum-optimism/optimism/blob/develop/op-node/rollup/derive/l1_block_info.go)), you'll use that transaction as a jumping-off point.
```bash theme={null}
cd ~/optimism/op-node
```
```bash theme={null}
touch rollup/derive/l1_burn_info.go
```
```go theme={null}
package derive
import (
"bytes"
"encoding/binary"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-node/eth"
)
const (
L1BurnFuncSignature = "report(uint64,uint64)"
L1BurnArguments = 2
L1BurnLen = 4 + 32*L1BurnArguments
)
var (
L1BurnFuncBytes4 = crypto.Keccak256([]byte(L1BurnFuncSignature))[:4]
L1BurnAddress = common.HexToAddress("YOUR_BURN_CONTRACT_HERE")
)
type L1BurnInfo struct {
Number uint64
Burn uint64
}
func (info *L1BurnInfo) MarshalBinary() ([]byte, error) {
data := make([]byte, L1BurnLen)
offset := 0
copy(data[offset:4], L1BurnFuncBytes4)
offset += 4
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Number)
offset += 32
binary.BigEndian.PutUint64(data[offset+24:offset+32], info.Burn)
return data, nil
}
func (info *L1BurnInfo) UnmarshalBinary(data []byte) error {
if len(data) != L1InfoLen {
return fmt.Errorf("data is unexpected length: %d", len(data))
}
var padding [24]byte
offset := 4
info.Number = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 burn tx number exceeds uint64 bounds: %x", data[offset:offset+32])
}
offset += 32
info.Burn = binary.BigEndian.Uint64(data[offset+24 : offset+32])
if !bytes.Equal(data[offset:offset+24], padding[:]) {
return fmt.Errorf("l1 burn tx burn exceeds uint64 bounds: %x", data[offset:offset+32])
}
return nil
}
func L1BurnDepositTxData(data []byte) (L1BurnInfo, error) {
var info L1BurnInfo
err := info.UnmarshalBinary(data)
return info, err
}
func L1BurnDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig) (*types.DepositTx, error) {
infoDat := L1BurnInfo{
Number: block.NumberU64(),
Burn: block.BaseFee().Uint64() * block.GasUsed(),
}
data, err := infoDat.MarshalBinary()
if err != nil {
return nil, err
}
source := L1InfoDepositSource{
L1BlockHash: block.Hash(),
SeqNumber: seqNumber,
}
return &types.DepositTx{
SourceHash: source.SourceHash(),
From: L1InfoDepositerAddress,
To: &L1BurnAddress,
Mint: nil,
Value: big.NewInt(0),
Gas: 150_000_000,
IsSystemTransaction: true,
Data: data,
}, nil
}
func L1BurnDepositBytes(seqNumber uint64, l1Info eth.BlockInfo, sysCfg eth.SystemConfig) ([]byte, error) {
dep, err := L1BurnDeposit(seqNumber, l1Info, sysCfg)
if err != nil {
return nil, fmt.Errorf("failed to create L1 burn tx: %w", err)
}
l1Tx := types.NewTx(dep)
opaqueL1Tx, err := l1Tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to encode L1 burn tx: %w", err)
}
return opaqueL1Tx, nil
}
```
Feel free to take a look at this file if you're interested. It's relatively simple, mainly just defining a new transaction type and describing how the transaction should be encoded.
## Insert the burn transactions
Finally, you'll need to update `~/optimism/op-node/rollup/derive/attributes.go` to insert the new burn transaction into every block. You'll need to make the following changes:
```go theme={null}
l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig)
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
}
```
```go theme={null}
l1BurnTx, err := L1BurnDepositBytes(seqNumber, l1Info, sysConfig)
if err != nil {
return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
}
```
```go theme={null}
txs := make([]hexutil.Bytes, 0, 1+len(depositTxs))
txs = append(txs, l1InfoTx)
```
to
```go theme={null}
txs := make([]hexutil.Bytes, 0, 2+len(depositTxs))
txs = append(txs, l1InfoTx)
txs = append(txs, l1BurnTx)
```
All you're doing here is creating a new burn transaction after every `l1InfoTx` and inserting it into every block.
## Rebuild your op-node
Before you can see this change take effect, you'll need to rebuild your `op-node`:
```bash theme={null}
cd ~/optimism/op-node
make op-node
```
Now start your `op-node` if it isn't running or restart your `op-node` if it's already running. You should see the change immediately — new blocks will contain two system transactions instead of just one!
## Checking the result
Query the `total` function of your contract, you should also start to see the total slowly increasing. Play around with the `tally` function to grab the amount of gas burned since a given L2 block. You could use this to implement a version of [ultrasound.money](http://ultrasound.money) that keeps track of things with an OP Stack as a backend.
One way to get the total is to run these commands:
```bash theme={null}
export ETH_RPC_URL=http://localhost:8545
cast call "total()" | cast --from-wei
```
## Conclusion
With just a few tiny changes to the `op-node`, you were just able to implement a change to the OP Stack that allows you to keep track of the L1 ETH burn on L2. With a live Cannon Fault Proof System, you should not only be able to track the L1 burn on L2, you should be able to *prove* the burn to contracts back on L1. That's crazy!
The OP Stack is an extremely powerful platform that allows you to perform a large amount of computation trustlessly. It's a superpower for smart contracts. Tracking the L1 burn is just one of the many, many wild things you can do with the OP Stack. If you're looking for inspiration or you want to see what others are building on the OP Stack, check out the OP Stack Hacks page. Maybe you'll find a project you want to work on, or maybe you'll get the inspiration you need to build the next killer smart contract.
# Adding a precompile
Source: https://docs.optimism.io/chain-operators/tutorials/adding-precompiles
Learn how to run an EVM with a new precompile for OP Stack chain operations to speed up calculations that are not currently supported.
OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use.
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks. Be prepared to get your hands dirty and to work without support.
One possible use of OP Stack is to run an EVM with a new precompile for operations to speed up calculations that are not currently supported. In this tutorial, you'll make a simple precompile that returns a constant value if it's called with four or less bytes, or an error if it is called with more than that.
To create a new precompile, the file to modify is [`op-geth/core/vm/contracts.go`](https://github.com/ethereum-optimism/op-geth/blob/optimism-history/core/vm/contracts.go).
* add a structure named after your new precompile, and
* use an address that is unlikely to ever clash with a standard precompile and avoids the [EIP-7587](https://eips.ethereum.org/EIPS/eip-7587) reserved range (0x1337, for example):
```go theme={null}
common.BytesToAddress([]byte{0x13,0x37}): &retConstant{},
```
```go theme={null}
type retConstant struct{}
func (c *retConstant) RequiredGas(input []byte) uint64 {
return uint64(1024)
}
var (
errConstInvalidInputLength = errors.New("invalid input length")
)
func (c *retConstant) Run(input []byte) ([]byte, error) {
// Only allow input up to four bytes (function signature)
if len(input) > 4 {
return nil, errConstInvalidInputLength
}
output := make([]byte, 6)
for i := 0; i < 6; i++ {
output[i] = byte(64+i)
}
return output, nil
}
```
```bash theme={null}
cd ~/op-geth
make geth
```
```bash theme={null}
cast call 0x0000000000000000000000000000000000001337 "whatever()"
cast call 0x0000000000000000000000000000000000001337 "whatever(string)" "fail"
```
## How does it work?
This is the precompile interface definition:
```go theme={null}
type PrecompiledContract interface {
RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use
Run(input []byte) ([]byte, error) // Run runs the precompiled contract
}
```
It means that for every precompile you need two functions:
* `RequiredGas` which returns the gas cost for the call. This function takes an array of bytes as input, and returns a single value, the gas cost.
* `Run` which runs the actual precompile. This function also takes an array of bytes, but it returns two values: the call's output (a byte array) and an error.
For every fork that changes the precompiles you have a [`map`](https://www.w3schools.com/go/go_maps.php) from addresses to the `PrecompiledContract` definitions:
```go theme={null}
// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum
// contracts used in the Berlin release.
var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): &ecrecover{},
.
.
.
common.BytesToAddress([]byte{9}): &blake2F{},
common.BytesToAddress([]byte{0x13,0x37}): &retConstant{},
}
```
The key of the map is an address. You create those from bytes using `common.BytesToAddress([]byte{})`. In this case you have two bytes, `0x13` and `0x37`. Together you get the address `0x0…1337`.
The syntax for a precompiled contract interface is `&{}`.
The next step is to define the precompiled contract itself.
```go theme={null}
type retConstant struct{}
```
First you create a structure for the precompile.
```go theme={null}
func (c *retConstant) RequiredGas(input []byte) uint64 {
return uint64(1024)
}
```
Then you define a function as part of that structure. Here you just require a constant amount of gas, but of course the calculation can be a lot more sophisticated.
```go theme={null}
var (
errConstInvalidInputLength = errors.New("invalid input length")
)
```
Next you define a variable for the error.
```go theme={null}
func (c *retConstant) Run(input []byte) ([]byte, error) {
```
This is the function that actually executes the precompile.
```go theme={null}
// Only allow input up to four bytes (function signature)
if len(input) > 4 {
return nil, errConstInvalidInputLength
}
```
Return an error if warranted. The reason this precompile allows up to four bytes of input is that any standard call (for example, using `cast`) is going to have at least four bytes for the function signature.
`return a, b` is the way we return two values from a function in Go. The normal output is `nil`, nothing, because we return an error.
```go theme={null}
output := make([]byte, 6)
for i := 0; i < 6; i++ {
output[i] = byte(64+i)
}
return output, nil
}
```
Finally, you create the output buffer, fill it, and then return it.
## Conclusion
An OP Stack chain with additional precompiles can be useful, for example, to further reduce the computational effort required for cryptographic operations by moving them from interpreted EVM code to compiled Go code.
# Generating an op-program absolute prestate (archived)
Source: https://docs.optimism.io/chain-operators/tutorials/archive/op-program-prestate
Archived: legacy op-program prestate generation flow for chains resolving in-flight CANNON game type 1 disputes.
**Archived.** As of [Upgrade 19](/notices/archive/upgrade-19), `CANNON_KONA` (game type `8`) is the respected game type for permissionless fault proofs and `op-program` (game type `0`, `CANNON`) is no longer supported for new fault proofs. This page is retained only for chains that still need to resolve in-flight `CANNON` games created before Upgrade 19.
For all current work, see [Generating absolute prestate and preimage files](/chain-operators/tutorials/absolute-prestate) (kona-client) and, if your chain is not yet in the Superchain Registry, [Generating a custom kona-client absolute prestate](/chain-operators/tutorials/kona-custom-prestate).
## Overview
Permissionless fault proofs are a critical component of the OP Stack's security model. They allow anyone to challenge invalid state proposals, ensuring the correctness of L2 to L1 withdrawals without relying on trusted third parties. To enable this functionality, chain operators must generate and maintain the necessary absolute prestate and preimage files. The absolute prestate is a commitment to the initial state of the fault proof program, and the preimage is the serialized binary representation of this program state. These files are essential for the op-challenger tool to participate in dispute games when challenging invalid claims.
## Prerequisites
Before starting, ensure you have:
* [Docker](https://docs.docker.com/engine/install/) running
## Official prestate hashes for superchain-registry chains
The superchain-registry maintains official absolute prestate hashes for chains that are part of the registry. These prestates include the configurations of chains that were in the superchain-registry at the time the prestate was created.
Important: A prestate listed in the superchain-registry may not be suitable for your chain if:
* Your chain was added to the registry after the prestate was created
* The configuration for your chain has been updated since the prestate was created
Before using a prestate from the registry, verify that it contains the latest configuration for your chain.
When in doubt, generating your own prestate with your specific chain configuration is the safest approach.
You can find the latest prestate tags in the [Superchain registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml).
## Generating the absolute prestate
First, clone the Optimism monorepo and check out the appropriate [release tag](https://github.com/ethereum-optimism/optimism/tags) for op-program:
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
# Check out the op-program version that matches the in-flight CANNON game you
# need to resolve. Use the version the game's original prestate was generated with.
git checkout op-program/v1.6.1
```
For chains that are not included in the superchain-registry, you'll need to provide the chain rollup configuration file and the L2 genesis file. Add the `rollup.json` and `l2-genesis.json` to the monorepo at `optimism/op-program/chainconfig/configs/-rollup.json` and `optimism/op-program/chainconfig/configs/-genesis-l2.json` respectively.
Run the following command from the root of the monorepo:
```bash theme={null}
make reproducible-prestate
```
You should see the following logs at the end of the command’s output:
```log theme={null}
-------------------- Production Prestates --------------------
Cannon64 Absolute prestate hash:
0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8
-------------------- Experimental Prestates --------------------
CannonInterop Absolute prestate hash:
0x03fc3b4d091527d53f1ff369ea8ed65e5e17cc7fc98ebf75380238151cdc949c
Cannon64Next Absolute prestate hash:
0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8
```
The output will display production and experimental prestate hashes:
* **Production prestates**: Contains the `Cannon64` prestate, which is the current production absolute prestate hash for the 64-bit version of Cannon. This is the hash you should use for permissionless fault proofs.
* **Experimental prestates**: These contain prestates for versions that are in development and not yet ready for production use.
After generating the prestate, the preimage file will be located in `op-program/bin/prestate-mt64.bin.gz`. The exact name might vary based on the version. Rename this file to include the prestate hash:
```bash theme={null}
cd op-program/bin
mv prestate-mt64.bin.gz .bin.gz
```
Replace `` with the actual `Cannon64` absolute prestate hash value from the output. This file needs to be uploaded to a location that's accessible by your op-challenger instances.
## Deploying and configuring with the absolute prestate
After generating the absolute prestate and preimage files, you'll need to:
Upload the preimage file to a location accessible by your op-challenger instances
Configure the op-challenger to use the generated prestate. There are two ways to provide prestates:
If your prestate files are hosted on a web server, you can simply provide the URL to the directory containing those files:
```bash theme={null}
docker run -d --name op-challenger \
-e OP_CHALLENGER_TRACE_TYPE=permissioned,cannon \
-e OP_CHALLENGER_PRESTATES_URL= \
-e OP_CHALLENGER_L1_ETH_RPC= \
-e OP_CHALLENGER_GAME_FACTORY_ADDRESS= \
-e OP_CHALLENGER_PRIVATE_KEY= \
-e OP_CHALLENGER_NETWORK= \
-e OP_CHALLENGER_CANNON_ROLLUP_CONFIG= \
-e OP_CHALLENGER_CANNON_L2_GENESIS= \
us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest
```
When using an HTTP URL, no volume mount is required. The challenger will download the prestate files as needed.
If you have prestate files stored locally, you'll need to mount them as a volume and use the `file://` protocol:
```bash theme={null}
docker run -d --name op-challenger \
-e OP_CHALLENGER_TRACE_TYPE=permissioned,cannon \
-e OP_CHALLENGER_PRESTATES_URL=file:///prestates \
-e OP_CHALLENGER_L1_ETH_RPC= \
-e OP_CHALLENGER_GAME_FACTORY_ADDRESS= \
-e OP_CHALLENGER_PRIVATE_KEY= \
-e OP_CHALLENGER_NETWORK= \
-e OP_CHALLENGER_CANNON_ROLLUP_CONFIG= \
-e OP_CHALLENGER_CANNON_L2_GENESIS= \
-v /path/to/local/prestates:/prestates \
us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest
```
When using local files, ensure your prestate files are in the mounted directory and properly named with their hash (e.g., `0x03eb07101fbdeaf3f04d9fb76526362c1eea2824e4c6e970bdb19675b72e4fc8.bin.gz`).
* Ensure you're using the latest op-challenger version, see the [release page](https://github.com/ethereum-optimism/optimism/releases).
* If your chain uses interoperability features, you'll need to add a `depsets.json` file to the `op-program/chainconfig/configs` directory.
* This file contains dependency set configurations. Use the same dependency set definition your interop-enabled chain is already configured with.
## Next steps
* Check out the [migrating to permissionless fault proofs guide](/chain-operators/tutorials/migrating-permissionless).
* Read the [Fault proofs explainer](/op-stack/fault-proofs/explainer).
# L2 Rollup Code Examples
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/code-setup
Complete working code examples for the Create L2 Rollup tutorial
This page contains complete working code examples for the Create L2 Rollup tutorial. You can find all the code and configuration files in the [create-l2-rollup-example directory](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/).
For the complete working implementation, visit the [Create L2 Rollup code on GitHub](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/).
## Quick Start
```bash theme={null}
# Copy and configure environment
cp .example.env .env
# Edit .env with your values
# Run the automated setup
make init # Download tools
make setup # Deploy and configure
make up # Start services
```
## Files
* `.example.env` - Environment configuration template
* `docker-compose.yml` - Service orchestration
* `Makefile` - Automation commands
* `scripts/` - Setup and utility scripts
* `README.md` - Detailed documentation
## About This Code
This implementation provides:
* Automated deployment of OP Stack L2 contracts
* Complete Docker-based service orchestration
* Working examples of all OP Stack components
* Production-ready configuration patterns
For detailed setup instructions, see the [Create L2 Rollup tutorial](/chain-operators/tutorials/create-l2-rollup).
# Creating your own L2 rollup testnet
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/index
Learn how to deploy and orchestrate all OP Stack components for a complete testnet deployment.
**Learn the OP Stack — stop 5 of 13.**
You've covered the foundations: what the stack is, how it differs from
Ethereum, and which components make it up. In this first project you
deploy a rollup testnet with op-deployer and start each component
yourself. Work through every part of the series, then continue to
[Transaction flow](/op-stack/transactions/transaction-flow).
Welcome to the complete guide for deploying your own OP Stack L2 rollup testnet. This multi-part tutorial will walk you through each component step-by-step, from initial setup to a fully functioning rollup.
This tutorial requires **intermediate-level experience working with EVM chains**.
You should be comfortable with concepts like smart contracts, private keys, RPC endpoints, gas fees, and command-line operations.
Basic familiarity with Docker is also recommended.
## What you'll build
By the end of this tutorial, you'll have a complete OP Stack testnet with:
* **L1 Smart Contracts** deployed on Sepolia testnet
* **Execution Client** (op-reth) processing transactions
* **Consensus Client** (op-node) managing rollup consensus
* **Batcher** (op-batcher) publishing transaction data to L1
* **Proposer** (op-proposer) submitting state root proposals
* **Challenger** (op-challenger) monitoring for disputes
## Before you start
Set up the following before you pick a setup path below. Both the automated and manual paths need these dependencies and resources.
### Software dependencies
| Dependency | Version | Version check command |
| ------------------------------------------------------------- | ------------------------------------ | --------------------- |
| [git](https://git-scm.com/) | `^2` | `git --version` |
| [go](https://go.dev/) | `^1.26` | `go version` |
| [rust](https://rustup.rs/) (source builds only) | pinned by `rust/rust-toolchain.toml` | `rustc --version` |
| [just](https://github.com/casey/just) (source builds only) | `^1` | `just --version` |
| [zip](https://infozip.sourceforge.net/) (source builds only) | `^3` | `zip --version` |
| [node](https://nodejs.org/en/) | `^20` | `node --version` |
| [pnpm](https://pnpm.io/installation) | `^8` | `pnpm --version` |
| [foundry](https://github.com/foundry-rs/foundry#installation) | `^0.2.0` | `forge --version` |
| [make](https://linux.die.net/man/1/make) | `^3` | `make --version` |
| [jq](https://github.com/jqlang/jq) | `^1.6` | `jq --version` |
| [direnv](https://direnv.net) | `^2` | `direnv --version` |
| [Docker](https://docs.docker.com/get-docker/) | `^24` | `docker --version` |
### Notes on specific dependencies
Expand each dependency below for details
We recommend using the latest LTS version of Node.js (currently v20).\
[`nvm`](https://github.com/nvm-sh/nvm) is a useful tool that can help you manage multiple versions of Node.js on your machine.\
You may experience unexpected errors on older versions of Node.js.
We will use cast to generate wallet addresses in this guide.
Parts of this tutorial use [`direnv`](https://direnv.net) as a way of loading environment variables from `.envrc` files into your shell.\
This means you won't have to manually export environment variables every time you want to use them.\
`direnv` only ever has access to files that you explicitly allow it to see.
After [installing `direnv`](https://direnv.net/docs/installation.html), you will need to **make sure that [`direnv` is hooked into your shell](https://direnv.net/docs/hook.html)**.\
Make sure you've followed [the guide on the `direnv` website](https://direnv.net/docs/hook.html), then **close your terminal and reopen it** so that the changes take effect (or `source` your config file if you know how to do that).
Make sure that you have correctly hooked `direnv` into your shell by modifying your shell configuration file (like `~/.bashrc` or `~/.zshrc`).\
If you haven't edited a config file then you probably haven't configured `direnv` properly (and things might not work later).
Docker is used extensively in this tutorial for running various OP Stack components.\
Make sure you have both Docker and Docker Compose installed and running on your system.\
On Linux, you may need to [configure Docker to run without sudo](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user).
If you're using Docker Desktop, ensure it's running before starting the tutorial.\
You can verify your installation with:
```bash theme={null}
docker run hello-world
```
### Get access to a sepolia node
Since you're deploying your OP Stack chain to Sepolia, you'll need to have access to a Sepolia node.
You can either use a node provider like [Alchemy](https://www.alchemy.com/) (easier) or run your own Sepolia node (harder).
### Required resources
* **Sepolia ETH** - You'll need about 2-3 ETH:
* Start with [Superchain Faucet](https://console.optimism.io/faucet) (gives 0.05 ETH)
* Get more from:
* [Alchemy Faucet](https://sepoliafaucet.com/)
* [Infura Faucet](https://www.infura.io/faucet/sepolia)
* [Paradigm Faucet](https://faucet.paradigm.xyz/)
* **L1 RPC URL** - An RPC endpoint to connect to the Sepolia network. You can get this from node providers like [Alchemy](https://www.alchemy.com/), [Infura](https://www.infura.io/). This is required so `op-deployer` and other services can read from and send transactions to L1.
**Testnet Only**: This guide is for **testnet deployment only**.
## Choose your path
With your dependencies and resources in place, pick the path that fits how you want to work:
* **[Automated setup](#automated-setup)** — the fastest way to a running rollup. Uses the complete working implementation in this repository and handles all configuration and deployment for you.
* **[Manual setup](#manual-setup)** — walks through each component step-by-step. Choose this if you want to understand each component in detail or need custom configurations.
## Automated setup
If you want to get started quickly, you can use the complete working implementation provided in this repository. This automated setup handles all the configuration and deployment steps for you.
**Complete working example**
A complete, working implementation is available in the [`create-l2-rollup-example/`](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/) directory. This includes all necessary scripts, Docker Compose configuration, and example environment files.
### Automated setup steps
1. **Clone and navigate to the code directory:**
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism/docs/public-docs/create-l2-rollup-example
```
2. **Configure your environment:**
```bash theme={null}
cp .example.env .env
# Edit .env with your L1_RPC_URL, PRIVATE_KEY, and other settings
```
3. **Run the automated setup:**
```bash theme={null}
make init # Download op-deployer
make setup # Deploy contracts and generate configs
make up # Start all services
make test-l1 # Verify L1 connectivity
make test-l2 # Verify L2 functionality
```
4. **Monitor your rollup:**
```bash theme={null}
make logs # View all service logs
make status # Check service health
```
The automated setup uses the standard OP Stack environment variable conventions (prefixed with `OP_*`) and handles all the complex configuration automatically.
## Manual setup
If you prefer to understand each component in detail or need custom configurations, follow the step-by-step guide below. Each step builds on the previous one, so complete them in order for the best experience.
### Directory structure
To keep your rollup deployment organized, we'll create a dedicated directory structure. All components will be set up within this structure:
```bash theme={null}
rollup/
├── deployer/ # op-deployer files and contracts
├── sequencer/ # op-reth and op-node
├── batcher/ # op-batcher configuration
├── proposer/ # op-proposer setup
└── challenger/ # op-challenger files
```
Each component's documentation will show you how the directory structure evolves as you add files and configurations.
Throughout this tutorial, all file paths will be relative to this `rollup` directory structure. Make sure to adjust any commands if you use different directory names.
### Manual setup steps
The manual path is organized into sequential steps that build upon each other:
Install op-deployer, deploy L1 contracts, and prepare your environment
[Go to op-deployer setup →](/chain-operators/tutorials/create-l2-rollup/op-deployer-setup)
Set up and run op-reth and op-node (the execution and consensus layers)
[Go to sequencer setup →](/chain-operators/tutorials/create-l2-rollup/op-reth-setup)
Configure and start op-batcher for L1 data publishing
[Go to batcher setup →](/chain-operators/tutorials/create-l2-rollup/op-batcher-setup)
Set up op-proposer for state root submissions
[Go to proposer setup →](/chain-operators/tutorials/create-l2-rollup/op-proposer-setup)
Configure op-challenger for dispute resolution monitoring
[Go to challenger setup →](/chain-operators/tutorials/create-l2-rollup/op-challenger-setup)
Already have your dependencies? Get started and spin up op-deployer
***
## Need help?
* **Questions or issues**: Ask questions or report bugs and docs problems on the [Optimism monorepo issue tracker](https://github.com/ethereum-optimism/optimism/issues)
* **Code examples**: Browse the [complete working example](/chain-operators/tutorials/create-l2-rollup/code-setup) that accompanies this tutorial
# Spin up batcher
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-batcher-setup
Learn how to set up and configure an OP Stack batcher to submit L2 transaction batches to L1.
After you have spun up your sequencer, you need to configure a batcher to submit L2 transaction batches to L1.
**Step 3 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the [previous one](/chain-operators/tutorials/create-l2-rollup/op-reth-setup).
**Automated Setup Available**
For a complete working setup with all components, check out the [automated approach](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/) in the code directory.
## Understanding the batcher's role
The batcher (`op-batcher`) serves as a crucial component that bridges your L2 chain data to L1. Its primary responsibilities include:
* **Batch submission**: Collecting L2 transactions and submitting them as batches to L1
* **Data availability**: Ensuring L2 transaction data is available on L1 for verification
* **Cost optimization**: Compressing and efficiently packing transaction data to minimize L1 costs
* **Channel management**: Managing data channels for optimal batch submission timing
The batcher reads transaction data from your sequencer and submits compressed batches to the `BatchInbox` contract on L1.
## Prerequisites
Before setting up your batcher, ensure you have:
**Running infrastructure:**
* An operational sequencer node
* Access to a L1 RPC endpoint
**Network information:**
* Your L2 chain ID and network configuration
* L1 network details (chain ID, RPC endpoints)
* `BatchInbox` contract address from your deployment
For setting up the batcher, we recommend using Docker as it provides a consistent and isolated environment. Building from source is also available for more advanced users.
If you prefer containerized deployment, you can use the official Docker images and do the following:
```bash theme={null}
# Create your batcher directory inside rollup
cd ../ # Go back to rollup directory if you're in sequencer
mkdir batcher
cd batcher
# Copy configuration files from deployer
cp ../deployer/.deployer/state.json .
# Extract the BatchInbox address
BATCH_INBOX_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].systemConfigProxyAddress')
echo "BatchInbox Address: $BATCH_INBOX_ADDRESS"
```
**OP Stack Standard Variables**
The batcher uses OP Stack standard environment variables following the OP Stack conventions. These are prefixed with `OP_BATCHER_` for batcher-specific settings.
```bash theme={null}
# Create .env file with your actual values
cat > .env << 'EOF'
# L1 Configuration - Replace with your actual RPC URLs
OP_BATCHER_L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
# Private key - Replace with your actual private key
OP_BATCHER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
# L2 Configuration - Should match your sequencer setup
OP_BATCHER_L2_ETH_RPC=http://op-reth:8545
OP_BATCHER_ROLLUP_RPC=http://op-node:8547
# Contract addresses - Extract from your op-deployer output
OP_BATCHER_BATCH_INBOX_ADDR=YOUR_ACTUAL_BATCH_INBOX_ADDRESS
# Batcher configuration
OP_BATCHER_POLL_INTERVAL=1s
OP_BATCHER_SUB_SAFETY_MARGIN=6
OP_BATCHER_NUM_CONFIRMATIONS=1
OP_BATCHER_SAFE_ABORT_NONCE_TOO_LOW_COUNT=3
OP_BATCHER_MAX_CHANNEL_DURATION=1
OP_BATCHER_DATA_AVAILABILITY_TYPE=calldata
# RPC configuration
OP_BATCHER_RPC_PORT=8548
EOF
```
**Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values.
This configuration assumes your sequencer is running in a Docker container named `sequencer-node` on the same `op-stack` network.
Make sure your sequencer is running before starting the batcher.
```yaml theme={null}
services:
op-batcher:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.16.11
volumes:
- .:/workspace
working_dir: /workspace
ports:
- "8548:8548"
env_file:
- .env
networks:
- sequencer-node_default
command: >
op-batcher
--rpc.addr=0.0.0.0
--rpc.enable-admin
--resubmission-timeout=30s
--log.level=info
--log.format=json
restart: unless-stopped
networks:
sequencer-node_default:
external: false
```
```bash theme={null}
# Make sure your sequencer network exists
# Start the batcher
docker-compose up -d
# View logs
docker-compose logs -f op-batcher
```
```bash theme={null}
# Check container status
docker-compose ps
```
```bash theme={null}
rollup/
├── deployer/ # From previous step
│ └── .deployer/ # Contains genesis.json and rollup.json
├── sequencer/ # From previous step
└── batcher/ # You are here
├── state.json # Copied from deployer
├── .env # Environment variables
└── docker-compose.yml # Docker configuration
```
Your batcher is now operational and will continuously submit L2 transaction batches to L1!
To ensure you're using the latest compatible versions of OP Stack components, always check the official [releases page](https://github.com/ethereum-optimism/optimism/releases).
Look for the latest `op-batcher/v*` release that's compatible with your sequencer setup.
This guide uses `op-batcher/v1.16.11`, the latest release at the time of writing, alongside op-node/v1.19.3 and op-reth/v2.4.0 from the sequencer setup.
Always check the [release notes](https://github.com/ethereum-optimism/optimism/releases) for compatibility information.
```bash theme={null}
# If you don't already have the optimism repository from the sequencer setup
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
# Checkout the latest release tag
git checkout op-batcher/v1.16.11
# Generate the embedded superchain config bundle (initializes the
# superchain-registry submodule; op-batcher embeds this file at compile time)
just build-superchain-go
# Build op-batcher
cd op-batcher
just
# Binary will be available at ./bin/op-batcher
```
Run this command to verify the installation:
```bash theme={null}
./bin/op-batcher --version
```
For advanced configuration options and fine-tuning your batcher, including:
* Batch submission policies
* Channel duration settings
* Data availability types (blobs vs calldata)
* Transaction throttling
* Network timeouts
Check out the [Batcher configuration reference](/chain-operators/reference/batcher-configuration).
This will help you optimize your batcher's performance and cost-efficiency.
Create your batcher working directory:
```bash theme={null}
# Create batcher directory inside rollup
cd ../ # Go back to rollup directory
mkdir batcher
cd batcher
# Create scripts directory
mkdir scripts
```
Your final directory structure should look like:
```bash theme={null}
rollup/
├── deployer/ # From previous step
│ └── .deployer/ # Contains state.json
├── optimism/ # Contains op-batcher binary
├── sequencer/ # From previous step
└── batcher/ # You are here
├── state.json # Copied from deployer
├── .env # Environment variables
└── scripts/ # Startup scripts
└── start-batcher.sh
```
Extract the `BatchInbox` contract address from your op-deployer output:
```bash theme={null}
# Make sure you're in the rollup/batcher directory
cd rollup/batcher
# Copy the deployment state file from deployer
cp ../deployer/.deployer/state.json .
# Extract the BatchInbox address
BATCH_INBOX_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].systemConfigProxyAddress')
echo "BatchInbox Address: $BATCH_INBOX_ADDRESS"
```
The batcher submits transaction batches to the `BatchInbox` contract on L1. This contract is responsible for accepting and storing L2 transaction data.
Create your `.env` file with the actual values:
```bash theme={null}
# Create .env file with your actual values
# L1 Configuration - Replace with your actual RPC URL
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
# L2 Configuration - Should match your sequencer setup
L2_RPC_URL=http://localhost:8545
ROLLUP_RPC_URL=http://localhost:8547
# Contract addresses - Extract from your op-deployer output
BATCH_INBOX_ADDRESS=YOUR_ACTUAL_BATCH_INBOX_ADDRESS
# Private key - Replace with your actual private key
BATCHER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
# Batcher configuration
POLL_INTERVAL=1s
SUB_SAFETY_MARGIN=6
NUM_CONFIRMATIONS=1
SAFE_ABORT_NONCE_TOO_LOW_COUNT=3
RESUBMISSION_TIMEOUT=30s
MAX_CHANNEL_DURATION=25
# RPC configuration
BATCHER_RPC_PORT=8548
```
**Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values!
Get a private key from your wallet that will be used for submitting batches to L1. This account needs sufficient ETH to pay for L1 gas costs.
The batcher account needs to be funded with ETH on L1 to pay for batch submission transactions. Monitor this account's balance regularly as it will consume ETH for each batch submission.
### Batcher configuration
Create `scripts/start-batcher.sh` in the same directory:
```bash theme={null}
#!/bin/bash
source .env
# Path to the op-batcher binary we built
../../optimism/op-batcher/bin/op-batcher \
--l2-eth-rpc=$L2_RPC_URL \
--rollup-rpc=$ROLLUP_RPC_URL \
--poll-interval=$POLL_INTERVAL \
--sub-safety-margin=$SUB_SAFETY_MARGIN \
--num-confirmations=$NUM_CONFIRMATIONS \
--safe-abort-nonce-too-low-count=$SAFE_ABORT_NONCE_TOO_LOW_COUNT \
--resubmission-timeout=$RESUBMISSION_TIMEOUT \
--rpc.addr=0.0.0.0 \
--rpc.port=$BATCHER_RPC_PORT \
--rpc.enable-admin \
--max-channel-duration=$MAX_CHANNEL_DURATION \
--l1-eth-rpc=$L1_RPC_URL \
--private-key=$BATCHER_PRIVATE_KEY \
--batch-type=1 \
--data-availability-type=blobs \
--log.level=info
```
### Batcher parameters explained
* **`--poll-interval`**: How frequently the batcher checks for new L2 blocks to batch
* **`--sub-safety-margin`**: Number of confirmations to wait before considering L1 transactions safe
* **`--max-channel-duration`**: Maximum time (in L1 blocks) to keep a channel open
* **`--batch-type`**: Type of batch encoding (1 for span batches, 0 for singular batches)
* **`--data-availability-type`**: Whether to use blobs or calldata for data availability
### Starting the batcher
### Start the batcher
```bash theme={null}
# Make the script executable
chmod +x scripts/start-batcher.sh
# Start the batcher
./scripts/start-batcher.sh
```
For detailed cost analysis and optimization strategies, refer to the [Transaction fees documentation](/op-stack/transactions/fees).
Your batcher is now operational and will continuously submit L2 transaction batches to L1!
**Understanding common startup messages**
When starting your batcher, you might see various log messages:
* `Added L2 block to local state`: Normal operation, shows the batcher processing blocks
* `SetMaxDASize RPC method unavailable`: Expected if the execution client version used doesn't support this method.
* `context canceled` errors during shutdown: Normal cleanup messages
* `Failed to query L1 tip`: Can occur during graceful shutdowns
Most of these messages are part of normal operation. For detailed explanations of configuration options and troubleshooting, see the [Batcher configuration reference](/chain-operators/reference/batcher-configuration).
## What's Next?
Excellent! Your batcher is publishing transaction data to L1. The next step is to set up the proposer to submit state root proposals.
**Next**: Configure and start op-proposer to submit L2 state roots to L1 for withdrawal verification.
***
## Need Help?
* **Batcher Configuration**: [Batcher configuration reference](/chain-operators/reference/batcher-configuration)
* **Monitoring Guide**: [Chain Monitoring](/chain-operators/tools/chain-monitoring)
* **Support**: Open an issue in the [Optimism monorepo](https://github.com/ethereum-optimism/optimism/issues)
# Spin up challenger
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-challenger-setup
Learn how to configure challenger for your OP Stack chain.
After you have spun up your sequencer, batcher, and proposer, the final step is to configure a challenger to monitor and respond to disputes.
**Step 5 of 5**: This tutorial is designed to be followed step-by-step.
Each step builds on the previous one, and this is the last part of the tutorial.
**Automated Setup Available**
For a complete working setup with all components including automated prestate generation, check out the [automated approach](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/) in the code directory.
The challenger is a critical fault proofs component that monitors dispute games and challenges invalid claims to protect your OP Stack chain. See the [op-challenger explainer](/op-stack/fault-proofs/challenger) for a general overview of this fault proofs feature.
The challenger is responsible for:
* Monitoring dispute games created by the fault proof system
* Challenging invalid claims in dispute games
* Defending valid state transitions
* Resolving games when possible
## Prerequisites
### Essential requirements
Complete these prerequisites before wiring up the challenger:
The challenger needs the absolute prestate to participate in dispute games. The prestate is the on-chain commitment to a specific build of `kona-client`, the maintained fault proof program. (`op-program`, which previously filled this role, has reached end-of-support; see [End of Support for op-geth and op-program](/notices/archive/op-geth-deprecation).) Here's how to generate it:
1. **Clone and checkout the correct version**:
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
git checkout kona-node/v1.6.1 # Use the latest release
```
2. **Stage your chain configuration**:
Because your chain is not in the public Superchain Registry, the prestate build must embed your chain's configuration. Create the two staging files (`chainList.json` and `configs.json`) from your `rollup.json`, L2 genesis, and `op-deployer` state by following [Generating a custom kona-client absolute prestate](/chain-operators/tutorials/kona-custom-prestate), then point the build at them:
```bash theme={null}
# Assuming you're in rollup/challenger/optimism directory
export KONA_CUSTOM_CONFIGS_DIR="$PWD/rust/kona/crates/protocol/registry/etc/custom-configs/"
```
3. **Generate the prestate**:
```bash theme={null}
just reproducible-prestate-kona
jq -r .pre rust/kona/prestate-artifacts-cannon/prestate-proof.json
```
The build runs in Docker and writes artifacts to `rust/kona/prestate-artifacts-cannon/`, including a preimage file named by its hash (`0x.bin.gz`). The hash printed by the `jq` command is your chain's absolute prestate.
4. **Verify your chain is embedded in the prestate**:
```bash theme={null}
gunzip -c rust/kona/prestate-artifacts-cannon/prestate.bin.gz | strings | grep ""
```
Zero matches means the custom configuration was not merged (the build silently produces the standard prestate instead); see the [custom prestate tutorial](/chain-operators/tutorials/kona-custom-prestate) for troubleshooting.
* Keep the `0x.bin.gz` file accessible - you'll need it for the challenger setup
* For Superchain registry chains, you can find official `cannon64-kona` prestates in the [registry](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml)
Your sequencer stack from the previous steps already provides the L2 endpoints the challenger needs (op-reth and op-node). In addition, the challenger needs:
* An L1 RPC endpoint for your settlement layer (Sepolia in this tutorial)
* An L1 beacon API endpoint, used to fetch blobs
* `0x.bin.gz` - The absolute prestate preimage file generated in step 1
* `rollup.json` - Rollup configuration file from the `op-deployer` guide
## Software installation
For challenger deployment, we recommend using Docker as it provides a consistent and isolated environment. Building from source is also available for more advanced users.
### Docker Setup
The Docker setup provides a containerized environment for running the challenger. This method uses the official Docker image that includes the embedded `kona` server and Cannon executable.
```bash theme={null}
# Create your challenger directory inside rollup
cd ../ # Go back to rollup directory if you're in proposer
mkdir challenger
cd challenger
```
**OP Stack Standard Variables**
The challenger uses OP Stack standard environment variables following the OP Stack conventions. These are prefixed with `OP_CHALLENGER_` for challenger-specific settings.
```bash theme={null}
# Create .env file with your actual values
cat > .env << 'EOF'
# Core configuration (required)
OP_CHALLENGER_L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
OP_CHALLENGER_L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com
OP_CHALLENGER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
# L2 Configuration - Replace with your actual node endpoints
OP_CHALLENGER_L2_ETH_RPC=http://op-reth:8545
OP_CHALLENGER_ROLLUP_RPC=http://op-node:8547
# OP Stack challenger configuration (optional - defaults provided)
OP_CHALLENGER_GAME_FACTORY_ADDRESS=YOUR_GAME_FACTORY_ADDRESS
OP_CHALLENGER_CANNON_KONA_L2_GENESIS=/workspace/genesis.json
OP_CHALLENGER_CANNON_KONA_ROLLUP_CONFIG=/workspace/rollup.json
# Prestate configuration - Replace with the file from 'just reproducible-prestate-kona'
OP_CHALLENGER_CANNON_KONA_PRESTATE=/workspace/${PRESTATE_HASH}.bin.gz
EOF
```
**Important:** Replace every `YOUR_ACTUAL_*` placeholder with the real values from your deployment.
Define the challenger service in a `docker-compose.yml`. It mounts several important files:
* `prestate-proof.json` and `${PRESTATE_HASH}.bin.gz`: Prestate files required for dispute games (the PRESTATE\_HASH comes from running `just reproducible-prestate-kona`), replace `PRESTATE_HASH` with the actual hash
```yaml theme={null}
services:
challenger:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.9.4
user: "1000"
volumes:
- ./challenger-data:/data
- ./rollup.json:/workspace/rollup.json:ro
- ./genesis-l2.json:/workspace/genesis-l2.json:ro
- ./prestate-proof.json:/workspace/prestate-proof.json:ro
- ./${PRESTATE_HASH}.bin.gz:/workspace/${PRESTATE_HASH}.bin.gz:ro
command: >
op-challenger run-trace
--trace-type=cannon-kona
--datadir=/data
--log.level=info
--log.format=json
restart: unless-stopped
networks:
- sequencer-node_default
networks:
sequencer-node_default:
external: false
```
Start the challenger service and follow its logs:
```bash theme={null}
# Start the challenger service
docker-compose up -d
# View logs
docker-compose logs -f challenger
```
The generic build-from-source walkthrough lives in the "Build from source" tab of the
[challenger configuration guide](/chain-operators/guides/configuration/op-challenger-config-guide#software-installation):
it covers picking the release, building `op-challenger`, Cannon, and `kona-host`,
verifying the binaries, the environment file, and the startup script. Follow it end
to end, then adapt it to this tutorial series as follows:
* **Workspace**: create the working directory as `rollup/challenger`, next to the
`deployer`, `sequencer`, `batcher`, and `proposer` directories from the previous
steps. With the monorepo checkout at `rollup/optimism`, the binaries are then
reachable from the startup script as `../../optimism/op-challenger/bin/op-challenger`
and `CANNON_BIN=../../optimism/cannon/bin/cannon`.
* **kona-host**: build it in the checkout you generated the prestate from in the
prerequisites (`kona-node/v1.6.1`), so the server matches your chain's absolute
prestate, and point `CANNON_KONA_SERVER` at the resulting binary.
* **Configuration files**: copy `rollup.json` and `genesis-l2.json` from the
op-deployer step into `rollup/challenger`, set `CANNON_ROLLUP_CONFIG=./rollup.json`
and `CANNON_L2_GENESIS=./genesis-l2.json` (the guide's startup script passes these
to `--cannon-kona-rollup-config` and `--cannon-kona-l2-genesis`), and set
`CANNON_KONA_PRESTATE=./0x.bin.gz`, the preimage file generated
in the prerequisites.
* **Trace type and wallet**: this chain is not in the superchain-registry, so keep
`GAME_FACTORY_ADDRESS` explicit, use `--trace-type cannon-kona`, and sign with the
funded private key you used for the other components (`--private-key` instead of
the guide's mnemonic example).
### Monitoring with op-dispute-mon
Consider running [`op-dispute-mon`](/chain-operators/tools/chain-monitoring#dispute-mon) for enhanced security monitoring:
* Provides visibility into all game statuses for the last 28 days
* Essential for production challenger deployments
## Congratulations
You've successfully completed the entire L2 rollup testnet tutorial! Your rollup is now fully operational with all components running:
* **op-deployer** - L1 contracts deployed
* **Sequencer** - Processing transactions
* **Batcher** - Publishing data to L1
* **Proposer** - Submitting state roots
* **Challenger** - Monitoring disputes
## Connect your wallet to your chain
You now have a fully functioning OP Stack Rollup with a Sequencer node running on `http://localhost:8545`. You can connect your wallet to this chain the same way you'd connect your wallet to any other EVM chain.
## Get ETH on your chain
Once you've connected your wallet, you'll probably notice that you don't have any ETH to pay for gas on your chain.
The easiest way to deposit Sepolia ETH into your chain is to send ETH directly to the `L1StandardBridge` contract.
### Get the L1StandardBridge address
The `L1StandardBridge` proxy address can be found in your deployment state file. To get it, run:
```bash theme={null}
# From your project root
jq -r .l1StandardBridgeProxyAddress /.deployer/state.json
```
This will output the `L1StandardBridge` proxy address that you should use for deposits. Make sure to use the proxy address, not the implementation address.
### Deposit ETH to your L2
Once you have the `L1StandardBridge` address, send a small amount of Sepolia ETH (0.1 or less) to that address from the wallet you want to use on L2.
This will trigger a deposit that will mint ETH into your wallet on L2.
It may take up to 5 minutes for the ETH to appear in your wallet on L2.
This delay is due to the time needed for the deposit transaction to be processed and finalized.
## See your rollup in action
You can interact with your Rollup the same way you'd interact with any other EVM chain.
Send some transactions, deploy some contracts, and see what happens!
You now have a working testnet. Here is what the distance to production looks like:
**Running this in production**
## Need Help?
* **OP Challenger Explainer**: [Fault Proofs Overview](/op-stack/fault-proofs/challenger)
* **Technical Specs**: [Honest Challenger Specification](https://specs.optimism.io/fault-proof/stage-one/honest-challenger-fdg.html)
# Deploy L1 contracts with op-deployer
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-deployer-setup
Install op-deployer, prepare your environment, and deploy the L1 smart contracts for your rollup.
Welcome to the first step of creating your own L2 rollup testnet! In this section, you'll install the op-deployer tool and deploy the necessary L1 smart contracts for your rollup.
**Step 1 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the previous one.
**Quick Setup Available**
For a complete automated setup that includes op-deployer deployment, check out the [`code/`](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/) directory. The automated setup handles all contract deployment and configuration automatically.
## About op-deployer
`op-deployer` simplifies the process of deploying the OP Stack. You define a declarative config file called an "**intent**," then run a command to apply it. `op-deployer` compares your chain's current state against the intent and makes the necessary changes to match.
## Installation
There are a couple of ways to install `op-deployer`:
The recommended way to install `op-deployer` is to download the latest release from the monorepo's [release page](https://github.com/ethereum-optimism/optimism/releases).
**Quick Setup Available**
For automated installation, you can use the download script from the [code directory](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/). This script automatically downloads the latest version for your system.
1. Go to the [release page](https://github.com/ethereum-optimism/optimism/releases)
2. Find the **latest** release that includes `op-deployer` (look for releases tagged with `op-deployer/v*`)
3. Under **assets**, download the binary that matches your system:
* For Linux: `op-deployer-linux-amd64`
* For macOS:
* Apple Silicon (M1/M2): `op-deployer-darwin-arm64`
* Intel processors: `op-deployer-darwin-amd64`
* For Windows: `op-deployer-windows-amd64.exe`
**Always download the latest version** to ensure you have the most recent features and bug fixes.
Not sure which macOS version to use?
* Open Terminal and run `uname -m`
* If it shows `arm64`, use the arm64 version
* If it shows `x86_64`, use the amd64 version
1. Create the rollup directory structure and enter the deployer directory:
```bash theme={null}
# Create main rollup directory
mkdir rollup && cd rollup
# Create and enter the deployer directory
mkdir deployer && cd deployer
```
Your directory structure will now look like this:
```bash theme={null}
rollup/
└── deployer/ # You are here
```
2. Move and rename the downloaded binary:
The downloaded file is likely in your Downloads folder:
* macOS/Linux: `/Users/YOUR_USERNAME/Downloads`
* Windows WSL: `/mnt/c/Users/YOUR_USERNAME/Downloads`
```bash theme={null}
# Step 1: Extract the downloaded archive in the deployer directory
# Replace FILENAME with the actual downloaded file name (includes version and arch)
tar -xvzf /Users/USERNAME/Downloads/FILENAME.tar.gz
# Step 2: Make the binary executable
# Replace FILENAME with the extracted binary name
chmod +x FILENAME
# Step 3: Remove macOS quarantine attribute (fixes "can't be opened" warning)
sudo xattr -dr com.apple.quarantine FILENAME
# Step 4: Move the binary to your PATH
# For Intel Macs:
sudo mv FILENAME /usr/local/bin/op-deployer
# For Apple Silicon Macs:
sudo mv FILENAME /opt/homebrew/bin/op-deployer
# Step 5: Verify installation (should print version info)
op-deployer --version
```
**Pro Tip**: Use the automated download script from the [code directory](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/) to avoid manual version management. It automatically detects your platform and downloads the latest version.
To install from source, you will need [Go](https://go.dev/doc/install), `just`, and `git`.
After installing all of that, run following:
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism.git # you can skip this if you already have the repo
cd optimism/op-deployer
just build
cp ./bin/op-deployer /usr/local/bin/op-deployer # or any other directory in your $PATH
# Verify installation
op-deployer --version
```
## L1 network requirements
Before deploying your L1 contracts, you'll need:
**L1 RPC URL**: An Ethereum RPC endpoint for your chosen L1 network
```bash theme={null}
# Examples:
# Sepolia (recommended for testing)
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR-PROJECT-ID
# or https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY
# Local network
L1_RPC_URL=http://localhost:8545
```
For testing, we recommend using Sepolia testnet. You can get free RPC access from:
* [Infura](https://infura.io) (create account, get API key)
* [Alchemy](https://alchemy.com) (create account, get API key)
* [Ankr](https://ankr.com) (create account, get API key)
## Generate deployment addresses
Your rollup needs several addresses for different roles. Let's generate them first:
```bash theme={null}
# Create a address directory inside the deployer directory
mkdir -p address
cd address
```
Your directory structure will now look like this:
```bash theme={null}
rollup/
└── deployer/
└── address/ # You are here
```
```bash theme={null}
# Generate 8 new wallet addresses
for role in admin base_Fee_Vault_Recipient l1_Fee_Vault_Recipient sequencer_Fee_Vault_Recipient system_config unsafe_block_signer batcher proposer ; do
wallet_output=$(cast wallet new)
echo "$wallet_output" | grep "Address:" | awk '{print $2}' > ${role}_address.txt
echo "Created wallet for $role"
done
```
This will save the various addresses for your intent file into files in your current directory. To view them later you can use `cat *_address.txt`.
**Important**:
* Save these address - you'll need them to operate your chain
* You can use any address for the purpose of testing, for production, use proper key management solutions (HSMs, multisigs addresses)
## Create and configure intent file
The intent file defines your chain's configuration.
Inside the `deployer` folder, run this command:
```bash theme={null}
#You can use a 2-7 digit random number for your ``
op-deployer init \
--l1-chain-id 11155111 \
--l2-chain-ids \
--workdir .deployer \
--intent-type standard-overrides
```
`op-deployer` supports three intent types:
* `standard`: Uses default OP Stack configuration, minimal customization
* `standard-overrides`: Recommended. Uses defaults but allows overriding specific values
* `custom`: Full customization, requires manual configuration of all values
For most users, `standard-overrides` provides the best balance of simplicity and flexibility.
Edit `.deployer/intent.toml` with your generated addresses. The `op-deployer init` command automatically populates this file with sensible defaults. Update the addresses while keeping the auto-generated contract locator values:
```toml theme={null}
configType = "standard-overrides"
l1ChainID = 11155111 # Sepolia
fundDevAccounts = false # Set to false for production/testnet
useInterop = false
opcmAddress = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78" # OPCM contract address on Sepolia
# Contract locators - REQUIRED fields, automatically populated by op-deployer init
# Keep these default values unless you need specific contract versions (advanced use case)
l1ContractsLocator = "tag://op-contracts/v2.0.0"
l2ContractsLocator = "tag://op-contracts/v1.7.0-beta.1+l2-contracts"
# Shared contract roles - only define if creating a standalone chain not part of the OP Stack ecosystem
# For standard OP Stack deployments, these are predefined and should not be set
# [superchainRoles]
# proxyAdminOwner = "0x..." # admin address
# guardian = "0x..." # admin address
[[chains]]
id = "0x000000000000000000000000000000000000000000000000000000000016de8d"
baseFeeVaultRecipient = "0x..." # receives base fees
l1FeeVaultRecipient = "0x..." # receives L1 data fees
sequencerFeeVaultRecipient = "0x..." # receives priority fees (tips)
operatorFeeVaultRecipient = "0x..." # receives operator fees
eip1559DenominatorCanyon = 250
eip1559Denominator = 50
eip1559Elasticity = 6
[chains.roles]
l1ProxyAdminOwner = "0x1eb2ffc903729a0f03966b917003800b145f56e2"
l2ProxyAdminOwner = "0x2fc3ffc903729a0f03966b917003800b145f67f3"
systemConfigOwner = "0x..." # system_config address
unsafeBlockSigner = "0x..." # unsafe_block_signer address
batcher = "0x..." # batcher address
proposer = "0x..." # proposer address
challenger = "0xfd1d2e729ae8eee2e146c033bf4400fe75284301"
```
**Global Settings:**
* `l1ChainID`: The L1 network ID (11155111 for Sepolia)
* `fundDevAccounts`: Creates test accounts with ETH if true (set to false for production)
* `useInterop`: Enable interoperability features (false for standard deployments)
* `opcmAddress`: OP Contracts Manager (OPCM) contract address on the L1 network (automatically populated for supported networks)
**Contract Locators (Required):**
These fields are **required** and automatically populated by `op-deployer init` with default values compatible with your `op-deployer` version.
Removing them will cause the error: "Application failed: L1ContractsLocator undefined".
Keep the auto-generated values unless you specifically need different contract versions.
For version compatibility details, see the [op-deployer release notes](https://github.com/ethereum-optimism/optimism/releases).
**Shared Contract Roles (Advanced):**
These are commented out because for standard OP Stack deployments, shared contract roles are predefined by the protocol. Only uncomment and define custom roles if you're creating a standalone chain not part of the OP Stack ecosystem.
**Chain Configuration:**
* `id`: Unique identifier for your chain
* `*FeeVaultRecipient`: Addresses receiving protocol fees (required — deployment fails if any are set to the zero address). See [fee vaults](/op-stack/transactions/fee-vaults) for details on each vault.
* `eip1559*`: Parameters for dynamic gas price calculation
**Fee Vault Optional Overrides:**
Each vault also supports optional parameters that can be set via deploy overrides. If not specified, the following defaults apply:
| Parameter | Default |
| -------------------------- | ------------ |
| `*MinimumWithdrawalAmount` | 10 ETH |
| `*WithdrawalNetwork` | `local` (L2) |
**Chain Roles:**
* `l1ProxyAdminOwner`: Can upgrade L1 contract implementations (usually same as superchain proxyAdminOwner)
* `l2ProxyAdminOwner`: Can upgrade L2 contract implementations
* `systemConfigOwner`: Manages system configuration parameters
* `unsafeBlockSigner`: Signs pre-confirmation blocks (can be same as batcher)
* `batcher`: Submits L2 transaction batches to L1
* `proposer`: Submits L2 state roots to L1 for verification
* `challenger`: Monitors dispute games and defends valid states
Replace all `0x...` with actual addresses from your `addresses.txt` file.
Never use the default test mnemonic addresses in production or public testnets!
## Create environment file
Before deploying, create a `.env` file in your `deployer` directory to store your environment variables:
```bash theme={null}
# Create .env file
cat << 'EOF' > .env
# Your L1 RPC URL (e.g., from Alchemy, Infura)
L1_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
# Private key for deployment.
# Get this from your self-custody wallet, like Metamask.
PRIVATE_KEY=WALLET_PRIVATE_KEY
EOF
```
Never commit your `.env` file to version control. Add it to your `.gitignore`:
```bash theme={null}
echo ".env" >> .gitignore
```
Load the environment variables:
```bash theme={null}
source .env
```
## Deploy L1 Contracts
Now that your intent file and environment variables are configured, let's deploy the L1 contracts:
```bash theme={null}
op-deployer apply \
--workdir .deployer \
--l1-rpc-url $L1_RPC_URL \
--private-key $PRIVATE_KEY
```
This will:
1. Deploy all required L1 contracts
2. Configure them according to your intent file
3. Save deployment information to `.deployer/state.json`
The deployment can take 10-15 seconds and requires multiple transactions.
## Generate chain configuration
After successful deployment, generate your chain configuration files:
```bash theme={null}
# Generate genesis and rollup configs
op-deployer inspect genesis --workdir .deployer > .deployer/genesis.json
op-deployer inspect rollup --workdir .deployer > .deployer/rollup.json
```
## What's Next?
Great! You've successfully:
1. Installed `op-deployer` using the `init` and `apply` command.
2. Created and configured your intent file
3. Deployed L1 smart contracts
4. Generated chain artifacts
Your final directory structure should look like this:
```bash theme={null}
rollup/
└── deployer/
├── .deployer/ # Contains deployment state and configs
│ ├── genesis.json # L2 genesis configuration
│ ├── intent.toml # Your chain configuration
│ ├── rollup.json # Rollup configuration
│ └── state.json # Deployment state
├── .env # Environment variables
└── address/ # Generated address pairs
├── admin_address.txt
├── base_Fee_Vault_Recipient_address.txt
├── batcher_address.txt
├── l1_Fee_Vault_Recipient_address.txt
├── proposer_address.txt
├── sequencer_Fee_Vault_Recipient_address.txt
├── system_config_address.txt
└── unsafe_block_signer_address.txt
```
Now you can move on to setting up your sequencer node.
**Next**: Set up op-reth and op-node, essential building blocks of the execution and consensus layers in your rollup.
***
## Need Help?
* **op-deployer Documentation**: [op-deployer overview](/chain-operators/tools/op-deployer/overview)
* **op-deployer Repository**: [GitHub](https://github.com/ethereum-optimism/optimism/tree/develop/op-deployer)
* **OPCM Documentation**: [OP Contracts Manager](/chain-operators/reference/opcm)
* **Support**: Open an issue in the [Optimism monorepo](https://github.com/ethereum-optimism/optimism/issues)
# Spin up proposer
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-proposer-setup
Learn how to set up and configure an OP Stack proposer to post L2 state roots.
After you have spun up your sequencer and batcher, you need to attach a proposer to post your L2 state roots data back onto L1 so we can prove withdrawal validity. The proposer is a critical component that enables trustless L2-to-L1 messaging and creates the authoritative view of L2 state from L1's perspective.
**Step 4 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the previous one.
**Automated Setup Available**
For a complete working setup with all components, check out the [automated approach](https://github.com/ethereum-optimism/optimism/tree/develop/docs/public-docs/create-l2-rollup-example/) in the code directory.
This guide assumes you already have a functioning sequencer, batcher, and the necessary L1 contracts deployed using [`op-deployer`](./op-deployer-setup). If you haven't set up your sequencer and batcher yet, please refer to the [sequencer guide](./op-reth-setup) and [batcher guide](./op-batcher-setup) first.
To see configuration info for the proposer, check out the [configuration page](/chain-operators/guides/configuration/proposer).
## Understanding the proposer's role
The proposer (`op-proposer`) serves as a crucial bridge between your L2 chain and L1. Its primary responsibilities include:
* **State commitment**: Proposing L2 state roots to L1 at regular intervals
* **Withdrawal enablement**: Providing the necessary commitments for users to prove and finalize withdrawals
The proposer creates dispute games via the `DisputeGameFactory` contract.
## Prerequisites
Before setting up your proposer, ensure you have:
**Running infrastructure:**
* An operational sequencer node
* Access to a L1 RPC endpoint
**Network information:**
* Your L2 chain ID and network configuration
* L1 network details (chain ID, RPC endpoints)
For setting up the proposer, we recommend using Docker as it provides a consistent and isolated environment. Building from source is also available as an option.
If you prefer containerized deployment, you can use the official Docker images and do the following:
```bash theme={null}
# Create a proposer directory inside rollup
cd ../ # Go back to rollup directory if you're in batcher
mkdir proposer
cd proposer
# inside the proposer directory, copy the state.json file from the op-deployer setup
# Copy configuration files from deployer
cp ../deployer/.deployer/state.json .
# Extract the DisputeGameFactory address
GAME_FACTORY_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].DisputeGameFactoryProxy')
echo "DisputeGameFactory Address: $GAME_FACTORY_ADDRESS"
```
**OP Stack Standard Variables**
The proposer uses OP Stack standard environment variables following the OP Stack conventions. These are prefixed with `OP_PROPOSER_` for proposer-specific settings.
```bash theme={null}
# Create .env file with your actual values
cat > .env << 'EOF'
# L1 Configuration - Replace with your actual RPC URLs
OP_PROPOSER_L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
# L2 Configuration - Should match your sequencer setup
OP_PROPOSER_ROLLUP_RPC=http://op-node:8547
# Contract addresses - Extract from your op-deployer output
OP_PROPOSER_GAME_FACTORY_ADDRESS=YOUR_ACTUAL_GAME_FACTORY_ADDRESS
# Private key - Replace with your actual private key
OP_PROPOSER_PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
# OP Stack proposer configuration (optional - defaults provided)
OP_PROPOSER_PROPOSAL_INTERVAL=3600s
OP_PROPOSER_GAME_TYPE=0
OP_PROPOSER_POLL_INTERVAL=20s
OP_PROPOSER_ALLOW_NON_FINALIZED=true
OP_PROPOSER_WAIT_NODE_SYNC=true
EOF
```
**Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values.
If you get "failed to dial address" errors, ensure your proposer is in the same Docker network as your sequencer.
Common fixes:
* Add `networks: - sequencer-node_default` to your proposer's docker-compose.yml
* Use service names like `op-reth:8545` and `op-node:8547` in your `.env` file
* Verify your sequencer network name with `docker network ls`
```yaml theme={null}
services:
op-proposer:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.16.3
volumes:
- .:/workspace
working_dir: /workspace
ports:
- "8560:8560"
env_file:
- .env
command: >
op-proposer
--rpc.port=8560
--log.level=info
--log.format=json
restart: unless-stopped
networks:
- sequencer-node_default
networks:
sequencer-node_default:
external: false
```
```bash theme={null}
# Make sure your sequencer network exists
docker network create op-stack 2>/dev/null || true
# Start the proposer
docker-compose up -d
# View logs
docker-compose logs -f op-proposer
```
```bash theme={null}
# Check container status
docker-compose ps
```
```bash theme={null}
rollup/
├── deployer/ # From previous step
│ └── .deployer/ # Contains state.json
├── sequencer/ # From previous step
├── batcher/ # From previous step
└── proposer/ # You are here
├── state.json # Copied from deployer
├── .env # Environment variables
└── docker-compose.yml # Docker configuration
```
When you first start your proposer, you'll see several types of log messages:
1. **Initialization messages** (normal):
```
lvl=info msg="Initializing L2Output Submitter"
lvl=info msg="Connected to DisputeGameFactory"
lvl=info msg="Starting JSON-RPC server"
```
2. **Sync status messages** (expected during startup):
```
msg="rollup current L1 block still behind target, retrying"
current_l1=...:9094035 target_l1=9132815
```
This is normal! It means:
* Your rollup is still syncing with L1 (e.g., Sepolia)
* The proposer is waiting until sync is closer to L1 tip
* You'll see the `current_l1` number increasing as it catches up
* Once caught up, the proposer will start submitting proposals
Don't worry about the "retrying" messages - they show healthy progress as your rollup catches up to the latest L1 blocks.
**Common log patterns:**
* Startup: You'll see initialization messages as services start
* Sync: "block still behind target" messages while catching up
* Normal operation: Regular proposal submissions once synced
* Network: Connection messages to L1/L2 endpoints
If you see errors about "failed to dial" or connection issues:
* For source build: Verify your localhost ports and services
Your proposer is now operational and will continuously submit state roots to L1!
### Finding the current stable releases
To ensure you're using the latest compatible versions of OP Stack components, always check the official [releases page](https://github.com/ethereum-optimism/optimism/releases).
Look for the latest `op-proposer/v*` release that's compatible with your sequencer setup.
This guide uses `op-proposer/v1.16.3`, the latest release at the time of writing, alongside op-node/v1.19.3 and op-reth/v2.4.0 from the sequencer setup.
Always check the [release notes](https://github.com/ethereum-optimism/optimism/releases) for compatibility information.
Building from source gives you full control over the binaries.
```bash theme={null}
# If you don't already have the optimism repository from the sequencer setup
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
# Checkout the latest release tag
git checkout op-proposer/v1.16.3
# Build op-proposer
cd op-proposer
just
# Binary will be available at ./bin/op-proposer
```
Run this command to verify the installation:
```bash theme={null}
./bin/op-proposer --version
```
## Configuration setup
The rest of this guide assumes you're using the **build-from-source** approach.
If you chose Docker, all the necessary configuration was covered in the Docker tab above.
Create your proposer working directory at the same level as your sequencer:
```bash theme={null}
# Create proposer directory inside rollup
cd ../ # Go back to rollup directory
mkdir proposer
cd proposer
# Create scripts directory
mkdir scripts
```
Extract the `DisputeGameFactory` contract address from your op-deployer output:
```bash theme={null}
# Make sure you're in the rollup/proposer directory
cd rollup/proposer
# Copy the state.json from deployer
cp ../deployer/.deployer/state.json .
# Extract the DisputeGameFactory address
GAME_FACTORY_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].disputeGameFactoryProxyAddress')
echo "DisputeGameFactory Address: $GAME_FACTORY_ADDRESS"
```
The proposer only needs the `DisputeGameFactory` address to submit proposals.
The `GAME_TYPE=0` represents the standard fault proof game type.
Create your `.env` file with the actual values:
```bash theme={null}
# Create .env file with your actual values
# L1 Configuration - Replace with your actual RPC URL
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
# L2 Configuration - Should match your sequencer setup
L2_RPC_URL=http://localhost:8545
ROLLUP_RPC_URL=http://localhost:8547
# Contract addresses - Extract from your op-deployer output
GAME_FACTORY_ADDRESS=YOUR_ACTUAL_GAME_FACTORY_ADDRESS
# Private key - Replace with your actual private key
PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
# Proposer configuration
PROPOSAL_INTERVAL=3600s
GAME_TYPE=0
POLL_INTERVAL=20s
# RPC configuration
PROPOSER_RPC_PORT=8560
```
**Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values!
Get a private key from your wallet that will be used for submitting proposals to L1. This account needs sufficient ETH to pay for L1 gas costs.
The proposer account needs to be funded with ETH on L1 to pay for proposal submission transactions. Monitor this account's balance regularly as it will consume ETH for each proposal submission.
## Proposer configuration
Create `scripts/start-proposer.sh`:
```bash theme={null}
#!/bin/bash
source .env
# Path to the op-proposer binary we built
../../optimism/op-proposer/bin/op-proposer \
--poll-interval=$POLL_INTERVAL \
--rpc.port=$PROPOSER_RPC_PORT \
--rpc.enable-admin \
--rollup-rpc=$ROLLUP_RPC_URL \
--l1-eth-rpc=$L1_RPC_URL \
--private-key=$PRIVATE_KEY \
--game-factory-address=$GAME_FACTORY_ADDRESS \
--game-type=$GAME_TYPE \
--proposal-interval=$PROPOSAL_INTERVAL \
--num-confirmations=1 \
--resubmission-timeout=30s \
--wait-node-sync=true \
--log.level=info
```
Your final directory structure should look like:
```bash theme={null}
rollup/
├── deployer/ # From previous step
│ └── .deployer/ # Contains state.json
├── optimism/ # Contains op-proposer binary
├── sequencer/ # From previous step
├── batcher/ # From previous step
└── proposer/ # You are here
├── state.json # Copied from deployer
├── .env # Environment variables
└── scripts/ # Startup scripts
└── start-proposer.sh
```
## Starting the proposer
```bash theme={null}
# Make the script executable
chmod +x scripts/start-proposer.sh
# Start the proposer
./scripts/start-proposer.sh
```
When you first start your proposer, you'll see several types of log messages:
1. **Initialization messages** (normal):
```
lvl=info msg="Initializing L2Output Submitter"
lvl=info msg="Connected to DisputeGameFactory"
lvl=info msg="Starting JSON-RPC server"
```
2. **Sync status messages** (expected during startup):
```
msg="rollup current L1 block still behind target, retrying"
current_l1=...:9094035 target_l1=9132815
```
This is normal! It means:
* Your rollup is still syncing with L1 (e.g., Sepolia)
* The proposer is waiting until sync is closer to L1 tip
* You'll see the `current_l1` number increasing as it catches up
* Once caught up, the proposer will start submitting proposals
Don't worry about the "retrying" messages - they show healthy progress as your rollup catches up to the latest L1 blocks.
**Common log patterns:**
* Startup: You'll see initialization messages as services start
* Sync: "block still behind target" messages while catching up
* Normal operation: Regular proposal submissions once synced
* Network: Connection messages to L1/L2 endpoints
If you see errors about "failed to dial" or connection issues:
* For Docker: Check your network configuration and service names
Your proposer is now operational!
## What's Next?
Perfect! Your proposer is submitting state roots to L1. The final step is to set up the challenger to monitor and respond to disputes.
**Next**: Configure and start op-challenger to monitor disputes and maintain your rollup's security.
***
## Need Help?
* **Proposer Configuration**: [op-proposer Configuration Reference](/chain-operators/guides/configuration/proposer)
* **Dispute Games**: [Deploying Dispute Games with OPCM](/chain-operators/tutorials/dispute-games)
# Spin up sequencer
Source: https://docs.optimism.io/chain-operators/tutorials/create-l2-rollup/op-reth-setup
Set up and run op-reth and op-node, the execution and consensus layers for your rollup.
Now that you have op-deployer configured, it's time to spin up the sequencer for your rollup. This involves running both `op-reth` and `op-node` to create a functioning sequencer.
**Step 2 of 5**: This tutorial builds on [Spin up op-deployer](./op-deployer-setup). Make sure you've completed that first.
**op-geth has reached end-of-support (2026-05-31) and does not support the now-active Karst hardfork, so op-geth nodes can no longer follow the canonical chain.** Migrate to op-reth, the primary supported execution client. See the [op-geth deprecation notice](/notices/archive/op-geth-deprecation) for the full migration plan.
This tutorial previously taught `op-geth` as the execution client; this page now uses `op-reth` throughout.
## What you'll set up
The sequencer node consists of two core components:
* `op-reth`: Execution layer that processes transactions and maintains state
* `op-node`: Consensus layer that orders transactions and creates L2 blocks
The sequencer is responsible for:
* Ordering transactions from users
* Building L2 blocks
* Signing blocks on the P2P network
## Software installation
For spinning up a sequencer, we recommend using Docker, as it provides a simpler setup and consistent environment. In this guide, building from source is also provided as an alternative for those who need more control and easier debugging.
The versions used in this guide (**op-node/v1.19.3** and **op-reth/v2.4.0**) were the latest releases at the time of writing.
Check the [op-node releases](https://github.com/ethereum-optimism/optimism/releases?q=op-node) and [op-reth releases](https://github.com/ethereum-optimism/optimism/releases?q=op-reth) for the current versions, and always read the release notes for compatibility information.
If you prefer containerized deployment, you can use the official Docker images, and do the following:
```bash theme={null}
# Create your sequencer directory inside rollup
cd ../ # Go back to rollup directory if you're in deployer
mkdir sequencer
cd sequencer
# Copy configuration files from deployer
cp ../deployer/.deployer/genesis.json .
cp ../deployer/.deployer/rollup.json .
# Generate JWT secret
openssl rand -hex 32 > jwt.txt
chmod 600 jwt.txt
```
```bash theme={null}
# Create .env file with your actual values
cat > .env << 'EOF'
# L1 Configuration - Replace with your actual RPC URLs
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com
# Private keys - Replace with your actual private key
PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
# P2P configuration - Replace with your actual public IP
# Run `curl ifconfig.me` in a separate shell to obtain the value, then paste it below
P2P_ADVERTISE_IP=YOUR_ACTUAL_PUBLIC_IP
EOF
```
**Important**: Replace ALL placeholder values (`YOUR_ACTUAL_*`) with your real configuration values.
Create a `docker-compose.yml` file in the same directory:
```yaml theme={null}
services:
op-reth:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-reth:v2.4.0
volumes:
# Mount entire directory to avoid file mounting issues
- .:/workspace
working_dir: /workspace
ports:
- "8545:8545"
- "8546:8546"
- "8551:8551"
command:
- "node"
- "--chain=/workspace/genesis.json"
- "--datadir=/workspace/op-reth-data"
- "--http"
- "--http.addr=0.0.0.0"
- "--http.port=8545"
- "--http.corsdomain=*"
- "--http.api=admin,debug,eth,net,txpool,web3"
- "--ws"
- "--ws.addr=0.0.0.0"
- "--ws.port=8546"
- "--ws.origins=*"
- "--ws.api=admin,debug,eth,net,txpool,web3"
- "--authrpc.addr=0.0.0.0"
- "--authrpc.port=8551"
- "--authrpc.jwtsecret=/workspace/jwt.txt"
- "--rollup.disable-tx-pool-gossip"
- "--builder.deadline=2"
- "--builder.interval=100ms"
op-node:
image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.19.3
depends_on:
- op-reth
volumes:
- .:/workspace
working_dir: /workspace
ports:
- "8547:8547"
- "9222:9222"
environment:
- L1_RPC_URL=${L1_RPC_URL}
- L1_BEACON_URL=${L1_BEACON_URL}
- PRIVATE_KEY=${PRIVATE_KEY}
- P2P_ADVERTISE_IP=${P2P_ADVERTISE_IP}
command:
- "op-node"
- "--l1=${L1_RPC_URL}"
- "--l1.beacon=${L1_BEACON_URL}"
- "--l2=http://op-reth:8551"
- "--l2.jwt-secret=/workspace/jwt.txt"
- "--l2.enginekind=reth"
- "--rollup.config=/workspace/rollup.json"
- "--sequencer.enabled=true"
- "--sequencer.stopped=false"
- "--sequencer.max-safe-lag=3600"
- "--verifier.l1-confs=4"
- "--p2p.listen.ip=0.0.0.0"
- "--p2p.listen.tcp=9222"
- "--p2p.listen.udp=9222"
- "--p2p.advertise.ip=${P2P_ADVERTISE_IP}"
- "--p2p.advertise.tcp=9222"
- "--p2p.advertise.udp=9222"
- "--p2p.sequencer.key=${PRIVATE_KEY}"
- "--rpc.addr=0.0.0.0"
- "--rpc.port=8547"
- "--rpc.enable-admin"
- "--log.level=info"
- "--log.format=json"
```
A few flags worth understanding:
* `--chain=/workspace/genesis.json` points op-reth at the genesis file generated by op-deployer. Unlike op-geth, op-reth needs no separate `init` step — it initializes its database from the chain spec on first startup.
* `--l2.enginekind=reth` tells op-node it is driving a reth-based execution client.
* `--rollup.disable-tx-pool-gossip` is the op-reth equivalent of op-geth's `--rollup.disabletxpoolgossip=true`.
* `--builder.deadline=2` and `--builder.interval=100ms` tune op-reth's block builder for the 2-second L2 block time (the defaults target Ethereum's 12-second slots).
* op-reth runs as an archive node by default, so there is no `--gcmode=archive` equivalent to set.
```bash theme={null}
# Start both services
docker-compose up -d
# View logs
docker-compose logs -f
```
```bash theme={null}
rollup/
├── deployer/ # From previous step
│ └── .deployer/ # Contains genesis.json and rollup.json
└── sequencer/ # You are here
├── jwt.txt # Generated JWT secret
├── genesis.json # Copied from deployer
├── rollup.json # Copied from deployer
├── .env # Environment variables
├── docker-compose.yml # Docker configuration
├── opnode_discovery_db/ # Created by Docker
├── opnode_peerstore_db/ # Created by Docker
└── op-reth-data/ # Created by Docker (op-reth database)
```
Your sequencer node is now operational and ready to process transactions.
To ensure you're using the latest compatible versions of OP Stack components, always check the official [release page](https://github.com/ethereum-optimism/optimism/releases).
The main components you'll need for sequencer deployment are:
* `op-node`: Look for the latest `op-node/v*` [release](https://github.com/ethereum-optimism/optimism/releases?q=op-node)
* `op-reth`: Look for the latest `op-reth/v*` [release](https://github.com/ethereum-optimism/optimism/releases?q=op-reth)
Both components live in the [optimism monorepo](https://github.com/ethereum-optimism/optimism). Building op-node requires [Go](https://go.dev/dl/) and [just](https://github.com/casey/just); building op-reth requires a [Rust toolchain](https://rustup.rs/) (the build picks up the version pinned in `rust/rust-toolchain.toml` automatically).
Building from source gives you full control over the binaries.
```bash theme={null}
# Clone the optimism monorepo
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
# Checkout the latest op-node release tag
git checkout op-node/v1.19.3
# Generate the embedded superchain config bundle (initializes the
# superchain-registry submodule; op-node embeds this file at compile time)
just build-superchain-go
# Build op-node
cd op-node
just
# Binary will be available at ./bin/op-node
cd ..
```
```bash theme={null}
# Still inside the optimism monorepo:
# checkout the latest op-reth release tag
git checkout op-reth/v2.4.0
# Sync the superchain-registry submodule to this tag
# (the op-reth build generates its chain configs from it)
just update-superchain-registry-submodule
# Build op-reth (release build; this can take a while)
cd rust
cargo build --release --bin op-reth
# Binary will be available at ./target/release/op-reth
cd ..
```
op-node and op-reth release from the same repository on different tags, so the two builds check out different tags in sequence. The `--version` checks below confirm what you actually built. If you prefer, use two separate clones of the monorepo instead.
Check that you have properly installed the needed components.
```bash theme={null}
# From the optimism monorepo root
./op-node/bin/op-node --version
./rust/target/release/op-reth --version
```
## Configuration setup
After building the binaries, you should have the following directory structure:
```bash theme={null}
rollup/
├── deployer/ # From previous step
│ └── .deployer/ # Contains genesis.json and rollup.json
└── optimism/ # Optimism monorepo
├── op-node/
│ └── bin/
│ └── op-node
└── rust/
└── target/
└── release/
└── op-reth
```
Now create your sequencer working directory:
```bash theme={null}
cd ../
mkdir sequencer
cd sequencer
```
```bash theme={null}
openssl rand -hex 32 > jwt.txt
chmod 600 jwt.txt
```
```bash theme={null}
mkdir scripts
cp ../deployer/.deployer/genesis.json .
cp ../deployer/.deployer/rollup.json .
```
You'll need to gather several pieces of information before creating your configuration.
You need access to the L1 network (Ethereum mainnet or Sepolia testnet) and its beacon node.
**L1 RPC URL options:**
* **Infura**: [infura.io](https://infura.io)
* **Alchemy**: [alchemy.com](https://alchemy.com)
**L1 Beacon URL options:**
* `https://ethereum-sepolia-beacon-api.publicnode.com`
* `https://ethereum-beacon-api.publicnode.com`
For this basic sequencer setup, you only need a private key during op-node initialization.
```bash theme={null}
curl ifconfig.me
curl ipinfo.io/ip
```
* `8545`: op-reth HTTP RPC
* `8546`: op-reth WebSocket RPC
* `8551`: op-reth Auth RPC (Engine API)
* `8547`: op-node RPC
* `9222`: P2P networking
```bash theme={null}
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_ACTUAL_INFURA_KEY
L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com
SEQUENCER_ENABLED=true
SEQUENCER_STOPPED=false
PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
P2P_LISTEN_PORT=9222
P2P_ADVERTISE_IP=YOUR_ACTUAL_PUBLIC_IP
OP_NODE_RPC_PORT=8547
OP_RETH_HTTP_PORT=8545
OP_RETH_WS_PORT=8546
OP_RETH_AUTH_PORT=8551
JWT_SECRET=./jwt.txt
```
## Sequencer specific configuration
### op-reth configuration for sequencer
Create `scripts/start-op-reth.sh`:
```bash theme={null}
#!/bin/bash
source .env
../optimism/rust/target/release/op-reth node \
--chain=./genesis.json \
--datadir=./op-reth-data \
--http \
--http.addr=0.0.0.0 \
--http.port=$OP_RETH_HTTP_PORT \
--http.corsdomain="*" \
--http.api=admin,debug,eth,net,txpool,web3 \
--ws \
--ws.addr=0.0.0.0 \
--ws.port=$OP_RETH_WS_PORT \
--ws.origins="*" \
--ws.api=admin,debug,eth,net,txpool,web3 \
--authrpc.addr=0.0.0.0 \
--authrpc.port=$OP_RETH_AUTH_PORT \
--authrpc.jwtsecret=$JWT_SECRET \
--rollup.disable-tx-pool-gossip \
--builder.deadline=2 \
--builder.interval=100ms
```
op-reth initializes its database from the genesis file on first startup — there is no separate `geth init`-style step. It also runs as an archive node by default.
### op-node configuration for sequencer
Create `scripts/start-op-node.sh`:
```bash theme={null}
#!/bin/bash
source .env
../optimism/op-node/bin/op-node \
--l1=$L1_RPC_URL \
--l1.beacon=$L1_BEACON_URL \
--l2=http://localhost:$OP_RETH_AUTH_PORT \
--l2.jwt-secret=$JWT_SECRET \
--l2.enginekind=reth \
--rollup.config=./rollup.json \
--sequencer.enabled=$SEQUENCER_ENABLED \
--sequencer.stopped=$SEQUENCER_STOPPED \
--sequencer.max-safe-lag=3600 \
--verifier.l1-confs=4 \
--p2p.listen.ip=0.0.0.0 \
--p2p.listen.tcp=$P2P_LISTEN_PORT \
--p2p.listen.udp=$P2P_LISTEN_PORT \
--p2p.advertise.ip=$P2P_ADVERTISE_IP \
--p2p.advertise.tcp=$P2P_LISTEN_PORT \
--p2p.advertise.udp=$P2P_LISTEN_PORT \
--p2p.sequencer.key=$PRIVATE_KEY \
--rpc.addr=0.0.0.0 \
--rpc.port=$OP_NODE_RPC_PORT \
--rpc.enable-admin \
--log.level=info \
--log.format=json
```
## Starting the sequencer
```bash theme={null}
cd rollup/sequencer
chmod +x scripts/start-op-reth.sh
chmod +x scripts/start-op-node.sh
./scripts/start-op-reth.sh
```
In a second terminal:
```bash theme={null}
./scripts/start-op-node.sh
```
```bash theme={null}
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
http://localhost:8545
curl -X POST -H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"admin_sequencerActive","params":[],"id":1}' \
http://localhost:8547
```
Your sequencer node is now operational and ready to process transactions.
## What's Next?
Great! Your sequencer is running and processing transactions. The next step is to set up the batcher to publish transaction data to L1.
**Next**: Configure and start op-batcher to publish L2 transaction data to L1 for data availability.
***
## Need Help?
* **Running op-reth**: [Execution client configuration](/node-operators/guides/configuration/execution-clients)
* **op-reth CLI reference**: [op-reth node command](/node-operators/op-reth/cli/op-reth/node)
* **Best Practices**: [Chain Operator Best Practices](/chain-operators/guides/management/best-practices)
* **Support**: [Report an issue or ask a question](https://github.com/ethereum-optimism/optimism/issues) in the Optimism monorepo
# Deploying new dispute games with OPCM
Source: https://docs.optimism.io/chain-operators/tutorials/dispute-games
Learn how to deploy new dispute games to an OP Stack chain using OPCM
This guide provides instructions on how to deploy new dispute games to an OP Stack chain using the [OPCM (OP Contracts Manager)](/chain-operators/reference/opcm). This process is particularly relevant for teams looking to upgrade their chains to support permissionless dispute games.
## Prerequisites
Before you begin, ensure that:
* Run op-contracts/v2.0.0 or higher on your chain
* Own the chain's L1 `ProxyAdmin` contract
* Install the Forge toolkit (see [Foundry docs](https://getfoundry.sh/))
## Understanding dispute games
The OP Stack uses two types of dispute games:
* **Permissioned dispute game**: Limited to specific proposer and challenger addresses
* **Permissionless dispute game**: Open to anyone to propose or challenge
In the Permissioned dispute game (PDG), the [challenger role](/op-stack/protocol/privileged-roles) is a protocol-level permission assigned to specific addresses, allowing them to initiate or respond to disputes. This role is distinct from the op-challenger service, which is an off-chain monitoring service responsible for automatically detecting discrepancies and submitting challenges.
While the op-challenger service typically operates using an address that has been assigned the challenger role, the protocol-level role itself can be independently assigned, regardless of whether the op-challenger service is in use.
Refer to the [OP Stack configurability spec](https://specs.optimism.io/protocol/configurability.html) for more details.
All chains deployed with `op-deployer` only initially include the permissioned dispute game.
This guide explains how to add the permissionless game.
## The `addGameType` function
The OPCM contract contains an `addGameType` function that handles the deployment of new dispute games. This function:
1. Deploys a new dispute game implementation
2. Optionally deploys a new [`DelayedWETH`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/dispute/DelayedWETH.sol) contract
3. Registers the game with the `DisputeGameFactory`
4. Sets the initial bond amount
### 1. Finding the correct OPCM instance
Each OP Contracts release has its own OPCM instance. You must use the OPCM corresponding exactly to your chain's contract version. For example, if your system uses contracts version **v3.0.0**, you must use the OPCM for **v3.0.0**.
To find the correct OPCM address:
* For **Mainnet**, refer to the [standard-versions-mainnet.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-mainnet.toml) file in the superchain-registry.
* For **Sepolia**, refer to the [standard-versions-sepolia.toml](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-versions-sepolia.toml) file in the superchain-registry.
These registry files contain mappings between OP contract versions and their corresponding OPCM addresses.
### 2. Preparing the `addGameType` Call
The `addGameType` function expects an array of `AddGameInput` structs. Here is the structure:
```solidity theme={null}
struct AddGameInput {
string saltMixer;
ISystemConfig systemConfig;
IProxyAdmin proxyAdmin;
IDelayedWETH delayedWETH;
GameType disputeGameType;
Claim disputeAbsolutePrestate;
uint256 disputeMaxGameDepth;
uint256 disputeSplitDepth;
Duration disputeClockExtension;
Duration disputeMaxClockDuration;
uint256 initialBond;
IBigStepper vm;
bool permissioned;
}
```
**Key parameters explained:**
* **saltMixer:** A string used to create unique contract addresses
* **systemConfig:** The address of your chain's `SystemConfig` contract
* **proxyAdmin:** The Address of your chain's `ProxyAdmin` contract
* **delayedWETH:** The Address of the `DelayedWETH` contract (use zero address to deploy a new one)
* **disputeGameType:** For permissionless games, use `GameTypes.CANNON`
* **disputeAbsolutePrestate:** The absolute prestate hash for the game
* **permissioned:** Set to `false` for a permissionless game
For a permissionless game, you'll generally want to mirror most parameters from your existing permissioned game, but set permissioned to `false` and use the GameType `CANNON`.
3. Execute the addGameType function
The following is a template for calling the addGameType function using Forge's cast:
The most recommended way is to use a script to execute this call, rather than manual execution.
```bash theme={null}
# Retrieve existing values from chain for reference
# Get permissioned game implementation
PERMISSIONED_GAME=$(cast call --rpc-url $RPC_URL $DISPUTE_GAME_FACTORY "gameImpls(uint32)" $PERMISSIONED_GAME_TYPE)
# Retrieve parameters from existing permissioned game
ABSOLUTE_PRESTATE=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "absolutePrestate()")
MAX_GAME_DEPTH=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "maxGameDepth()")
SPLIT_DEPTH=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "splitDepth()")
CLOCK_EXTENSION=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "clockExtension()")
MAX_CLOCK_DURATION=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "maxClockDuration()")
VM=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "vm()")
ANCHOR_STATE_REGISTRY=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "anchorStateRegistry()")
L2_CHAIN_ID=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "l2ChainId()")
# Create call data for addGameType function
# Note: Set delayedWETH to 0x0 to deploy a new one
CALLDATA=$(cast calldata "addGameType((string,address,address,address,uint32,bytes32,uint256,uint256,uint64,uint64,uint256,address,bool)[])" \
"[(\
\"unique_salt_mixer\",\
$SYSTEM_CONFIG,\
$PROXY_ADMIN,\
0x0000000000000000000000000000000000000000,\
$CANNON_GAME_TYPE,\
$ABSOLUTE_PRESTATE,\
$MAX_GAME_DEPTH,\
$SPLIT_DEPTH,\
$CLOCK_EXTENSION,\
$MAX_CLOCK_DURATION,\
$INITIAL_BOND,\
$VM,\
false\
)]")
# Execute the transaction
cast send --rpc-url $RPC_URL --private-key $PRIVATE_KEY $OPCM_ADDRESS $CALLDATA
```
4. Setting the respected game type
After deploying the permissionless dispute game, you'll need to update the respectedGameType in the OptimismPortal to start using it.
For detailed instructions on setting the respected game type and migrating your chain from permissioned to permissionless fault proofs, refer to the [migrating to permissionless fault proofs guide](/chain-operators/tutorials/migrating-permissionless).
## Next Steps
* For more detail on deploying new dispute games with OPCM, [see the docs](/chain-operators/tutorials/dispute-games).
* Learn about [absolute prestate](/chain-operators/tutorials/absolute-prestate)
* checkout the [migrating to permissionless fault proofs](/chain-operators/tutorials/migrating-permissionless) guide
* [Fault proofs explainer](/op-stack/fault-proofs/explainer)
# Integrating a new DA layer with Alt-DA
Source: https://docs.optimism.io/chain-operators/tutorials/integrating-da-layer
Learn how to add support for a new DA Layer within the OP Stack.
The Alt-DA Mode feature is currently in Beta within the MIT-licensed OP Stack. Beta features are built and reviewed by Optimism Collective core contributors, and provide developers with early access to highly requested configurations.
These features may experience stability issues, and we encourage feedback from our early users.
[Alt-DA Mode](/op-stack/features/experimental/alt-da-mode) enables seamless integration of any DA Layer, regardless of their commitment type, into the OP Stack. After a DA Server is built for a DA Layer, any chain operator can launch an OP Stack chain using that DA Layer for sustainably low costs.
## Build your DA server
Our suggestion is for every DA Layer to build and maintain their own DA Server, with support from the OP Labs team along the way. The DA Server will need to be run by every node operator, so we highly recommend making your DA Server open source and MIT licensed.
* It must point to the data on your layer (like block height / hash).
* It must be able to validate the data returned from the data (i.e., include a cryptographic commitment to the data like a hash, merkle proof, or polynomial commitment, this could be done against the block hash with a complex proof).
See the [specs](https://specs.optimism.io/experimental/alt-da.html?highlight=input-commitment-submission?utm_source=op-docs\&utm_medium=docs#input-commitment-submission) for more info on commitment submission.
* Claim your [byte](https://github.com/ethereum-optimism/specs/discussions/135)
* Write a simple HTTP server which supports `get` and `put`
* `put` is used by the batcher and can return the commitment to the batcher in the body. It should not return until the data is known to be submitted to your DA layer.
* `get` should fetch the data. If the data is not available, it should return a `404` not found. If there are other errors, a different error should be returned.
## Run Alt-DA
Follow our guide on [how to operate an Alt-DA Mode chain](/chain-operators/guides/features/alt-da-mode-guide), except instead of using the S3 DA server, use the DA server that you built.
## Next steps
* For more detail on implementing the DA Server, [see the specification](https://specs.optimism.io/experimental/alt-da.html?utm_source=op-docs\&utm_medium=docs#da-server).
# Generating a custom kona-client absolute prestate
Source: https://docs.optimism.io/chain-operators/tutorials/kona-custom-prestate
How to build a kona-client absolute prestate that embeds a chain configuration not yet in the public Superchain Registry.
# Overview
For chains that are part of the public [`superchain-registry`](https://github.com/ethereum-optimism/superchain-registry), the standard `kona-client` absolute prestate published in [`standard-prestates.toml`](https://github.com/ethereum-optimism/superchain-registry/blob/main/validation/standard/standard-prestates.toml) already embeds your chain configuration. No custom build is needed.
This tutorial is for the rarer case: a partner chain whose `RollupConfig` is not yet in the public registry but which still needs to run permissionless fault proofs (`cannon-kona` game type). In that case you must build a custom `kona-client` that embeds your chain's configuration, generate the absolute prestate hash from that build, and host the resulting binary at a URL `op-challenger` can fetch.
Most chains are in the Superchain Registry and should use the standard prestate; follow [Generating absolute prestate and preimage files](/chain-operators/tutorials/absolute-prestate) instead. Only follow this tutorial if your chain is **not yet** in the registry. Once the chain is added to the public registry, future releases will cover it via the standard prestate and you can stop maintaining a custom build.
## Prerequisites
Before starting, ensure you have:
* [Docker](https://docs.docker.com/engine/install/) running
* [`just`](https://github.com/casey/just) installed
* Your chain's `rollup.json`, L2 genesis, and deployment artifacts (typically produced by `op-deployer`)
## How kona-client picks up custom chain configurations
`kona-client` reads its embedded chain registry from the [`kona-registry` crate](https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona/crates/protocol/registry). At build time, the crate's `build.rs` can merge additional chains on top of the canonical Superchain Registry snapshot. The merge is gated by two environment variables:
```bash theme={null}
KONA_CUSTOM_CONFIGS=true
KONA_CUSTOM_CONFIGS_DIR=/absolute/path/to/your/configs
```
When set, the build script reads two files from `KONA_CUSTOM_CONFIGS_DIR`:
* `chainList.json` — light-weight `Chain` entries for your custom chains
* `configs.json` — full `ChainConfig` + `RollupConfig` entries grouped under a `Superchain` bucket
The full schema and an end-to-end example are documented in the [`kona-registry` README](https://github.com/ethereum-optimism/optimism/tree/develop/rust/kona/crates/protocol/registry#custom-chain-configurations).
The compiled `kona-client` binary embeds the merged registry via `include_str!()`, so the resulting absolute prestate hash differs from the standard one. Any honest challenger participating in your chain's dispute games will need to run this same binary.
## Generating the custom prestate
Use the same `kona-node/v` tag that your chain's nodes are running. The native `KONA_CUSTOM_CONFIGS_DIR` support documented below requires **`kona-node/v1.5.2` or later**; on older tags, additional justfile patches are needed.
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism.git
cd optimism
git checkout kona-node/v
```
Create a directory for your custom configs:
```bash theme={null}
mkdir -p rust/kona/crates/protocol/registry/etc/custom-configs/
```
Add two files inside it — `chainList.json` (lightweight chain entry) and `configs.json` (full rollup config). Replace every `` with your chain's value. Notes:
* **``**: `mainnet` if your L1 is Ethereum mainnet, `sepolia` if your L1 is Sepolia. The bucket groups chains by L1 network, not by governance — your custom-chain status is expressed via `governedByOptimism: false` and `superchainLevel: 0`.
* **`faultProofs.status`**: `permissionless` for chains running the `cannon-kona` game type publicly, `permissioned` for chains that only allow whitelisted challengers.
```json theme={null}
[
{
"name": "",
"identifier": "/",
"chainId": ,
"rpc": [],
"explorers": [],
"superchainLevel": 0,
"governedByOptimism": false,
"dataAvailabilityType": "eth-da",
"parent": { "type": "L2", "chain": "" },
"faultProofs": { "status": "" }
}
]
```
```json theme={null}
{
"superchains": [
{
"name": "",
"config": {
"name": "",
"l1": {
"chain_id": ,
"public_rpc": "",
"explorer": ""
},
"hardforks": {
"canyon_time": 0,
"delta_time": 0,
"ecotone_time": 0,
"fjord_time": 0,
"granite_time": 0,
"holocene_time": 0,
"isthmus_time": 0,
"jovian_time": 0
},
"superchain_config_addr": null,
"op_contracts_manager_proxy_addr": null
},
"chains": [
{
"Name": "",
"PublicRPC": "",
"SequencerRPC": "",
"Explorer": "",
"SuperchainLevel": 0,
"GovernedByOptimism": false,
"SuperchainTime": 0,
"DataAvailabilityType": "eth-da",
"l2_chain_id": ,
"batch_inbox_address": "",
"block_time": 2,
"seq_window_size": 3600,
"max_sequencer_drift": 600,
"GasPayingToken": null,
"hardfork_configuration": {
"canyon_time": 0,
"delta_time": 0,
"ecotone_time": 0,
"fjord_time": 0,
"granite_time": 0,
"holocene_time": 0,
"isthmus_time": 0,
"jovian_time": 0
},
"optimism": {
"eip1559Elasticity": 6,
"eip1559Denominator": 50,
"eip1559DenominatorCanyon": 250
},
"alt_da": null,
"genesis": {
"l1": {
"number": ,
"hash": ""
},
"l2": {
"number": 0,
"hash": ""
},
"l2_time": ,
"system_config": {
"batcherAddr": "",
"overhead": "0x0000000000000000000000000000000000000000000000000000000000000000",
"scalar": "",
"gasLimit": 60000000
}
},
"Roles": {
"SystemConfigOwner": "",
"ProxyAdminOwner": "",
"Guardian": "",
"Challenger": "",
"Proposer": "",
"UnsafeBlockSigner": "",
"BatchSubmitter": ""
},
"Addresses": {
"AddressManager": "",
"L1CrossDomainMessengerProxy": "",
"L1Erc721BridgeProxy": "",
"L1StandardBridgeProxy": "",
"L2OutputOracleProxy": "0x0000000000000000000000000000000000000000",
"OptimismMintableErc20FactoryProxy": "",
"OptimismPortalProxy": "",
"SystemConfigProxy": "",
"ProxyAdmin": "",
"AnchorStateRegistryProxy": "",
"DelayedWethProxy": "",
"DisputeGameFactoryProxy": "",
"FaultDisputeGame": "",
"PermissionedDisputeGame": ""
}
}
]
}
]
}
```
Source the values from your chain's existing artifacts:
* `genesis.*`, `block_time`, `seq_window_size`, `max_sequencer_drift`, `batch_inbox_address`, hardfork timestamps → your chain's `rollup.json`
* `Addresses.*` → your `op-deployer` state (`opChainDeployments[]`)
* `Roles.*` → your `op-deployer` state's per-chain `roles` block
Cross-check each L1 contract address onchain (`cast call`) before committing.
From the root of the monorepo, point `KONA_CUSTOM_CONFIGS_DIR` at the directory you created in Step 2, then run the canonical build:
```bash theme={null}
export KONA_CUSTOM_CONFIGS_DIR="$PWD/rust/kona/crates/protocol/registry/etc/custom-configs/"
just reproducible-prestate-kona
jq -r .pre rust/kona/prestate-artifacts-cannon/prestate-proof.json
```
The hash printed is your chain's custom kona absolute prestate. The build also writes a hash-named gzipped binary at `rust/kona/prestate-artifacts-cannon/0x.bin.gz` — this is the file `op-challenger` needs to fetch at dispute time.
A custom-config build that silently fails to merge your chain produces the *standard* prestate hash — and an `op-challenger` pointed at it will never agree with your games. Always confirm your chain actually made it in:
```bash theme={null}
# 1. The build log must list every custom chain that was merged:
# cargo:warning=...: inserting new custom chain : [chain-a,chain-b]
# (or "merging custom chains : [...]" if the bucket already exists)
#
# 2. The chain name must appear inside the prestate image itself:
gunzip -c rust/kona/prestate-artifacts-cannon/prestate.bin.gz | strings | grep ""
```
Run the `grep` once per chain in your `chainList.json` and confirm at least one match for **every** chain — a build that embeds chain A but drops chain B still produces a valid-looking hash. Zero matches means `KONA_CUSTOM_CONFIGS=true` was not set in the build shell, or `KONA_CUSTOM_CONFIGS_DIR` was a relative path.
From a fresh checkout of the same tag with the same custom-configs files in place, repeat the build and confirm the hash is bit-identical. Anyone who wants to participate as an honest challenger on your chain must be able to reproduce this hash from the same inputs.
## Deploying and configuring with the custom prestate
Upload `0x.bin.gz` to wherever you serve preimage files for `op-challenger` — typically a GCS bucket or HTTP endpoint. Files must be named by their absolute prestate hash so the challenger can resolve them on demand.
On the current (v2.4+) dispute-game contracts the absolute prestate is **not** a constructor argument — it lives in the `DisputeGameFactory`'s per-game-type `gameArgs`, appended to each game via clones-with-immutable-args. So you do **not** deploy a new implementation for a new prestate. Reuse the existing permissionless `FaultDisputeGame` implementation (a fresh op-deployer chain deploys one but leaves it unregistered) and register it for game type 8 with `gameArgs` that carry your prestate.
`gameArgs` for a permissionless game is the packed encoding (124 bytes):
```
abi.encodePacked(absolutePrestate, vm, anchorStateRegistry, weth, l2ChainId)
```
Reuse `vm` / `anchorStateRegistry` / `weth` / `l2ChainId` from an existing registered game type (e.g. read `gameArgs(1)` and swap in your prestate), then register from the `DisputeGameFactory` owner:
```solidity theme={null}
// GAME_TYPE_CANNON_KONA == 8
DisputeGameFactory.setImplementation(8, faultDisputeGameImpl, gameArgs);
DisputeGameFactory.setInitBond(8, initBond); // 0 is fine for a dev/test chain
```
Then make game type 8 the respected type from the Guardian role — `respectedGameType` lives on the `AnchorStateRegistry`, not the `OptimismPortal` (the portal proxies to it):
```solidity theme={null}
AnchorStateRegistry.setRespectedGameType(8);
```
See [Migrating to permissionless fault proofs](/chain-operators/tutorials/migrating-permissionless) for the full role/transaction walkthrough. (On older, pre-v2.4 contracts where `absolutePrestate` was a constructor immutable, each prestate did require a fresh implementation deployment instead.)
Add the kona-specific env vars to your existing challenger config:
```bash theme={null}
OP_CHALLENGER_TRACE_TYPE=cannon-kona,permissioned
OP_CHALLENGER_CANNON_KONA_PRESTATES_URL=
```
The challenger appends `/0x.bin.gz` to `*_PRESTATES_URL` to resolve the right binary per dispute. Your existing `OP_CHALLENGER_ROLLUP_CONFIG`, `OP_CHALLENGER_L2_GENESIS`, and `OP_CHALLENGER_GAME_FACTORY_ADDRESS` continue to apply unchanged.
## Next Steps
* [Upgrade 19 notice](/notices/archive/upgrade-19) — the `cannon-kona` game type promotion and reference build commands for the standard prestate.
* [Generating absolute prestate and preimage files](/chain-operators/tutorials/absolute-prestate): the standard `kona-client` prestate flow for chains in the Superchain Registry.
* [Generating an op-program absolute prestate (archived)](/chain-operators/tutorials/archive/op-program-prestate): the legacy `op-program` flow, kept only for resolving in-flight `CANNON` (game type `0`) disputes created before [Upgrade 19](/notices/archive/upgrade-19). `op-program` has reached end-of-support; see [End of Support for op-geth and op-program](/notices/archive/op-geth-deprecation).
# Upgrade L1 contracts using op-deployer
Source: https://docs.optimism.io/chain-operators/tutorials/l1-contract-upgrades/op-deployer-upgrade
Version availability and migration paths for the op-deployer upgrade command, which supports L1 contract upgrades up to op-contracts/v5.0.0.
The `op-deployer upgrade` command can be used for upgrades up to `op-contracts/v5.0.0`. It does **not** support
upgrading from `op-contracts/v5.0.0` to `op-contracts/v6.0.0`. For upgrades beyond v5.0.0, use
[superchain-ops](/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide) or interact with the
OPCM directly. See the [deprecation notice](/notices/archive/op-deployer-upgrade-deprecation) for details.
## Version availability
The `op-deployer upgrade` command supports the following contract upgrade paths (upgrades must be performed in steps):
| Upgrade path | op-deployer version | Status |
| ------------------------------------------ | ------------------- | --------- |
| op-contracts/v1.8.0 to op-contracts/v2.0.0 | v0.2.x | Available |
| op-contracts/v2.0.0 to op-contracts/v3.0.0 | v0.3.x | Available |
| op-contracts/v3.0.0 to op-contracts/v4.0.0 | v0.4.x | Available |
| op-contracts/v4.0.0 to op-contracts/v5.0.0 | v0.5.x | Available |
Each minor version of `op-deployer` supported a single release of the governance-approved smart contracts. See the [releases guide](/chain-operators/tools/op-deployer/reference/releases) for more information on versioning.
## Migration
For L1 contract upgrades beyond `op-contracts/v5.0.0`, use [superchain-ops](/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide). For non-Optimism governed chains, you can interact with the OPCM directly using your own tooling.
# Upgrade using superchain-ops
Source: https://docs.optimism.io/chain-operators/tutorials/l1-contract-upgrades/superchain-ops-guide
Upgrade your chain's L1 contracts with superchain-ops by creating a task from a template, configuring and simulating it, then executing it or submitting it for review.
This guide outlines the process for upgrading Optimism chains using the `superchain-ops` repository. It's intended primarily for OP Stack chains managed by the Security Council, those with the Foundation or Security Council as signers, and/or chains requiring a highly secure process.
For chains that don't require the enhanced security of superchain-ops or security council signing, alternative upgrade tooling may be used. Note that the `op-deployer upgrade` command only supports upgrades [up to `op-contracts/v5.0.0`](/notices/archive/op-deployer-upgrade-deprecation).
For non-Optimism governed chains, you can use your own tooling to interact with the OPCM directly to upgrade your chain.
`superchain-ops` is a highly secure service designed for Optimism chains. It provides a structured and security-focused approach to chain upgrades. The process involves creating tasks that use predefined templates to generate the necessary upgrade transactions.
## Who should use `superchain-ops`
`superchain-ops` is primarily intended for:
1. **OP Stack chains managed by the Security Council**: For standard chains managed by the Optimism Security Council, upgrades are typically handled through `superchain-ops`.
2. **Chains with Foundation or Security Council as signers**: If your chain has the Foundation multi-sig or Security Council as signers, your upgrade tasks should go through `superchain-ops`.
3. **Chains requiring a highly secure process**: For chains that prioritize security over automation, `superchain-ops` provides an intentionally manual workflow with thorough verification steps (e.g. EVM state diff inspection).
For chains that don't fall into these categories, you'll need to generate appropriate call data for upgrades through other means or develop your own upgrade process for non-OPCM upgrades.
## Understanding templates and tasks
`superchain-ops` uses two key concepts:
* **Templates**: Define what the upgrade is and contain the code for specific upgrade paths (e.g., [`op-contracts/v1.8.0` to `op-contracts/v2.0.0`](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/template/OPCMUpgradeV200.sol)). Templates are version-specific and live in the [/src/improvements/template](https://github.com/ethereum-optimism/superchain-ops/tree/main/src/improvements/template) directory.
* **Tasks**: Use a template to define a specific upgrade transaction for a chain. Multiple tasks can use the same template. Tasks are organized by network (`eth` or `sep`) in the [/src/improvements/tasks](https://github.com/ethereum-optimism/superchain-ops/tree/main/src/improvements/tasks) directory.
## General upgrade process
The following process outlines how to upgrade a chain using `superchain-ops`, using the [`op-contracts/v1.8.0` to `op-contracts/v2.0.0`](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/template/OPCMUpgradeV200.sol) upgrade as an example. This same pattern applies to other OPCM-based upgrades (like [`op-contracts/v2.0.0` to `op-contracts/v3.0.0`](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/template/OPCMUpgradeV300.sol)).
### Step 1: Clone the `superchain-ops` repository
```bash theme={null}
git clone https://github.com/ethereum-optimism/superchain-ops.git
cd superchain-ops/src/improvements
```
### Step 1a: One-time Install Dependencies Setup
Follow the 'Install Dependencies' instructions in the ['Quick Start'](https://github.com/ethereum-optimism/superchain-ops/blob/main/README.md#quick-start) section of the `README.md` file.
### Step 2: Create a new task using the quick start
```bash theme={null}
just new task
```
Follow the prompts to select the appropriate template (e.g., `OPCMUpgradeV200` for a `op-contracts/v1.8.0` to `op-contracts/v2.0.0` upgrade) and provide the necessary details.
This will create a new task directory containing a `config.toml` and `README` file. The config file will look like this:
```bash theme={null}
l2chains = [] # e.g. [{name = "OP Mainnet", chainId = 10}]
templateName = "OPCMUpgradeV200"
```
### Step 3: Configure the task
You'll have to add additional properties to your `config.toml` file to fully configure your task. For example, when upgrading from `op-contracts/v1.8.0` to `op-contracts/v2.0.0`, you can look at a previous task for reference: [src/improvements/tasks/eth/002-opcm-upgrade-v200/config.toml](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/improvements/tasks/eth/002-opcm-upgrade-v200/config.toml):
This is an example task. You must figure out which values you'll need for your own specific task. Ensure you replace all addresses and other values in the example below.
```bash theme={null}
l2chains = [
{name = "Unichain", chainId = 130}
]
templateName = "OPCMUpgradeV200"
[opcmUpgrades]
absolutePrestates = [
{absolutePrestate = "0x039facea52b20c605c05efb0a33560a92de7074218998f75bcdf61e8989cb5d9", chainId = 130},
]
[addresses]
OPCM = "0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76"
StandardValidatorV200 = "0xecabaeaa1d58261f1579232520c5b460ca58a164"
```
### Step 5: Simulate the task
Before executing the upgrade, simulate it to ensure everything is configured correctly:
```bash theme={null}
just --dotenv-path $(pwd)/.env simulate [child-safe-name-depth-1] [child-safe-name-depth-2]
# Both [child-safe-name-depth-1] and [child-safe-name-depth-2] are optional. You'll only need to specify
# [child-safe-name-depth-2] if it's a nested safe and [child-safe-name-depth-2] if it has multiple levels of nesting.
# Omit both arguments if it's a single safe.
```
This will run through the upgrade process without actually executing the transaction.
For more information on the simulate command, please reference the [README](https://github.com/ethereum-optimism/superchain-ops/blob/main/README.md#quick-start).
### Step 6: Execute or submit for review
For OP Stack chains managed by the Security Council, submit a pull request to have your task reviewed. If your chain is not managed by the Security Council, execute the transaction yourself.
# Upgrading Smart Contracts from v1.3.0 to v1.8.0
Source: https://docs.optimism.io/chain-operators/tutorials/l1-contract-upgrades/upgrade-op-contracts-1-3-1-8
Upgrade your OP Stack chain's L1 contracts from op-contracts/v1.3.0 to v1.8.0, moving from the L2 Output Oracle to the permissioned Fault Proof System.
This guide provides specific instructions for upgrading the OP Stack's Layer 1 contracts from `op-contracts/v1.3.0` to `op-contracts/v1.8.0`. This upgrade includes important changes to the system configuration and introduces the Fault Proof System.
## Overview of the Holocene upgrade
The Holocene upgrade is a protocol upgrade. Learn more about it in the [Holocene notice page](https://docs.optimism.io/notices/holocene-changes). This guide shows you how to take your OP Stack chain from the L2 Output Oracle System to a permissioned Fault Proof System contract version associated with the Holocene upgrade.
After upgrading to OP Stack contracts v1.8.0 and enabling permissioned fault proofs, all pending (unfinalized) withdrawal proofs created on L1 are invalidated. This means that withdrawals must be manually reproven by users after the upgrade; the process is not automatic.
### Formatting config files
You need your chain's `deployments.json` and `deploy-config.json`
Ensure both files are correctly formatted, using the exact field names shown in the below examples, with **ALL** values - particularly the relevant ones - updated for **your specific chain**:
`deployments.json`:
```json theme={null}
{
"AddressManager": "0xEF8115F2733fb2033a7c756402Fc1deaa56550Ef",
"L2OutputOracleProxy": "0x9E6204F750cD866b299594e2aC9eA824E2e5f95c",
"OptimismMintableERC20FactoryProxy": "0xc52BC7344e24e39dF1bf026fe05C4e6E23CfBcFf",
"L1StandardBridgeProxy": "0x3e2Ea9B92B7E48A52296fD261dc26fd995284631",
"ProxyAdmin": "0xD4ef175B9e72cAEe9f1fe7660a6Ec19009903b49",
"L1CrossDomainMessengerProxy": "0xdC40a14d9abd6F410226f1E6de71aE03441ca506",
"L1ERC721BridgeProxy": "0x83A4521A3573Ca87f3a971B169C5A0E1d34481c3",
"OptimismPortalProxy": "0x1a0ad011913A150f69f6A19DF447A0CfD9551054",
"SystemConfigProxy": "0xA3cAB0126d5F504B071b81a3e8A2BBBF17930d86"
}
```
`deploy-config.json`:
```json theme={null}
{
"l1StartingBlockTag": "0x10aa183",
"l1ChainID": 1,
"l2ChainID": 7777777,
"l2BlockTime": 2,
"finalizationPeriodSeconds": 604800,
"controller": "0xEe729F57F0111FD0F660867d0F522f983202a5aF",
"baseFeeVaultRecipient": "0xe900b3Edc1BA0430CFa9a204A1027B90825ac951",
"l1FeeVaultRecipient": "0xe900b3Edc1BA0430CFa9a204A1027B90825ac951",
"sequencerFeeVaultRecipient": "0xe900b3Edc1BA0430CFa9a204A1027B90825ac951",
"l2GenesisBlockBaseFeePerGas": "0x3b9aca00",
"governanceTokenOwner": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542",
"governanceTokenSymbol": "OP",
"governanceTokenName": "Optimism",
"maxSequencerDrift": 600,
"sequencerWindowSize": 3600,
"channelTimeout": 300,
"p2pSequencerAddress": "0x3Dc8Dfd0709C835cAd15a6A27e089FF4cF4C9228",
"optimismL2FeeRecipient": "0x63AA492609175d1824dD668BDadF0042E74b0fC8",
"batchInboxAddress": "0x6F54Ca6F6EdE96662024Ffd61BFd18f3f4e34DFf",
"batchSenderAddress": "0x625726c858dBF78c0125436C943Bf4b4bE9d9033",
"l2GenesisRegolithTimeOffset": "0x0",
"l2OutputOracleSubmissionInterval": 180,
"l2OutputOracleStartingTimestamp": -1,
"l2OutputOracleStartingBlockNumber": "0x0",
"l2GenesisBlockGasLimit": "0x1c9c380",
"fundDevAccounts": false,
"gasPriceOracleOverhead": 188,
"gasPriceOracleScalar": 684000,
"eip1559Denominator": 50,
"eip1559Elasticity": 6,
"optimismBaseFeeRecipient": "0xea4591A6e5a31CF0b822A4f563163CeeBeEe4eb1",
"optimismL1FeeRecipient": "0xdD7aCF916c3E3Fb959CA3bB29beFffcAD2e90be6",
"l2CrossDomainMessengerOwner": "0xA53EF9bBec25fdA4b6Da7EF5617565794369A2A5",
"gasPriceOracleOwner": "0x9c3651E0B3CE47A0b17d775077E3d9B712582be0",
- RELEVANT VALUES
"systemConfigOwner": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542",
"finalSystemOwner": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542",
"superchainConfigGuardian": "0x9BA6e03D8B90dE867373Db8cF1A58d2F7F006b3A",
"portalGuardian": "0xC72aE5c7cc9a332699305E29F68Be66c73b60542",
"l2OutputOracleProposer": "0x48247032092e7b0ecf5dEF611ad89eaf3fC888Dd",
"l2OutputOracleOwner": "0xDA1F62857EA7f10444725c6c435235243D623540",
"proxyAdmin": "0x027860cA56cF779371461C14c3a483c94e1aA8a0",
"proxyAdminOwner": "0xb0cCdbD6fe09D2199171BE19450aF249250518A0",
"l2OutputOracleChallenger": "0xcA4571b1ecBeC86Ea2E660d242c1c29FcB55Dc72",
- add the below default values, if you expect different values for your chain reach out to OP Labs support for clarification before proceeding
"useFaultProofs": true,
"faultGameMaxDepth": 73,
"faultGameSplitDepth": 30,
"faultGameWithdrawalDelay": 604800,
"faultGameMaxClockDuration": 302400,
"faultGameClockExtension": 10800,
- You can update this to the latest absolute prestate hash in the superchain-registry; however, the permissioned Fault Proof System doesn't use this. This comes into play when you upgrade your chain to the permissionless Fault Proof System.
"faultGameAbsolutePrestate": "0x03925193e3e89f87835bbdf3a813f60b2aa818a36bbe71cd5d8fd7e79f52bafe",
- add the below default values, if you expect different values for your chain, reach out to OP Labs support for clarification before proceeding
"faultGameGenesisBlock": 0,
"faultGameGenesisOutputRoot": "0xdead000000000000000000000000000000000000000000000000000000000000",
"respectedGameType": 1,
"preimageOracleMinProposalSize": 126000,
"preimageOracleChallengePeriod": 86400,
"proofMaturityDelaySeconds": 604800,
"disputeGameFinalityDelaySeconds": 302400,
"enableGovernance": false,
"systemConfigStartBlock": 0,
"requiredProtocolVersion": "0x0000000000000000000000000000000000000003000000010000000000000000",
"recommendedProtocolVersion": "0x0000000000000000000000000000000000000003000000010000000000000000",
- make sure to add the below if not present already, these values won't matter but the script needs them to be present in the config
"sequencerFeeVaultWithdrawalNetwork": 0,
"baseFeeVaultWithdrawalNetwork": 0,
"l1FeeVaultWithdrawalNetwork": 0,
"baseFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"sequencerFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000"
}
```
Make sure that important addresses (like the `ProxyAdmin`, `ProxyAdminOwner`, all L1 addresses, `L2OO`) are actually up to date by cross checking the superchain-registry and on-chain (you can do this easily with [`op-fetcher`](https://github.com/ethereum-optimism/optimism/tree/develop/op-fetcher), especially for the `deployments.json`.
## Step-by-step upgrade process
### 1. Create your working directory
Create a working directory:
```shell theme={null}
mkdir upgrade-dir
cd upgrade-dir
```
And then create an outputs directory:
```shell theme={null}
mkdir outputs
```
### 2. Copy config files
Copy your `deployments.json` and `deploy-config.json` into the working directory
### 3. Configure and deploy environment
Create and update an `.env` file with the required information:
```json theme={null}
##############################################
# ↓ Required ↓ #
##############################################
# Can be "mainnet" or "sepolia"
NETWORK=
# Etherscan API key used to verify contract bytecode
ETHERSCAN_API_KEY=
# RPC URL for the L1 network that matches $NETWORK
ETH_RPC_URL=
# Private key used to deploy the new contracts for this upgrade
PRIVATE_KEY=
# Base fee scalar for the SystemConfig
BASE_FEE_SCALAR=
# Blob base fee scalar for the SystemConfig
BLOB_BASE_FEE_SCALAR=
# Check if required files and folders exist
if [ ! -f "./deploy_config.json" ]; then
echo "Error: deploy_config.json not found"
fi
if [ ! -f "./deployments.json" ]; then
echo "Error: deployments.json not found"
fi
if [ ! -d "./outputs" ]; then
echo "Error: outputs folder not found"
fi
```
### 4. Run the deployment process
Run the deployment process with the following command:
```docker theme={null}
docker run -t
--env-file .env
-v ./deploy_config.json:/deploy_config.json
-v ./deployments.json:/deployments.json
-v ./outputs:/outputs
kfoplabs/upgrade-v1.3.0-v1.8.0-permissioned /deploy_config.json /deployments.json
```
The source code for the `kfoplabs/upgrade-v1.3.0-v1.8.0-permissioned` image is available in the [`perm/op-contracts/v1.8.0` branch of the Optimism monorepo](https://github.com/ethereum-optimism/optimism/tree/perm/op-contracts/v1.8.0). If you need to fork or customize the upgrade script (e.g. to support L3s), start from that branch.
### 5. Verify outputs
The deployment should output four files:
* `deploy.log` is a log of the deployment process
* `deployments.json` includes the newly deployed contract addresses
* `bundle.json` is the safe transaction bundle
* `transactions.json` is the summary of the executed deployment transactions
* `standard-addresses.json` is the addresses of `SystemConfigImpl`, `OptimismPortal2Impl`, `L1CrossDomainMessengerImpl`, `L1StandardBridgeImpl`, `L1ERC721BridgeImpl`, and `OptimismMintableERC20FactoryImpl`
* `validation.txt` is used for Tenderly state diff validation. Some info needed for the superchain-ops task might be missing from this file, and will instead be generated during the Tenderly simulation.
### 6. Verify outputs
Now you have the calldata that can be executed onchain to perform the L1 contract upgrade. You should simulate this upgrade and make sure the changes are expected. You can reference the validation files of previously executed upgrade tasks in the [superchain-ops repo](https://github.com/ethereum-optimism/superchain-ops/blob/main/tasks/eth/022-holocene-fp-upgrade/NestedSignFromJson.s.sol) to see what the expected changes are. Once you're confident the state changes are expected, you can sign and execute the upgrade.
## Additional Resources
* [superchain-ops Repository](https://github.com/ethereum-optimism/superchain-ops)
* [Optimism Monorepo](https://github.com/ethereum-optimism/optimism)
* [Upgrade script source code (`perm/op-contracts/v1.8.0` branch)](https://github.com/ethereum-optimism/optimism/tree/perm/op-contracts/v1.8.0)
# Merging Two Chains Into a Shared Dispute Game
Source: https://docs.optimism.io/chain-operators/tutorials/merge-shared-dispute-game
Merge two pre-interop OP Stack chains into a shared DisputeGameFactory and AnchorStateRegistry with opcm.migrate, then cut over op-proposer, op-challenger, and op-dispute-mon to the shared factory.
# Merging Two Chains Into a Shared Dispute Game
This guide depends on the interop feature, which is still in development. Do not follow it on production chains.
This runbook walks you through merging two existing OP Stack chains into a shared dispute game by calling `OPContractsManagerMigrator.migrate` (`opcm.migrate`). After migration, both chains share a newly deployed `DisputeGameFactory`, `AnchorStateRegistry`, and `ETHLockbox`. New proposals are super-root claims defended on the shared factory; the per-chain dispute game factories are emptied of implementations and exist only to let still-in-flight games resolve so their bonds can be reclaimed.
By the end you will have one `op-proposer` proposing super-root claims to the shared factory against an `op-supernode` that derives both chains, one `op-challenger` defending super-root games on the shared factory, one `op-dispute-mon` watching it, and per-chain `op-challenger` and `op-dispute-mon` "drain" instances — each using that shared op-supernode's `//` endpoint — winding down the retired games on the old per-chain factories.
`opcm.migrate` is a one-way operation that invalidates every withdrawal proof submitted but not finalized on either chain. Affected users must re-prove against a new super-root game after migration. Announce the migration window to users at least seven days in advance—long enough for a withdrawal proven just before the announcement to mature past `PROOF_MATURITY_DELAY_SECONDS` and finalize.
If you instead want to swap a single chain's proof method from output roots to super roots without merging chains, follow [Upgrading a Chain From Output Roots to Super Roots](/chain-operators/tutorials/upgrade-chain-to-super-roots). This runbook is for two chains that are merging into a shared interop set.
## Before You Begin
This runbook assumes:
* Exactly two chains, referred to below as `chainA` and `chainB`, are being merged.
* Neither chain has activated interop yet.
* Both chains run permissionless fault proofs with `SUPER_CANNON_KONA` (game type `9`) as the respected game type today.
* The per-chain `op-proposer`, `op-challenger`, and `op-dispute-mon` instances stood up by the [single-chain super-roots upgrade](/chain-operators/tutorials/upgrade-chain-to-super-roots) are running for both chains. The per-chain challengers and monitors keep running through migration cutover and the subsequent drain window.
* A separate `op-supernode` instance is in sync and derives **exactly `chainA` and `chainB`** (and no other chains) in the same process. This is the supernode the new shared `op-proposer`, `op-challenger`, and `op-dispute-mon` point at after migration; it must not double as a multi-chain supernode for any other chain or interop cluster.
* Both chains have already been upgraded to a release that exposes `OPContractsManagerMigrator.migrate` and have `Features.INTEROP` and `Features.ETH_LOCKBOX` enabled on their `SystemConfig`. These features are turned on automatically by `OPContractsManagerV2.upgrade()` when the OPCM container has the `OPTIMISM_PORTAL_INTEROP` dev feature set; that pre-upgrade is a prerequisite for this runbook.
### Install Required Tooling
The versions below are the ones the optimism repo's `mise.toml` pins; older versions of `cast` in particular may not parse the function-selector syntax used in this runbook.
| Tool | Minimum version |
| --------------------------- | --------------- |
| `foundry` (`cast`, `forge`) | `1.2.3` |
| `just` | `1.46.0` |
| `jq` | `1.7.1` |
| `go` | `1.26.5` |
| `curl` | any |
### Gather the Required Inputs
Collect everything below before you start. Replace `` and `` placeholders with the actual values for each chain. Anywhere this runbook says `` it means a value you must capture.
**Per-chain identity, addresses, and roles.** For each chain, open the entry under `superchain/configs//.toml` in `superchain-registry` and read `chain_id`, `addresses.SystemConfigProxy`, and `addresses.L1StandardBridgeProxy`. Pass those two proxies to [`op-fetcher`](https://github.com/ethereum-optimism/optimism/tree/develop/op-fetcher) to resolve the rest in a single L1 call, writing the JSON to a file you can reference later:
```bash theme={null}
op-fetcher fetch \
--l1-rpc-url $L1_RPC \
--system-config \
--l1-standard-bridge \
--output-file .json
```
The portal-derived lookups return the chain's current `DisputeGameFactory`, `AnchorStateRegistry`, and `ETHLockbox` while migration has not yet run; after migration, the same selectors return the new shared addresses, so capture both chains' values now. Capture the following fields from each chain's JSON output:
| Item | Source |
| -------------------------------------------------------- | -------------------------------------------------------------- |
| L2 chain ID | `chain_id` in the chain's registry TOML |
| `SystemConfig` proxy | `addresses.SystemConfigProxy` in the chain's registry TOML |
| `L1StandardBridge` proxy | `addresses.L1StandardBridgeProxy` in the chain's registry TOML |
| `OptimismPortal2` proxy | `addresses.OptimismPortalProxy` in `.json` |
| Existing `AnchorStateRegistry` proxy (``) | `addresses.AnchorStateRegistryProxy` in `.json` |
| Existing `DisputeGameFactory` proxy (``) | `addresses.DisputeGameFactoryProxy` in `.json` |
| Existing `ETHLockbox` proxy | `addresses.EthLockboxProxy` in `.json` |
| `SuperchainConfig` proxy | `addresses.SuperchainConfigProxy` in `.json` |
| `ProxyAdmin` owner Safe | `roles.OpChainProxyAdminOwner` in `.json` |
`migrate` reads each chain's `DelayedWETH` directly from `SystemConfig.delayedWETH()` at execution time, so there is nothing to capture for it here.
**Init bonds.** The migration registers two super game types on the new shared factory. `SUPER_PERMISSIONED` (game type `5`) is bondless and must use an `initBond` of `0`. `SUPER_CANNON_KONA` (game type `9`) uses `0.08 ether` (`80000000000000000` wei).
**Release artifacts and infrastructure.**
| Item | Source |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPCM` address for the migrate-feature container | The same OPCM the chains ran for the prerequisite `OPCMv2.upgrade()`. The container has `OPTIMISM_PORTAL_INTEROP` set in `devFeatureBitmap`, which is what enables both `Features.INTEROP` and `Features.ETH_LOCKBOX` on each `SystemConfig`. |
| `SUPER_CANNON_KONA` absolute prestate hash | `validation/standard/standard-prestates.toml` in `superchain-registry`. Use the entry with `type="interop"` for the OPCM release version. |
| Prestate artifact URL | The `cannon-kona-prestates-url` server, with the kona-interop prestate uploaded ahead of the migration. |
| Shared `op-supernode` RPC URL | Your infrastructure team. The instance must derive **exactly `chainA` and `chainB`** (and no other chains). The shared `op-proposer`, `op-challenger`, and `op-dispute-mon` for the merged factory all point at this same instance. The per-chain services will be pointed to chain-specific endpoints under this URL. |
| Privileged `proposer` for `SUPER_PERMISSIONED` | The address authorized to post proposals to the permissioned super game. `op-proposer` is configured to sign with this key. |
### Verify the Preconditions
Run every check below. Each one corresponds to an invariant the migrator enforces; resolve any failure before proceeding.
* **Confirm the same `ProxyAdmin` owner across both chains.** `migrate` reverts with `OPContractsManagerMigrator_ProxyAdminOwnerMismatch` otherwise. Each chain's `ProxyAdmin` proxy may differ as long as the owner Safe is identical on both.
```bash theme={null}
diff <(jq -r '.roles.OpChainProxyAdminOwner' .json) <(jq -r '.roles.OpChainProxyAdminOwner' .json)
# Expect: no output (identical).
```
* **Confirm the same `SuperchainConfig` across both chains.** `migrate` reverts with `OPContractsManagerMigrator_SuperchainConfigMismatch` otherwise.
```bash theme={null}
diff <(jq -r '.addresses.SuperchainConfigProxy' .json) <(jq -r '.addresses.SuperchainConfigProxy' .json)
# Expect: no output (identical).
```
* **Confirm both chains have `Features.INTEROP` and `Features.ETH_LOCKBOX` enabled.** `_migratePortal` reverts with `OPContractsManagerMigrator_InteropFeatureNotEnabled` or `OPContractsManagerMigrator_EthLockboxFeatureNotEnabled` if either is missing. These flip automatically during the prerequisite `OPCMv2.upgrade()` against an `OPTIMISM_PORTAL_INTEROP`-enabled OPCM container; if any of the four checks below returns `false`, run that prerequisite upgrade first. `Features` constants are short-string `bytes32` values, so feed them through `cast format-bytes32-string`.
```bash theme={null}
for SC in ; do
for F in INTEROP ETH_LOCKBOX; do
echo "$SC $F = $(cast call $SC 'isFeatureEnabled(bytes32)(bool)' "$(cast format-bytes32-string $F)" --rpc-url $L1_RPC)"
done
done
# Expect: every line ends in true.
```
* **Confirm neither chain runs in Custom Gas Token mode.** `_migratePortal` reverts with `OPContractsManagerMigrator_CustomGasTokenNotSupported` for any CGT chain.
```bash theme={null}
cast call 'isCustomGasToken()(bool)' --rpc-url $L1_RPC
cast call 'isCustomGasToken()(bool)' --rpc-url $L1_RPC
# Expect: both false.
```
* **Confirm the current respected game type on both chains is `SUPER_CANNON_KONA` (`9`).**
```bash theme={null}
cast call 'respectedGameType()(uint32)' --rpc-url $L1_RPC
cast call 'respectedGameType()(uint32)' --rpc-url $L1_RPC
# Expect: 9 for both.
```
* **Confirm the shared supernode derives exactly the two merging chains.** Verify with your infrastructure team that the instance behind `$SUPERNODE_RPC` has `chainA` and `chainB` in its dependency set and **no** other chains. A supernode that also tracks an unrelated chain (or an interop cluster that includes one) computes super roots over that broader set, and those roots will not match what the shared `SUPER_CANNON_KONA` and `SUPER_PERMISSIONED` games verify.
* **Confirm the shared supernode is in sync.** It must report a finalized L2 head within both chains' expected finality windows:
```bash theme={null}
cast rpc supernode_syncStatus --rpc-url $SUPERNODE_RPC | jq -r '.finalized_timestamp'
```
If the supernode is materially behind either chain, postpone the migration.
* **Confirm the prestate server serves the `SUPER_CANNON_KONA` prestate.**
```bash theme={null}
curl -fI "$PRESTATE_URL/$SUPER_CANNON_KONA_PRESTATE.bin.gz"
```
* **Note the chain B `DelayedWETH` divergence.** `migrate` reuses chain A's `DelayedWETH` for the new shared super games and does not touch chain B's. After migration, chain B's `SystemConfig.delayedWETH()` still references chain B's old `DelayedWETH`, which is no longer attached to any super game. Future `OPCMv2.upgrade()` calls against chain B that read `SystemConfig.delayedWETH()` will therefore reference a different contract than the shared games use. Track this in your operational runbook so future upgrades do not surprise you. The bonds posted by chain B's still-in-flight pre-migration games continue to claim out of chain B's old `DelayedWETH`, so the divergence does not block the drain described in [Drain Pre-Migration Games and Reclaim Bonds](#drain-pre-migration-games-and-reclaim-bonds).
## Stage the Off-Chain Configuration
Apply these changes before you submit the on-chain migration. The pre-migration `op-challenger` and `op-dispute-mon` instances per chain must keep running to defend and watch in-flight games on the per-chain factories until those games resolve. Add a third instance of each component for the shared factory. Stop both per-chain `op-proposer` instances at cutover time, in [Execute the Migration](#execute-the-migration).
### Point Per-Chain Services at the Shared op-supernode
Before migration, point every existing per-chain service at its chain's RPC namespace on the shared op-supernode:
| Service flag | chainA value | chainB value |
| -------------------------------- | --------------------------------- | --------------------------------- |
| `op-proposer --superroot-rpcs` | `$SUPERNODE_RPC/$CHAIN_A_CHAINID` | `$SUPERNODE_RPC/$CHAIN_B_CHAINID` |
| `op-challenger --supernode-rpc` | `$SUPERNODE_RPC/$CHAIN_A_CHAINID` | `$SUPERNODE_RPC/$CHAIN_B_CHAINID` |
| `op-dispute-mon --supernode-rpc` | `$SUPERNODE_RPC/$CHAIN_A_CHAINID` | `$SUPERNODE_RPC/$CHAIN_B_CHAINID` |
Do not point these per-chain instances at `$SUPERNODE_RPC` without the chain ID suffix; the root endpoint returns a two-chain super root. Restart each service and confirm it connects successfully before continuing. This removes the operational dependency on separate single-chain op-node or op-supernode processes before interop activation.
### Stand Up a Shared op-challenger
A single `op-challenger` process can only watch one `DisputeGameFactory`, so the existing per-chain instances cannot also cover the new shared factory. Run a third `op-challenger` instance dedicated to the shared factory in addition to the existing per-chain instances.
Configure the new shared instance with the flags below.
| Flag (env var) | Value |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--game-factory-address` (`OP_CHALLENGER_GAME_FACTORY_ADDRESS`) | The new shared `` (capture from chain A's portal after [Execute the Migration](#execute-the-migration)) |
| `--superroot-rpc` (`OP_CHALLENGER_SUPERNODE_RPC`) | `$SUPERNODE_RPC` |
| `--l2-eth-rpc` (`OP_CHALLENGER_L2_ETH_RPC`) | `,` (comma-separated) |
| `--game-types` (`OP_CHALLENGER_GAME_TYPES`) | `super-cannon-kona`. Do not add `super-permissioned`; the simplified `SUPER_PERMISSIONED` game does not require `op-challenger` to post claims. `op-challenger` is still required by permissioned only networks and will automatically update the `AnchorStateRegistry` as games are finalized. |
| `--network` (`OP_CHALLENGER_NETWORK`) | `,` (the registry chain names; the challenger loads the depset from `superchain-registry` the same way `op-supernode` does) |
| `--cannon-kona-prestates-url` (`OP_CHALLENGER_CANNON_KONA_PRESTATES_URL`) | The prestate server URL validated in the preconditions |
If either chain is not in `superchain-registry`, omit `--network` and instead pass each chain's rollup config and L2 genesis as comma-separated paths via `--rollup-config` (`OP_CHALLENGER_ROLLUP_CONFIG`) and `--l2-genesis` (`OP_CHALLENGER_L2_GENESIS`), plus the shared depset via `--depset-config` (`OP_CHALLENGER_DEPSET_CONFIG`).
The shared instance's `--game-factory-address` is the new shared factory, which only exists once `migrate` has been broadcast. Stage the deployment now with every other flag set; the address is filled in and the process is started in [Start the Shared op-challenger and op-dispute-mon](#start-the-shared-op-challenger-and-op-dispute-mon) after the migration is verified on-chain.
Keep the per-chain `op-challenger` instances running with the namespaced RPC configuration for the duration of the drain window.
### Stand Up a Shared op-dispute-mon
`op-dispute-mon` is also single-factory per process. Run a third instance pointed at the new shared factory in addition to the two per-chain instances.
| Flag (env var) | Value |
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `--game-factory-address` (`OP_DISPUTE_MON_GAME_FACTORY_ADDRESS`) | The new shared `` |
| `--superroot-rpc` (`OP_DISPUTE_MON_SUPERNODE_RPC`) | `$SUPERNODE_RPC` or a comma-separated list of high-availability root endpoints for the same dependency set |
| `--l1-eth-rpc` (`OP_DISPUTE_MON_L1_ETH_RPC`) | unchanged |
As with the shared `op-challenger`, stage the deployment now with every flag except `--game-factory-address`; the address is filled in and the process is started in [Start the Shared op-challenger and op-dispute-mon](#start-the-shared-op-challenger-and-op-dispute-mon) after the migration is verified on-chain.
Keep both per-chain `op-dispute-mon` instances running with the namespaced RPC configuration so they continue to track in-flight pre-migration games to resolution.
### Leave Other op-proposer Settings Unchanged for Now
After switching the per-chain `op-proposer` RPC endpoints above, leave their other settings unchanged until [Execute the Migration](#execute-the-migration). Stop both per-chain proposers at cutover and replace them with a single shared one; see [Cut Over to a Single Shared op-proposer](#cut-over-to-a-single-shared-op-proposer).
## Generate the Starting Anchor Super Root
The new shared `AnchorStateRegistry` is initialized with a starting anchor super root. Compute the value from the supernode using `superroot_atTimestamp`. Pick the supernode's current finalized timestamp (or any recent finalized timestamp), then ask the supernode for the super root at that timestamp:
```bash theme={null}
# .finalized_timestamp is a JSON number (decimal unix seconds).
TS=$(cast rpc supernode_syncStatus --rpc-url $SUPERNODE_RPC | jq -r '.finalized_timestamp')
# superroot_atTimestamp expects a hex-encoded JSON string (hexutil.Uint64); cast 2h handles the conversion.
cast rpc superroot_atTimestamp "$(cast 2h $TS)" --rpc-url $SUPERNODE_RPC | jq -r '.data.super_root'
echo "timestamp=$TS"
```
Capture two values:
* `super_root` (a `bytes32` hash from `.data.super_root`)—passed as `Proposal.root` in `startingAnchorRoot`.
* `timestamp` (the uint64 you passed in)—passed as `Proposal.l2SequenceNumber`. For super games this field is the timestamp itself, not a block number.
## Build the superchain-ops Task
Author a new task directory under `superchain-ops/src/tasks///` using the `OPCMMigrateInterop` template, which wraps `OPContractsManagerMigrator.migrate(MigrateInput)` and runs `OPContractsManagerMigrationValidator` over the resulting state.
### Configure config.toml
```toml theme={null}
l2chains = [
{name = "t stat", chainId = },
{name = "", chainId = },
]
templateName = "OPCMMigrateInterop"
[addresses]
OPCM = "0x"
# One stanza per chain, both required.
[[migrateChains]]
chainId =
systemConfigProxy = "0x"
[[migrateChains]]
chainId =
systemConfigProxy = "0x"
# Starting anchor for the new shared AnchorStateRegistry, computed in the previous step.
[startingAnchorRoot]
root = "0x"
l2SequenceNumber =
# 9 = SUPER_CANNON_KONA. Required because both chains run permissionless fault proofs today.
startingRespectedGameType = 9
# Both super game types must be registered. The migration validator requires it.
# The template assembles the on-chain DisputeGameConfig.gameArgs bytes from these
# fields: abi.encode(absolutePrestate) for game type 9, and
# abi.encode(proposer) for game type 5.
[[disputeGameConfigs]]
gameType = 9 # SUPER_CANNON_KONA
enabled = true
initBond = 80000000000000000 # match the value gathered in the inputs
absolutePrestate = "0x"
[[disputeGameConfigs]]
gameType = 5 # SUPER_PERMISSIONED
enabled = true
initBond = 0 # SUPER_PERMISSIONED does not charge init bonds
proposer = "0x"
expectedValidationErrors = "" # fill in after the dry run if any structurally expected codes appear
```
### Capture expectedValidationErrors
Run the task in simulation mode against an L1 fork from inside the task directory:
```bash theme={null}
cd superchain-ops/src/tasks//
just simulate
```
The validator behind `OPContractsManagerMigrationValidator` returns a comma-separated string of error codes when something is structurally wrong. The default expectation is no errors—`expectedValidationErrors = ""` and a clean simulation. The codes you may see fall into the families below. Resolve every code rather than copy-pasting it into `expectedValidationErrors`; a code added to that field must have an inline TOML comment explaining why it is structurally expected for this migration.
| Code | Meaning |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MIG-DGF-10` | `SUPER_PERMISSIONED` is not registered on the shared factory. |
| `MIG-DGF-20` | `SUPER_CANNON_KONA` is not registered on the shared factory. |
| `MIG-DGF-30` | `CANNON` is still registered on the shared factory. |
| `MIG-DGF-40` | `PERMISSIONED_CANNON` is still registered on the shared factory. |
| `MIG-DGF-50` | `CANNON_KONA` is still registered on the shared factory. |
| `MIG-SDGF-10` to `-40` | Shared factory version, proxy implementation pointer, owner, or `ProxyAdmin` does not match the expected configuration. |
| `MIG-SASR-RGT` | Shared `AnchorStateRegistry.respectedGameType()` is not a super game type. |
| `MIG-SLOCKBOX-10` to `-30` | Shared lockbox version, proxy implementation pointer, or `ProxyAdmin` mismatch. |
| `MIG-CHAIN-EMPTY` | The validator received an empty chain list. |
| `MIG-LOCKBOX-MISSING` | Chain A's portal does not point at the shared lockbox. |
| `MIG-CHAIN-{i}-10` | Chain `i`'s portal does not reference the shared `AnchorStateRegistry`. |
| `MIG-CHAIN-{i}-20` | Chain `i`'s per-chain factory still has `CANNON` registered. |
| `MIG-CHAIN-{i}-30` | Chain `i`'s per-chain factory still has `PERMISSIONED_CANNON` registered. |
| `MIG-CHAIN-{i}-40` | Chain `i`'s per-chain factory still has `CANNON_KONA` registered. |
| `MIG-CHAIN-{i}-60` | Chain `i`'s per-chain factory still has `SUPER_PERMISSIONED` registered. |
| `MIG-CHAIN-{i}-70` | Chain `i`'s per-chain factory still has `SUPER_CANNON_KONA` registered. |
| `MIG-CHAIN-{i}-80` | Shared lockbox does not list chain `i`'s portal as authorized. |
| `MIG-CHAIN-{i}-90` | Chain `i`'s portal `ethLockbox` does not equal the shared lockbox. |
| `MIG-CHAIN-{i}-100` | Chain `i`'s `SystemConfig` does not have `Features.INTEROP` enabled. |
| `MIG-CHAIN-{i}-110` | Chain `i`'s `SystemConfig` does not have `Features.ETH_LOCKBOX` enabled. |
| `MIG-SPDG-*` or `MIG-SCKDG-*` | Super game drill-down. The first prefix covers `SUPER_PERMISSIONED` and the second covers `SUPER_CANNON_KONA`. `MIG-SPDG-*` subcodes cover the simplified permissioned game's args, proposer, and `AnchorStateRegistry`; `MIG-SCKDG-*` covers the fault-proof game checks. |
## Execute the Migration
Verify that the user announcement has gone out and at least seven days have elapsed since the announcement before broadcasting. Withdrawals proven against either chain's old games will not finalize against the new shared registry; users must re-prove or finalize ahead of cutover.
1. Stop both per-chain `op-proposer` processes. Once the migration broadcasts, the per-chain factories no longer have `SUPER_CANNON_KONA` registered, so `DisputeGameFactory.create` reverts. Stopping the proposers first prevents wasted gas.
2. Leave both per-chain `op-challenger` and `op-dispute-mon` instances running. They continue to defend and observe in-flight pre-migration games on the per-chain factories.
3. Sign and broadcast the task using your team's standard `superchain-ops` signing workflow.
4. Wait for the L1 transaction to confirm.
The on-chain effect:
* `OPCM` deploys and initializes new `ETHLockbox`, `DisputeGameFactory`, and `AnchorStateRegistry` proxies. The new `AnchorStateRegistry` binds to the new factory and sets `retirementTimestamp` to `block.timestamp` at initialization.
* `OPCM` calls `OptimismPortal2.migrateToSharedDisputeGame` on each chain to repoint the portal at the new lockbox and the new `AnchorStateRegistry`. It also authorizes each chain's existing portal on the shared lockbox, authorizes each chain's existing lockbox to forward liquidity, and migrates that liquidity into the shared lockbox.
* `OPCM` zeroes every implementation pointer on each chain's existing per-chain `DisputeGameFactory`. Already-deployed game proxies on those factories are unaffected and continue to resolve.
Capture the new shared addresses by re-running op-fetcher against each chain. After migration, `optimismPortal().anchorStateRegistry()`, `disputeGameFactory()`, and `ethLockbox()` all return the new shared proxies, so the same op-fetcher inputs you used in [Gather the Required Inputs](#gather-the-required-inputs) now resolve the post-migration addresses. Write the post-migration output to new files (do not overwrite the pre-migration ones — you still need them to reference the per-chain `` and `` during the drain).
```bash theme={null}
op-fetcher fetch \
--l1-rpc-url $L1_RPC \
--system-config \
--l1-standard-bridge \
--output-file -postmigrate.json
op-fetcher fetch \
--l1-rpc-url $L1_RPC \
--system-config \
--l1-standard-bridge \
--output-file -postmigrate.json
```
Capture the shared addresses from chain A's post-migration output:
```bash theme={null}
SHARED_ASR=$(jq -r '.addresses.AnchorStateRegistryProxy' -postmigrate.json)
SHARED_DGF=$(jq -r '.addresses.DisputeGameFactoryProxy' -postmigrate.json)
SHARED_LOCKBOX=$(jq -r '.addresses.EthLockboxProxy' -postmigrate.json)
```
Confirm chain B's portal resolves to the same shared proxies:
```bash theme={null}
diff <(jq -r '.addresses.AnchorStateRegistryProxy, .addresses.DisputeGameFactoryProxy, .addresses.EthLockboxProxy' -postmigrate.json) \
<(jq -r '.addresses.AnchorStateRegistryProxy, .addresses.DisputeGameFactoryProxy, .addresses.EthLockboxProxy' -postmigrate.json)
# Expect: no output (identical).
```
## Verify the Migration On-Chain
Run every check below before you start the new `op-proposer`. If any check fails, stop and escalate via your standard incident-response channel; recovery from a half-cut state with a misbehaving proposer is materially harder than recovery from one that is paused.
### Verify the Shared Registry and Factory
```bash theme={null}
# Respected game type on the new ASR
cast call $SHARED_ASR 'respectedGameType()(uint32)' --rpc-url $L1_RPC
# Expect: 9 (SUPER_CANNON_KONA)
# Anchor root and timestamp on the new ASR
cast call $SHARED_ASR 'getAnchorRoot()(bytes32,uint256)' --rpc-url $L1_RPC
# Expect: (, ) from "Generate the Starting Anchor Super Root"
# Retirement timestamp on the new ASR
cast call $SHARED_ASR 'retirementTimestamp()(uint64)' --rpc-url $L1_RPC
# Expect: equal to the migration block's timestamp
# Game implementations registered on the shared factory
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 5 --rpc-url $L1_RPC # SUPER_PERMISSIONED
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 9 --rpc-url $L1_RPC # SUPER_CANNON_KONA
# Expect both: non-zero
# Init bonds match each game type's configuration
cast call $SHARED_DGF 'initBonds(uint32)(uint256)' 5 --rpc-url $L1_RPC
# Expect: 0
cast call $SHARED_DGF 'initBonds(uint32)(uint256)' 9 --rpc-url $L1_RPC
# Expect: 80000000000000000
# No legacy implementations registered on the shared factory
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 0 --rpc-url $L1_RPC # CANNON
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 1 --rpc-url $L1_RPC # PERMISSIONED_CANNON
cast call $SHARED_DGF 'gameImpls(uint32)(address)' 8 --rpc-url $L1_RPC # CANNON_KONA
# Expect each: 0x0000000000000000000000000000000000000000
```
### Confirm Per-Chain Factories Are Cleared
```bash theme={null}
for OLD_DGF in ; do
for GT in 0 1 5 8 9; do
echo "$OLD_DGF gameImpls($GT)= $(cast call $OLD_DGF 'gameImpls(uint32)(address)' $GT --rpc-url $L1_RPC)"
done
done
# Expect every entry: 0x0000000000000000000000000000000000000000
```
### Confirm the Shared Lockbox Is Authorized
```bash theme={null}
cast call $SHARED_LOCKBOX 'authorizedPortals(address)(bool)' --rpc-url $L1_RPC
cast call $SHARED_LOCKBOX 'authorizedPortals(address)(bool)' --rpc-url $L1_RPC
# Expect both: true
```
## Start the Shared op-challenger and op-dispute-mon
Both shared instances were staged in [Stage the Off-Chain Configuration](#stage-the-off-chain-configuration) with every flag except `--game-factory-address` set. Now that `$SHARED_DGF` is known and the migration is verified on-chain, fill in the address on both and start the processes as soon as practical.
`SUPER_CANNON_KONA` is a permissionless game type, so the moment the shared factory is live anyone can create a game against it — not just `op-proposer`. Once a game is created, its clock runs and it resolves whether or not anyone challenges. `op-challenger` must be running to defend invalid claims before their game clocks expire, and `op-dispute-mon` must be running to observe and alert. This is independent of [Cut Over to a Single Shared op-proposer](#cut-over-to-a-single-shared-op-proposer) below — complete it first regardless of when the proposer cutover happens.
1. **Set the shared `op-challenger`'s `--game-factory-address` (`OP_CHALLENGER_GAME_FACTORY_ADDRESS`) to `$SHARED_DGF` and start the process.** Confirm the startup log lists `super-cannon-kona` as a registered trace type and shows no errors connecting to the shared supernode root RPC endpoint.
2. **Set the shared `op-dispute-mon`'s `--game-factory-address` (`OP_DISPUTE_MON_GAME_FACTORY_ADDRESS`) to `$SHARED_DGF` and start the process.** Confirm the supernode root connection is healthy and the metrics endpoint is serving.
Healthy startup logs and a quiet error stream are the bar for proceeding. Both instances discover existing games on startup, so it does not matter whether a super-root game already exists in the shared factory by the time they come up.
## Cut Over to a Single Shared op-proposer
Start one new `op-proposer` process for the shared factory. A single proposer replaces both per-chain ones because the supernode produces one super root per timestamp covering both chains.
Configure the new instance with the flags below.
| Flag (env var) | Value |
| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `--game-factory-address` (`OP_PROPOSER_GAME_FACTORY_ADDRESS`) | the new shared `` |
| `--superroot-rpcs` (`OP_PROPOSER_SUPERROOT_RPCS`) | `$SUPERNODE_RPC` or high-availability root endpoints for the same dependency set |
| `--game-type` (`OP_PROPOSER_GAME_TYPE`) | `9` (SUPER\_CANNON\_KONA) |
| `--l1-eth-rpc` (`OP_PROPOSER_L1_ETH_RPC`) | as for the existing proposers |
| `--proposal-interval`, `--poll-interval`, `--mnemonic` or `--private-key` | as for the existing proposers |
After start-up, verify:
* The logs show the proposer polling the supernode and writing to the shared factory.
* Within one `--proposal-interval`, the proposer submits a new game on the shared factory. Inspect it:
```bash theme={null}
COUNT=$(cast call $SHARED_DGF 'gameCount()(uint256)' --rpc-url $L1_RPC)
echo "gameCount=$COUNT"
read -r GAME_TYPE CREATED_AT GAME_PROXY < <(cast call $SHARED_DGF 'gameAtIndex(uint256)(uint32,uint64,address)' $((COUNT-1)) --rpc-url $L1_RPC)
# Expect: GAME_TYPE=9 and GAME_PROXY non-zero.
echo "gameType=$GAME_TYPE createdAt=$CREATED_AT gameProxy=$GAME_PROXY"
cast call $GAME_PROXY 'rootClaim()(bytes32)' --rpc-url $L1_RPC
cast call $GAME_PROXY 'l2SequenceNumber()(uint256)' --rpc-url $L1_RPC
```
* Recompute the super root for the proposed timestamp and confirm it matches the proposal:
```bash theme={null}
cast rpc superroot_atTimestamp "$(cast 2h )" \
--rpc-url $SUPERNODE_RPC | jq -r '.data.super_root'
# Expect: equals rootClaim from above.
```
Once the proposer is stable, do not stop the per-chain `op-challenger` or `op-dispute-mon` instances—they are still needed for the drain window described next.
## Drain Pre-Migration Games and Reclaim Bonds
The migration cleared the per-chain factories' implementation pointers so no new pre-migration games can be created, but every game proxy that already existed on those factories at migration time continues to function. Each game's implementation, `AnchorStateRegistry`, and `DelayedWETH` references were captured as immutable arguments on the proxy when it was created, so the cleared factory pointer does not affect resolution. Bond claims through `FaultDisputeGame.claimCredit` flow into the same `DelayedWETH` the bonds were posted into, independent of the portal or the new shared factory.
Concretely:
* The two per-chain `op-challenger` instances continue to play retired games to `DEFENDER_WINS` or `CHALLENGER_WINS` and schedule bond claims as games finalize.
* Bond claims succeed without operator intervention. Chain A's retired games claim through chain A's `DelayedWETH` (which is also the one used by the new shared games); chain B's retired games claim through chain B's `DelayedWETH`, which is otherwise unused after migration.
* The two per-chain `op-dispute-mon` instances keep observing those games. The per-game-type metric `op_dispute_mon_games{game_type="super-cannon-kona"}` and the equivalent for `super-permissioned` should monotonically decrease toward zero across the drain window.
* `op-challenger`'s default `--game-window` is 28 days, sized to cover up to 16 days of game play plus a seven-day `DelayedWETH` withdrawal delay plus a five-day buffer. Do not shrink it on the per-chain instances; doing so risks dropping games before their bonds are claimable.
Pick a still-in-flight pre-migration game and watch it through to resolution and bond claim:
```bash theme={null}
cast call 'status()(uint8)' --rpc-url $L1_RPC
# 0 = IN_PROGRESS, 1 = CHALLENGER_WINS, 2 = DEFENDER_WINS
cast call 'resolvedAt()(uint64)' --rpc-url $L1_RPC
# Non-zero once the game has resolved.
```
Once a game is resolved and the `DelayedWETH` finality window has elapsed, the challenger schedules `claimCredit`. Confirm bonds reach the configured claimant by watching the `op_challenger_bonds_total` metric on the per-chain instance, alongside `op_challenger_claim_failures_total` staying flat.
## Verify After the Migration
Run these checks once the new proposer is stable.
* **Confirm the shared `op-challenger` is defending super-root games.** The challenger does not break metrics down by game type, so verify by way of the startup log (every configured trace type, including `super-cannon-kona` and `super-permissioned`, must be listed as registered) and by ongoing logs (no scheduler or game-poll errors as super games appear on the shared factory). The aggregate `op_challenger_tracked_games{status="in_progress"}` becomes non-zero as super games are created and remains non-zero while games are in progress.
* **Confirm the shared `op-dispute-mon` reports super-root games.** On the shared instance, `op_dispute_mon_games{game_type="super-cannon-kona"}` and `op_dispute_mon_games{game_type="super-permissioned"}` are non-zero once games are created. `op_dispute_mon_failed_games` stays near zero; a non-zero value typically points at missing `--supernode-rpc` or supernode unavailability.
* **Confirm new pre-migration games cannot be created.** Optionally call `DisputeGameFactory.create` with one of the cleared game types on either per-chain factory and confirm it reverts.
* **Watch for the first super-game finalization.** The first anchor update typically lands roughly seven days after the first super-root game is created (game duration plus `DISPUTE_GAME_FINALITY_DELAY_SECONDS`). Do not block the rollout on this—schedule a follow-up to re-run `getAnchorRoot()` after that window and confirm it returns the new game's claim instead of the starting anchor.
## Tear Down After the Drain
A per-chain instance is safe to retire when its `op_dispute_mon_games{game_type="super-cannon-kona"}` and `op_dispute_mon_games{game_type="super-permissioned"}` have been zero for at least one full `--game-window` (default 28 days) and `op_challenger_tracked_games{status="in_progress"}` on the matching per-chain `op-challenger` is zero with bond-claim metrics stable.
For each chain when both conditions hold, stop the per-chain `op-challenger` and `op-dispute-mon` instances.
Once both chains are drained, the steady-state operational footprint is one `op-proposer`, one `op-challenger`, and one `op-dispute-mon`, all pointed at the shared factory and a single supernode that derives both chains.
## Next Steps
* Plan the full interop activation milestone—`migrate` joins the chains under a shared dispute game, but turning on cross-chain messaging is a separate step. See [Interop Explainer](/op-stack/interop/explainer) when you are ready.
* Schedule a follow-up to verify the anchor advances on the first super-game finalization, roughly seven days after the new proposer's first proposal. Re-run `cast call $SHARED_ASR 'getAnchorRoot()(bytes32,uint256)' --rpc-url $L1_RPC` and confirm it returns the new game's claim.
# Migrating to permissionless fault proofs on OP Stack
Source: https://docs.optimism.io/chain-operators/tutorials/migrating-permissionless
Migrate your OP Stack chain from permissioned to permissionless fault proofs: configure the dispute components, deploy the contracts with OPCM, test the off-chain agents, and switch the respected game type.
This guide shows you how to transition your OP Stack chain from permissioned to permissionless fault proofs.
It walks chain operators through the four migration phases: configuring the dispute components, deploying the smart contracts with OPCM, testing the off-chain agents, and switching the respected game type.
## Overview
The OP Stack architecture uses Fault Proofs to ensure the validity of withdrawals from L2 to L1.
Transitioning from permissioned to permissionless proofs represents a significant security upgrade, allowing any participant to propose and challenge state output roots.
Permissioned games previously relied on a single trusted validator, this is typically the proposer which is configured in the PermissionedDisputeGame, and is usually the network's only sequencer.
This migration involves several key components:
* Configuring security-critical dispute [monitoring services](/chain-operators/tools/chain-monitoring)
* Deploying and configuring smart contracts using [op-deployer](/chain-operators/tools/op-deployer/overview)
* Testing the new system before activation
* Setting the respected game type to permissionless fault proofs, specifically using the [`FaultDisputeGame`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol)
## Prerequisites
Before beginning this transition, your chain should:
* Be running a standard OP Stack implementation
* It's recommended to use the latest contracts version, minimum required [**v5.0.0**](https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv5.0.0).
* Be operating with the required infrastructure services including [`op-challenger`](/op-stack/fault-proofs/challenger) and [`op-dispute-mon`](/chain-operators/tools/chain-monitoring#dispute-mon).
## Migration steps
The process of migrating from permissioned to permissionless fault proofs involves four main phases: configuring off-chain dispute components, deploying the necessary smart contracts, testing the system thoroughly, and finally switching the chain to use permissionless proofs. Each step builds on the previous one to ensure a smooth and secure transition.
Let's begin with configuring the dispute components that will interact with the new permissionless game.
## 1. Configure the dispute components
The `op-challenger` and `op-dispute-mon` services are critical security components that participate in the dispute game process to challenge invalid proposals and monitor active games.
### Upgrade to the latest `op-challenger`
Upgrade to the [latest release](https://github.com/ethereum-optimism/optimism/releases), which contains important improvements to simplify the upgrade process.
You can use the official Docker image for reliability and ease of deployment:
```bash theme={null}
# Pull a specific stable release version
docker pull us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:
```
Then run the image, for example:
```bash theme={null}
# Run with all required flags
docker run -d --name op-challenger \
-e OP_CHALLENGER_GAME_TYPES=permissioned,cannon-kona \
-e OP_CHALLENGER_PRESTATES_URL= \
-e OP_CHALLENGER_L1_ETH_RPC= \
-e OP_CHALLENGER_GAME_FACTORY_ADDRESS= \
-e OP_CHALLENGER_PRIVATE_KEY= \
-e OP_CHALLENGER_NETWORK= \
-v /path/to/local/prestates:/prestates \
us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:latest
# Replace all placeholder values with your actual configuration
```
Replace `` with your actual prestates URL.
If your deployment requires building from source, you can alternatively use:
```bash theme={null}
git clone https://github.com/ethereum-optimism/optimism -b op-challenger/ --recurse-submodules
cd optimism
make op-challenger
```
### Update network configuration
Configure `op-challenger` to load your chain configuration.
Even if your chain is not included in the [superchain-registry](/op-stack/protocol/superchain-registry), you can specify a custom configuration:
```bash theme={null}
# For chains in the registry
--network
# For chains not in the registry, provide a path to your rollup configuration
- `rollup.json` - Your rollup configuration
- `genesis-l2.json` - Your L2 genesis file
```
### Enable the cannon-kona game type
From the Karst upgrade, `cannon-kona` (the kona-client program run inside the Cannon VM) is the respected permissionless game type, replacing op-program. Configure `op-challenger` to support both permissioned and permissionless games by setting:
```bash theme={null}
--game-types permissioned,cannon-kona
```
Or by setting the environment variable:
```bash theme={null}
OP_CHALLENGER_GAME_TYPES=permissioned,cannon-kona
```
### Configure prestates access
Replace the `--cannon-kona-prestate` flag with `--prestates-url`, which points to a source containing all required prestates:
```bash theme={null}
--prestates-url
```
The URL can use `http`, `https`, or `file` protocols. Each prestate should be named as `.bin.gz`.
### Building required prestates for chains not in the superchain-registry
You'll need a new absolute prestate to register with the `FaultDisputeGame` and `PermissionedDisputeGame` (see [Section 2](#2-deploy-and-configure-smart-contracts-using-opcm)).
The initial prestate used for permissioned games doesn't include the necessary chain configuration for the Fault Proof System. The assumption is that the chain operator, the single permissioned actor, will not challenge their own games. So the absolute prestate on the initial `PermissionedDisputeGame` will never be used.
When deploying a new chain, you must first deploy the L1 contracts and then generate the chain genesis file and rollup configuration files.
These are inputs to the creation of the absolute prestate and this circular dependency is the reason chains cannot be deployed directly to the permissionless Fault Proof System.
For chains not in the superchain-registry, you need to build custom prestates with your chain's configuration:
Build the kona-client absolute prestate with `just reproducible-prestate-kona` (see the [absolute prestate tutorial](/chain-operators/tutorials/absolute-prestate)). The artifacts are written to:
* `rust/kona/prestate-artifacts-cannon/prestate-proof.json` — the proof file; its `pre` field is the absolute prestate hash (`jq -r .pre ...`)
* `rust/kona/prestate-artifacts-cannon/.bin.gz` — the preimage file `op-challenger` loads
Register the kona-client absolute prestate with your `FaultDisputeGame` and `PermissionedDisputeGame`. The challenger refuses to interact with games whose on-chain absolute prestate doesn't match the one it has, so the hash must match exactly.
### Ensure sufficient funds for bonds
Bonds are required for both permissioned and permissionless games.
However, with permissioned games, you typically don't post claims regularly, making bond requirements less noticeable.
In contrast, the challenger in permissionless games will frequently need to post bonds with each claim it makes.
Therefore, ensure your challenger has sufficient funds available.
As a general guideline:
* Maintain a minimum balance of 50 ETH
* Have access to a large pool of ETH for potential attack scenarios
* Implement monitoring to ensure sufficient funds are always available
### Ensure there is disk space for the challenger to use
`op-challenger`, particularly in permissionless mode, should have access to disk. About 50GB is enough to cover a couple of `invalid_ games`. Though the storage requirements will increase if the challenger needs to respond to more invalid dispute games.
Nominally, the challenger does not use disk space as long as there aren’t any invalid proposals being made.
One way to gain more confidence that the op-challenger was configured correctly is to operate the op-challenger in “runner” mode.
Using the same op-challenger configuration, invoke the `op-challenger run-trace --run cannon-kona` subcommand.
This will run op-challenger in a mode where it runs the kona-client program in a Cannon VM on live L2 blocks to ensure those were configured correctly.
This command runs forever, checking every couple of L2 blocks. But it suffices to let it run until it has completed one loop and kill it.
Wait you see `Successfully verified output root` in the logs before shutting it down.
Any errors indicate a misconfiguration.
### Set up `op-dispute-mon`
Ensure `op-dispute-mon` is properly configured by following the [steps](/chain-operators/tools/chain-monitoring#dispute-mon) in the documentation.
## 2. Deploy and configure smart contracts using OPCM
This section requires privileged actions by the `ProxyAdminOwner` and the `Guardian` role.
### Understanding ProxyAdmin Owner and Guardian roles
This migration requires actions by privileged roles in your system:
* The **ProxyAdmin Owner** has the authority to upgrade proxy contracts.
* The **Guardian** has emergency powers like pausing withdrawals and changing the respected game type.
For detailed information about privileged roles and their security implications, refer to the [privileged roles documentation](/op-stack/protocol/privileged-roles).
### Adding the PermissionlessDisputeGame to a chain
To enable the permissionless dispute game, call the `upgrade()` function on the `OPContractsManagerV2` (OPCM) contract with the appropriate `DisputeGameConfig` entries. This is typically done via `op-deployer manage add-game-type-v2`.
The upgrade will:
1. Register the `FaultDisputeGame` implementation on the `DisputeGameFactory` with the correct game arguments (absolute prestate, VM, anchor state registry, delayed WETH, chain ID).
2. Set the initial bond for the new game type.
See a high‐level implementation from this [docs](/chain-operators/tutorials/dispute-games) or [this superchain-ops template](https://github.com/ethereum-optimism/superchain-ops/blob/main/src/template/AddGameTypeTemplate.sol).
## 3. Testing off-chain agents
After you've set the permissionless `FaultDisputeContract` implementations on the `DisputeGameFactory` and before you set the respected game type to it (game type 0), you can test `op-challenger` and `op-dispute-mon` to ensure they are working correctly with permissionless games.
There are a number of useful `op-challenger` subcommands that can be used for testing, particularly `list-games`, `list-claims` and `create-game`. See the [README](https://github.com/ethereum-optimism/optimism/tree/develop/op-challenger#subcommands) and `op-challenger --help` output for further details. The two tests below are basic sanity tests:
### Test defending valid proposals
Create a valid proposal using the permissionless game type `0`:
1. Ensure the proposal is from a block at or before the `safe` head:
```bash theme={null}
cast block --rpc-url safe
```
2. Get a valid output root (from op-node):
```bash theme={null}
cast rpc --rpc-url optimism_outputAtBlock \
$(cast 2h ) | jq -r .outputRoot
```
3. Create a test game:
```bash theme={null}
./op-challenger/bin/op-challenger create-game \
--l1-eth-rpc= \
--game-factory-address \
--l2-block-num \
--output-root \
```
4. Verify:
* `op-challenger` logs a message showing the game is in progress
* `op-challenger` doesn't post a counter claim (as this is a valid proposal)
* `dispute-mon` includes the new game with `status="agree_defender_ahead"`
### Test countering invalid claims
Post an invalid counter claim to the valid proposal created above:
```bash theme={null}
./op-challenger/bin/op-challenger move \
--l1-eth-rpc \
--game-address \
--attack \
--parent-index 0 \
--claim 0x0000000000000000000000000000000000000000000000000000000000000000 \
```
Verify that `op-challenger` posts a counter-claim to the invalid claim. You can view claims using:
```bash theme={null}
./op-challenger/bin/op-challenger list-claims \
--l1-eth-rpc \
--game-address
```
There should be 3 claims in the game after this test.
## Switch to permissionless proofs
After completing all previous steps and verifying their successful operation, you need to update the `respectedGameType` in the `AnchorStateRegistry`. This requires execution through the appropriate privileged role (typically the Guardian).
You have two main options for executing this step:
### Option 1: Execute using a multisig
If your privileged role (such as the Guardian) is controlled by a multisig or DAO governance system, use the provided JSON payload (`input.json`):
```json theme={null}
{
"chainId": "",
"metadata": {
"name": "Deputy Guardian - Enable Permissionless Dispute Game",
"description": "This task updates the `respectedGameType` in the `AnchorStateRegistry` to `CANNON` (game type 0), enabling users to permissionlessly propose outputs as well as for anyone to participate in the dispute of these proposals. This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` that was created after this update occurs."
},
"transactions": [
{
"metadata": {
"name": "Update `respectedGameType` in the `AnchorStateRegistry`",
"description": "Updates the `respectedGameType` to `CANNON` in the `AnchorStateRegistry`, enabling permissionless proposals and challenging."
},
"to": "",
"value": "0x0",
"data": "0x7fc485040000000000000000000000000000000000000000000000000000000000000000",
"contractMethod": {
"type": "function",
"name": "setRespectedGameType",
"inputs": [
{
"name": "_gameType",
"type": "uint32"
}
],
"outputs": [],
"stateMutability": "nonpayable"
},
"contractInputsValues": {
"_gameType": "0"
}
}
]
}
```
* **Submit this JSON payload** through your interface (e.g., Safe transaction builder).
* **Simulate** this transaction using Tenderly before execution to ensure the expected state changes:
* The `respectedGameType` in the `AnchorStateRegistry` should change from `1` (PERMISSIONED) to `0` (CANNON).
***
### Option 2: Direct execution via `cast` (Forge CLI)
Alternatively, if you're executing directly from a single-privileged wallet or want quicker execution, use the following `cast` commands:
1. First, encode the transaction calldata using `cast calldata`:
```bash theme={null}
CALLDATA=$(cast calldata "setRespectedGameType(uint32)" 0)
```
2. Send the transaction:
```bash theme={null}
# Execute the transaction
cast send --rpc-url --private-key "$CALLDATA"
```
3. After execution, verify the respected game type:
```bash theme={null}
cast call --rpc-url "respectedGameType()(uint32)"
# The value should now be 0 (CANNON)
```
### Post-execution configuration:
After updating, configure `op-proposer` to create proposals using the permissionless `CANNON` game type:
Via command-line argument:
```bash theme={null}
--game-type 0
```
Or via environment variable:
```bash theme={null}
OP_PROPOSER_GAME_TYPE=0
```
This action requires all in-progress withdrawals to be re-proven against a new `FaultDisputeGame` created after this update occurs.
## Next steps
* For more detail on deploying new dispute games with OPCM, [see the docs](/chain-operators/tutorials/dispute-games).
* Deploy new dispute games with OPCM via [this tutorial](/chain-operators/tutorials/dispute-games).
* Generate an absolute prestate using the [absolute prestate guide](/chain-operators/tutorials/absolute-prestate).
* Understand fault proofs in the [Fault proofs explainer](/op-stack/fault-proofs/explainer).
# Modifying predeployed contracts
Source: https://docs.optimism.io/chain-operators/tutorials/modifying-predeploys
Learn how to modify predeployed contracts for an OP Stack chain by upgrading the proxy.
OP Stack Hacks are explicitly things that you can do with the OP Stack that are *not* currently intended for production use.
OP Stack Hacks are not for the faint of heart. You will not be able to receive significant developer support for OP Stack Hacks. Be prepared to get your hands dirty and to work without support.
OP Stack blockchains have a number of [predeployed contracts](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/libraries/Predeploys.sol) that provide important functionality.
Most of those contracts are proxies that can be upgraded using the `proxyAdminOwner` which was configured when the network was initially deployed.
## Before you begin
In this tutorial, you learn how to modify predeployed contracts for an OP Stack chain by upgrading the proxy. The predeploys are controlled from a predeploy called [`ProxyAdmin`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/ProxyAdmin.sol), whose address is `0x4200000000000000000000000000000000000018`.
The function to call is [`upgrade(address,address)`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/ProxyAdmin.sol#L152-L168).
The first parameter is the proxy to upgrade, and the second is the address of a new implementation.
## Modify the legacy `L1BlockNumber` contract
For example, the legacy `L1BlockNumber` contract is at `0x420...013`.
To disable this function, we'll set the implementation to `0x00...00`.
We do this using the [Foundry](https://book.getfoundry.sh/) command `cast`.
* Set these addresses as variables in your terminal.
```sh theme={null}
L1BLOCKNUM=0x4200000000000000000000000000000000000013
PROXY_ADMIN=0x4200000000000000000000000000000000000018
ZERO_ADDR=0x0000000000000000000000000000000000000000
```
* Set `PRIVKEY` to the private key of your ADMIN address.
* Set `ETH_RPC_URL`. If you're on the computer that runs the blockchain, use this command.
```sh theme={null}
export ETH_RPC_URL=http://localhost:8545
```
See that when you call the contract you get a block number, and twelve seconds later you get the next one (block time on L1 is twelve seconds).
```sh theme={null}
cast call $L1BLOCKNUM 'number()' | cast --to-dec
sleep 12 && cast call $L1BLOCKNUM 'number()' | cast --to-dec
```
```sh theme={null}
L1BLOCKNUM_IMPLEMENTATION=`cast call $L1BLOCKNUM "implementation()" | sed 's/000000000000000000000000//'`
echo $L1BLOCKNUM_IMPLEMENTATION
```
```sh theme={null}
cast send --private-key $PRIVKEY $PROXY_ADMIN "upgrade(address,address)" $L1BLOCKNUM $ZERO_ADDR
```
```sh theme={null}
cast call $L1BLOCKNUM 'implementation()'
cast call $L1BLOCKNUM 'number()'
```
```sh theme={null}
cast send --private-key $PRIVKEY $PROXY_ADMIN "upgrade(address,address)" $L1BLOCKNUM $L1BLOCKNUM_IMPLEMENTATION
cast call $L1BLOCKNUM 'number()' | cast --to-dec
```
# How to rewind op-geth
Source: https://docs.optimism.io/chain-operators/tutorials/rewind-op-geth
Learn how to rewind an op-geth node to a previous chain head.
## Overview
This tutorial teaches you how to rewind a single op-geth instance to a previous chain head.
This can be helpful to fix a divergent node, recreate archival state, or the safedb from an earlier chain state.
## Rewind op-geth
Follow these steps to rewind your op-geth node.
Whatever CL clients are driving the op-geth instance need to be stopped to prevent them from interfering with the rewind later.
This would typically be a locally running `op-node` or `kona-node` application, a Docker container or a pod in a Kubernetes cluster.
Make sure you don't have something like ArgoCD syncing the app in question which would just scale the pod back up again.
If you already know the block number/hash you want to rewind to, you can proceed to the next step.
If you only know the *timestamp* you want to rewind to, you can find the corresponding block with
```bash theme={null}
cast find-block 1741890000 -r
```
Where `` points to your op-geth node’s RPC endpoint, e.g. `http://localhost:8545` if it was a local instance with the default port and `1741890000` is an example unix timestamp. You can use e.g. [https://www.epochconverter.com/](https://www.epochconverter.com/) to convert from human times to unix timestamps.
You need the JWT secret and *open* & *admin* port (default `8545` & `8551`).
Then you can use the `op-wheel` command from the [monorepo](https://github.com/ethereum-optimism/optimism/tree/develop/op-wheel) to issue the rewind.
```bash theme={null}
# save JWT secret to env var
export JWT="YourJWTSecret123"
# assuming default ports 8545 and 8551 and local endpoint
go run ./op-wheel/cmd engine rewind \
--engine.open http://localhost:8545 \
--engine http://localhost:8551 \
--engine.jwt-secret $JWT \
--log.level DEBUG \
--set-head --to 8460000
```
This would rewind the op-geth node to the block with number `8460000`. You should have determined the right block number in step 2.
You can now start your CL client again. It will sync from the rewound block head.
# Upgrading a Chain From Output Roots to Super Roots
Source: https://docs.optimism.io/chain-operators/tutorials/upgrade-chain-to-super-roots
Upgrade an OP Stack chain from output-root dispute games to super-root dispute games with a single opcm.upgrade call, then cut op-proposer, op-challenger, and op-dispute-mon over to super roots.
# Upgrading a Chain From Output Roots to Super Roots
This guide depends on the interop feature, which is still in development. Do not follow it on production chains.
This runbook walks you through upgrading a single OP Stack chain from output-root dispute games (`PERMISSIONED_CANNON`, `CANNON` or `CANNON_KONA`) to super-root dispute games (`SUPER_PERMISSIONED` or `SUPER_CANNON_KONA`). It runs as a single `opcm.upgrade` call against the chain's existing per-chain `AnchorStateRegistry` and `DisputeGameFactory`. The chain's permission model is preserved: a permissioned chain stays permissioned with `SUPER_PERMISSIONED` only; a permissionless chain runs both super game types with `SUPER_CANNON_KONA` as the respected one. Differences between the two are called out inline.
By the end you will have a chain proposing super-root claims via `op-proposer` using a single-chain super-root RPC endpoint and monitoring them with `op-dispute-mon`. On permissionless chains, `op-challenger` defends `SUPER_CANNON_KONA` claims. On permissioned chains, `op-dispute-mon` detects invalid `SUPER_PERMISSIONED` claims, `op-challenger` updates the `AnchorStateRegistry` as games finalize. Every pre-migration dispute game already in flight continues to resolve, and proven withdrawals are not invalidated.
This is `opcm.upgrade`, not `opcm.migrate` — the chain keeps its own per-chain `AnchorStateRegistry` and `DisputeGameFactory`.
Looking to switch from permissioned to permissionless fault proofs? See [Migrating to permissionless fault proofs](/chain-operators/tutorials/migrating-permissionless). This runbook is for chains already running fault proofs that need to move from output-root games to super-root games.
## Before You Begin
### Required tooling
The versions below are the ones the optimism repo's `mise.toml` pins; older versions of `cast` in particular may not parse the function-selector syntax used in this runbook.
| Tool | Minimum version |
| --------------------------- | --------------- |
| `foundry` (`cast`, `forge`) | `1.2.3` |
| `just` | `1.46.0` |
| `jq` | `1.7.1` |
| `go` | `1.26.5` |
| `curl` | any |
You also need a checkout of the [optimism monorepo](https://github.com/ethereum-optimism/optimism) to run [`op-fetcher`](https://github.com/ethereum-optimism/optimism/tree/develop/op-fetcher) (used in the next step).
### Gather the Required Inputs
Collect everything below before you start.
**Chain identity.** The chain's per-chain addresses live in the `superchain-registry` repository under `superchain/configs//.toml`. Open that file and read `chain_id`, `addresses.SystemConfigProxy`, and `addresses.L1StandardBridgeProxy`.
**Chain addresses and roles.** Use `op-fetcher` to derive the rest of the on-chain configuration in a single call. It runs an embedded forge script against `$L1_RPC` and resolves every per-chain proxy, role, and the current fault-proof status from `SystemConfig` and `L1StandardBridge`:
```bash theme={null}
cd /op-fetcher
just build-all
go run ./cmd fetch \
--l1-rpc-url "$L1_RPC" \
--system-config "" \
--l1-standard-bridge "" \
--output-file chain.json
```
Read the values you need from `chain.json`:
| Item | Source |
| ---------------------------------------------------------- | ---------------------------------------------------------- |
| L2 chain ID | `chain_id` in the chain's registry TOML |
| `SystemConfig` proxy | `addresses.SystemConfigProxy` in the chain's registry TOML |
| `OptimismPortal2` proxy | `.addresses.OptimismPortalProxy` |
| `AnchorStateRegistry` proxy (referred to below as ``) | `.addresses.AnchorStateRegistryProxy` |
| `DisputeGameFactory` proxy (referred to below as ``) | `.addresses.DisputeGameFactoryProxy` |
| `SuperchainConfig` proxy | `.addresses.SuperchainConfigProxy` |
**Chain type.** The upgrade preserves the chain's existing permission model. `chain.json` reports the current respected game type at `.faultProofs.respectedGameType`:
* `1` (`PERMISSIONED_CANNON`) — chain is **permissioned**. The new respected game type is `5` (`SUPER_PERMISSIONED`).
* `0` (`CANNON`) or `8` (`CANNON_KONA`) — chain is **permissionless**. The new respected game type is `9` (`SUPER_CANNON_KONA`).
**Init bonds.** `SUPER_PERMISSIONED` (game type `5`) is bondless and must use an `initBond` of `0`. `SUPER_CANNON_KONA` (game type `9`) uses `0.08 ether` (`80000000000000000` wei), the standard value used on existing chains.
**Release artifacts and infrastructure.**
| Item | Source |
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OPCM` address for the target network | `validation/standard/standard-versions-.toml` in the [superchain-registry](https://github.com/ethereum-optimism/superchain-registry). Look up the `op-contracts/v8.x.y` release pinned by the `OPCMUpgradeV800` template and read `op_contracts_manager.address`. |
| Kona-interop prestate hash | OPCM release manifest. Use the `-interop` kona variant, even if interop is not scheduled for the chain. |
| Prestate artifact URL | The chain's existing `cannon-kona-prestates-url` server, with the kona-interop prestate uploaded ahead of the upgrade |
| Single-chain super-root RPC endpoint (`$SUPER_ROOT_RPC`) | Your infrastructure team. Use the chain's op-node RPC endpoint or an op-supernode `//` endpoint. An op-node must run with `--safedb.path` enabled and populated. The namespaced endpoint returns a super root containing only this chain, even if the op-supernode tracks other chains. |
### Verify the Preconditions
Before you change any configuration:
* **Confirm the template's extra instructions.** Open `superchain-ops/src/template/OPCMUpgradeV800.sol` and check `_buildExtraInstructions`. As of writing it injects `overrides.cfg.startingRespectedGameType` and `PermittedProxyDeployment`. Both need attention before continuing. `PermittedProxyDeployment` must be dropped: OPCM `8.0.0` permits no proxy deployments during an upgrade, because the unified `DelayedWETH` shipped with the 7.x OPCM, so the instruction now reverts with `OPContractsManagerV2_InvalidUpgradeInstruction`. An `overrides.cfg.startingAnchorRoot` override must be added: without it, OPCM falls back to the existing on-chain `startingAnchorRoot`, which on a pre-upgrade chain is an output-root-shaped value with a block-number `l2SequenceNumber` — not a valid super-root anchor. The template must be extended to read a starting super-root anchor from TOML and add it as an extra instruction. Fix the template before continuing.
* **Confirm the RPC endpoint returns a single-chain super root.** Either the chain's op-node RPC endpoint or an op-supernode `//` endpoint can be used. Do not use a multi-chain op-supernode root endpoint; it computes roots across its full dependency set.
* **Verify the single-chain RPC endpoint is healthy.** For an op-node or namespaced op-supernode endpoint, confirm it reports a recent finalized L2 head and can produce the corresponding super root:
```bash theme={null}
TS=$(cast rpc optimism_syncStatus --rpc-url $SUPER_ROOT_RPC | jq -r '.finalized_l2.timestamp')
cast rpc superroot_atTimestamp "$(cast 2h $TS)" \
--rpc-url $SUPER_ROOT_RPC | jq -r '.data.super_root'
```
If an op-node returns a SafeDB error, configure `--safedb.path`, let the node populate records through derivation, and retry. `optimism_syncStatus` can succeed while SafeDB is disabled, but `superroot_atTimestamp` cannot serve a non-genesis timestamp without SafeDB.
* **Verify the prestate server serves the new hash.**
```bash theme={null}
curl -fI "$PRESTATE_URL/$SUPER_PRESTATE_HASH.bin.gz"
```
## Stage the Off-Chain Configuration
Apply these changes before you submit the on-chain upgrade. After the changes ship, the components keep operating against existing pre-migration games and pick up super-root games automatically once the upgrade lands.
### Update op-challenger
Update the running `op-challenger` for the chain. Keep `--rollup-rpc` and the existing `--game-types` so existing games continue to be defended. Add the super-root trace type only on permissionless chains, where `SUPER_CANNON_KONA` is enabled.
| Action | Flag (env var) | Value |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| Add | `--superroot-rpc` (`OP_CHALLENGER_SUPERROOT_RPC`) | `$SUPER_ROOT_RPC` |
| Append to | `--game-types` (`OP_CHALLENGER_GAME_TYPES`) | `super-cannon-kona`. Do not add `super-permissioned`; the simplified `SUPER_PERMISSIONED` game does not require claims to be posted. |
| Confirm set | `--cannon-kona-prestates-url` (`OP_CHALLENGER_CANNON_KONA_PRESTATES_URL`) | The prestate-server URL you validated in the preconditions |
| Confirm set | `--cannon-kona-network` (`OP_CHALLENGER_CANNON_KONA_NETWORK`) or `--cannon-kona-depset-config` (`OP_CHALLENGER_CANNON_KONA_DEPSET_CONFIG`) | One must be set so kona can resolve the dependency set for `SUPER_CANNON_KONA`. |
On a permissionless chain, confirm the startup log lists `super-cannon-kona` as registered and shows no connection errors against the single-chain super-root RPC endpoint. On a permissioned chain, confirm the existing trace types remain registered; do not configure a `SUPER_PERMISSIONED` trace type. `op-challenger` is still required on permissioned chains to update the `AnchorStateRegistry` as `SUPER_PERMISSIONED` games finalize.
### Update op-dispute-mon
Update the running `op-dispute-mon` for the chain.
| Action | Flag (env var) | Value |
| ------ | -------------------------------------------------- | ----------------- |
| Add | `--superroot-rpc` (`OP_DISPUTE_MON_SUPERROOT_RPC`) | `$SUPER_ROOT_RPC` |
Keep `--rollup-rpc`. `op-dispute-mon` discovers games from the existing `DisputeGameFactory` and does not need a game-type filter change. After the restart, confirm the existing-games metrics keep populating.
### Leave op-proposer Unchanged for Now
Skip `op-proposer` until cutover. Its configuration changes in [Switch op-proposer to Super Roots](#switch-op-proposer-to-super-roots).
## Generate the Starting Anchor Super Root
The upgrade must re-initialise `` with a super-root-shaped starting anchor. The existing on-chain `startingAnchorRoot` is in output-root form (its `l2SequenceNumber` is an L2 block number), so the template must inject a fresh super-root value via `overrides.cfg.startingAnchorRoot` in `extraInstructions`. Confirm in the precondition above that the template has been extended to read this from TOML and pass it through.
Compute the value using `superroot_atTimestamp`. Pick the current finalized timestamp (or any recent finalized timestamp), then ask the single-chain RPC endpoint for the super root at that timestamp.
```bash theme={null}
# .finalized_l2.timestamp is a JSON number (decimal Unix seconds).
TS=$(cast rpc optimism_syncStatus --rpc-url $SUPER_ROOT_RPC | jq -r '.finalized_l2.timestamp')
# superroot_atTimestamp expects a hex-encoded JSON string (hexutil.Uint64); cast 2h handles the conversion.
cast rpc superroot_atTimestamp "$(cast 2h $TS)" --rpc-url $SUPER_ROOT_RPC | jq -r '.data.super_root'
echo "timestamp=$TS"
```
Capture two values:
* `super_root` (a `bytes32` hash from `.data.super_root`) — passed as `Proposal.root` in `overrides.cfg.startingAnchorRoot`.
* `timestamp` (the uint64 you passed in) — passed as `Proposal.l2SequenceNumber`. This field is the timestamp itself, not a block number or sequence index.
## Build the superchain-ops Task
Author a new task directory under `superchain-ops/src/tasks///` using the `OPCMUpgradeV800` template. The schema below assumes the template has been extended (per the precondition above) to also read a starting super-root anchor and inject it as `overrides.cfg.startingAnchorRoot`.
### Configure config.toml
```toml theme={null}
l2chains = [
{name = "", chainId = },
]
templateName = "OPCMUpgradeV800"
[[opcmUpgrades]]
chainId =
# Kona-interop super prestate, used by SUPER_CANNON_KONA. The `-interop` variant
# supports super roots and works whether
# or not interop is enabled or scheduled on the chain.
cannonKonaPrestate = "0x"
expectedValidationErrors = "" # fill in after the dry run
initBond = 80000000000000000 # 0.8 ether, applied to `SUPER_CANNON_KONA` games; the template always forces SUPER_PERMISSIONED to 0
startingRespectedGameType = <9 or 5> # 9 permissionless, 5 permissioned
# Pending template extension — see the "Generate the Starting Anchor Super Root" section.
startingAnchorRoot = { root = "0x", l2SequenceNumber = }
[addresses]
OPCM = "0x"
```
The template derives everything else (per-game-type config, `SuperchainConfig`, validator, etc.) automatically — no further TOML required.
### Capture expectedValidationErrors
Run the task in simulation mode against an L1 fork from inside the task directory:
```bash theme={null}
cd superchain-ops/src/tasks//
just simulate
```
If the validator's output does not match `expectedValidationErrors` in TOML, the simulation reverts with a message of the form `Unexpected errors: ; expected: `. Read the actual codes from that revert message. The default expectation is **no errors** — `expectedValidationErrors = ""` and a clean simulation.
Any non-empty error string must be reviewed code by code, not copy-pasted. For each code:
* **Resolve it** if it points at a fixable input — a wrong address, a wrong prestate, a template release mismatch, a missing override, or a registry entry that needs updating.
* **Add it to `expectedValidationErrors` only after justifying it** with an inline comment in the TOML explaining why it is structurally expected for this chain.
The simulation passes once every printed code has been either resolved or knowingly added with justification. Treat any code you cannot explain as a hard stop.
## Execute the Upgrade
1. **Stop `op-proposer` for the chain.** Existing games continue to resolve; only new proposals halt.
2. **Leave `op-challenger` and `op-dispute-mon` running.** Both were configured earlier and pick up super-root games automatically.
3. **Sign and broadcast the task** using your team's standard `superchain-ops` signing workflow.
4. **Wait for the L1 transaction to confirm.**
The on-chain effect:
* `` is re-initialised with the supplied anchor and respected game type. The retirement timestamp is not bumped, so existing in-flight games stay valid.
* The super dispute game implementation or implementations are installed on ``.
* Implementation pointers for the game types passed with `enabled = false` are cleared, blocking creation of new games of those types.
## Verify the Upgrade On-Chain
Run the checks below before you cut `op-proposer` over.
If any check fails, stop, do not start the new proposer, and escalate via your standard incident-response channel. Recovery from a half-cut state with a misbehaving proposer is materially harder than recovery from a paused proposer.
### Common Checks
```bash theme={null}
# Respected game type
cast call 'respectedGameType()(uint32)' --rpc-url $L1_RPC
# Expect: 9 (permissionless) or 5 (permissioned)
# Anchor root and timestamp
cast call 'getAnchorRoot()(bytes32,uint256)' --rpc-url $L1_RPC
# Expect: (