---
# System prepended metadata

title: "\U0001F680 OP Stack Rollup"

---

# 🚀 Setting Up OP Stack Testnet Rollup 

[![OP Stack Docs](https://img.shields.io/badge/Docs-OP%20Stack-blue?logo=ethereum&style=flat-square)](https://docs.optimism.io/operators/chain-operators/tutorials/create-l2-rollup)  
[![GitHub Repository](https://img.shields.io/badge/GitHub-OP%20Stack-171515?logo=github&style=flat-square)](https://github.com/ethereum-optimism/optimism.git)  
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)

## 📖 Overview
With the rise of layer 2 scaling solutions, optimistic rollups have become a key technology in ethereum scalabilty solutions. These rollup help reduce transaction cost and improve throughput while leveraging ethereum security.

If you are looking to setup and deploy your own optimistic rollup, This guide will walk you through the fundamental concepts of setting up and deploying an optimistic rollup.


This **hands-on tutorial** is designed for developers who want to:  
✅ **Learn** how the OP Stack works by deploying a testnet chain.  
✅ **Understand** core components like the rollup node, batcher, and proposer.  
✅ **Build** a fully functional testnet Optimistic Rollup from scratch.

By the end, you'll have:  
🔹 **Your own OP Stack testnet**  
🔹 **Knowledge of L2 architecture**  
🔹 **A foundation for production deployments**

---

## 📗 Table of Contents

- [📖 Overview](#📖-overview)
- [📂 Setting Up Project Folder](#📂-setting-up-project-folder)
- [⚙️ Configure your network](#️⚙️-configure-your-network)
- [🚀 Op-deployer](#🚀-op-deployer)
- [💻 Op-geth](#💻-op-geth)
- [☋ Op-node](#☋-op-node)
- [Op-batcher](#-op-batcher)
- [Op-proposer](#-op-proposer)
- [Interact With Your Chain](#interact-with-chain)

## 📂 Setting Up Project Folder

### Clone Optimism Repo

```
cd ~/Documents
git clone https://github.com/ethereum-optimism/optimism.git
```

### Change Directory To optimism/

```
cd ~/optimism
```

### Switch git branch

```
git checkout tutorials/chain
```

### Check Required Dependencies

```
./packages/contracts-bedrock/scripts/getting-started/versions.sh
```

```
Dependency                               | Min req         | Actual
-------------------------------------------------------------------------------
git (https://git-scm.com)                  2                2.45.2
go (https://go.dev)                        1.21             1.22.7
node (https://nodejs.org)                  20               23.5.0
foundry (https://getfoundry.sh)            0.2.0 (a5efe4f)  No version, commit hash, and timestamp found.
make (https://www.gnu.org/software/make)   3                3.81
jq (https://jqlang.github.io/jq)           1.6              1.6
direnv (https://direnv.net/)               2                2.35.0
just (https://github.com/casey/just)       1.34.0           1.40.0
```

### Install pnpm

```
pnpm install
```

### Duplicate the sample environment variable file

```
cp .envrc.example .envrc
```

Open up the environment variable file and fill out the following variables:

- `L1_RPC_URL`: URL for your L1 node (a Sepolia node in this case).
- `L1_RPC_KIND`: Kind of L1 RPC you're connecting to, used to inform optimal transactions receipts fetching. Valid options: *alchemy, quicknode, infura, parity, nethermind, debug_geth, erigon, basic, any*.

### Generate addresses

You'll need four addresses and their private keys when setting up the chain:

- The Admin address has the ability to upgrade contracts.
- The Batcher address publishes Sequencer transaction data to L1.
- The Proposer address publishes L2 transaction results (state roots) to L1.
- The Sequencer address signs blocks on the p2p network.

run

```
./packages/contracts-bedrock/scripts/getting-started/wallets.sh
```

```
Copy the following into your .envrc file:

# Admin address
export GS_ADMIN_ADDRESS=0x9625B9aF7C42b4Ab7f2C437dbc4ee749d52E19FC
export GS_ADMIN_PRIVATE_KEY=0xbb93a75f64c57c6f464fd259ea37c2d4694110df57b2e293db8226a502b30a34

# Batcher address
export GS_BATCHER_ADDRESS=0xa1AEF4C07AB21E39c37F05466b872094edcf9cB1
export GS_BATCHER_PRIVATE_KEY=0xe4d9cd91a3e53853b7ea0dad275efdb5173666720b1100866fb2d89757ca9c5a

# Proposer address
export GS_PROPOSER_ADDRESS=0x40E805e252D0Ee3D587b68736544dEfB419F351b
export GS_PROPOSER_PRIVATE_KEY=0x2d1f265683ebe37d960c67df03a378f79a7859038c6d634a61e40776d561f8a2

# Sequencer address
export GS_SEQUENCER_ADDRESS=0xC06566E8Ec6cF81B4B26376880dB620d83d50Dfb
export GS_SEQUENCER_PRIVATE_KEY=0x2a0290473f3838dbd083a5e17783e3cc33c905539c0121f9c76614dda8a38dca
```

> Copy the output from the previous step and paste it into your .envrc file as directed.

### Fund Addressess

You will need to send ETH to the Admin, Proposer, and Batcher addresses. The exact amount of ETH required depends on the L1 network being used. You do not need to send any ETH to the Sequencer address as it does not send transactions.

It's recommended to fund the addresses with the following amounts when using Sepolia:

| Role      | Sepolia ETH Required |
|-----------|----------------------|
| Admin     | 0.5 Sepolia ETH      |
| Batcher   | 0.1 Sepolia ETH      |
| Proposer  | 0.2 Sepolia ETH      |

### Load the variables with direnv

Next you'll need to allow direnv to read this file and load the variables into your terminal using the following command.

```
direnv allow
```

After running direnv allow you should see output that looks something like the following (the exact output will vary depending on the variables you've set, don't worry if it doesn't look exactly like this):

```
bankat@Bankats-MacBook-Pro-2 optimism % direnv allow
direnv: loading ~/Documents/rollups/optimism/.envrc
direnv: export +BEACON_URL +DEPLOYMENT_CONTEXT +ETHERSCAN_API_KEY +GS_ADMIN_ADDRESS +GS_ADMIN_PRIVATE_KEY +GS_BATCHER_ADDRESS +GS_BATCHER_PRIVATE_KEY +GS_PROPOSER_ADDRESS +GS_PROPOSER_PRIVATE_KEY +GS_SEQUENCER_ADDRESS +GS_SEQUENCER_PRIVATE_KEY +IMPL_SALT +L1_RPC_KIND +L1_RPC_URL +PRIVATE_KEY +TENDERLY_PROJECT +TENDERLY_USERNAME

```

if you dont see any output, try setting up direnv following the docs: 

>https://direnv.net/docs/hook.html

## ⚙️ Configure your network

Once you've built your repository, you'll need to set up the configuration file for your chain. Currently, chain configuration lives inside of the contracts-bedrock package in the form of a JSON file.

### Move into the contracts-bedrock package

```
cd optimism/packages/contracts-bedrock
```

### Install Foundry Dependencies

```
forge install
```

### Generate The Configuration File

Run the following script to generate the `getting-started.json` configuration file inside of the `deploy-config/` directory.

```
./scripts/getting-started/config.sh
```


:::warning

NOTE: We ran into failed builds for `op-deployer`, `op-node`, `op-batcher`, `op-proposer` in the first optimism repo cloned (i'm guessing WIP, cos there are still ongoing merges and prs, most likey the cause of the break). But i was able to fix by using a different optimisim git repo. We are about to clone a new optimism folder and we will refer to it as
`optimism2`

Dependency needed: intsall `just` a command runner
linux: `sudo snap install just --classic`
mac: `brew install just`
:::

## Cloning Optimism2 For Stable Build of Op-deployer, Op-node, Op-batcher & Op-proposer

```
git clone https://github.com/ethereum-optimism/optimism.git

// rename to optimism2
```

## 🚀 Op-Deployer

The op-deployer tool simplifies the creation of genesis and rollup configuration files (`genesis.json` and `rollup.json`). These files are crucial for initializing the execution client (op-geth) and consensus client (op-node) for your network.

:::info
**NOTE:** you'll need to have sufficient gas to do this. Most preferrable when there is low network congestion.
:::

Run this command in the first `optimism/` clone not `optimism2/`
```
forge script scripts/Deploy.s.sol:Deploy --private-key $GS_ADMIN_PRIVATE_KEY --broadcast --rpc-url $L1_RPC_URL --slow
```

### Move Into Op-deployer Directory
Change directory to wherever your `optimism2/` folder is located.

```
cd ~/Documents/optimism2/op-deployer
```

### Build Op-deployer

```
go build -o ~/op-deployer/bin/op-deployer ./cmd/op-deployer
```

### Grant Required Permissions

```
chmod +x ~/op-deployer/bin/op-deployer
```

:::info
the op-deployer gets built to your home `~/` directory.
:::

```
cd ~/op-deployer
./bin/op-deployer init --l1-chain-id 11155111 --l2-chain-ids 42069 --workdir .deployer
```

this generates a `.deployer/` that has a config file named `intent.toml`.
Open the file in your code editor.

```
code ~/op-deployer/.deployer/intent.toml  // for those using vs-code
```

modify the config to look like this.
_(replace all the `.env` variables with the actual values from your `.envrc`)_

```
configType = "standard-overrides" // change standard to standard-overrides
l1ChainID = 11155111
fundDevAccounts = true // make this true
useInterop = false
l1ContractsLocator = "tag://op-contracts/v3.0.0-rc.2"
l2ContractsLocator = "tag://op-contracts/v3.0.0-rc.2"

// do not alter the superchain roles, they are system specific
[superchainRoles]
  proxyAdminOwner = "0x1eb2ffc903729a0f03966b917003800b145f56e2"
  protocolVersionsOwner = "0xfd1d2e729ae8eee2e146c033bf4400fe75284301"
  guardian = "0x7a50f00e8d05b95f98fe38d8bee366a7324dcf7e"

[[chains]]
  id = "0x000000000000000000000000000000000000000000000000000000000000a455"
  baseFeeVaultRecipient = "<your wallet address>"
  l1FeeVaultRecipient = "<your wallet address>"
  sequencerFeeVaultRecipient = "<your wallet address>"
  eip1559DenominatorCanyon = 250
  eip1559Denominator = 50
  eip1559Elasticity = 6
  operatorFeeScalar = 0
  operatorFeeConstant = 0
  [chains.roles]
    l1ProxyAdminOwner = "$GS_ADMIN_ADDRESS" // copy and paste actual address in a string for all env variables
    l2ProxyAdminOwner = "$GS_ADMIN_ADDRESS"
    systemConfigOwner = "$GS_ADMIN_ADDRESS"
    unsafeBlockSigner = "$GS_SEQUENCER_ADDRESS"
    batcher = "$GS_BATCHER_ADDRESS"
    proposer = "$GS_PROPOSER_ADDRESS"
    challenger = "$GS_ADMIN_ADDRESS"
```

### Generate genesis.json and rollup.json

```
./bin/op-deployer inspect genesis --workdir .deployer 42069 > .deployer/genesis.json
./bin/op-deployer inspect rollup --workdir .deployer 42069 > .deployer/rollup.json
```

## 💻 Op-geth

Change dir to `~/Documents` or any dir of your choice and clone op-geth

```
git clone https://github.com/ethereum-optimism/op-geth.git

cd op-geth/

make geth

build/bin/geth init --state.scheme=hash --datadir=datadir ~/op-deployer/.deployer/genesis.json
```

### Start The Op-geth

```
./build/bin/geth \
  --datadir ./datadir \
  --http \
  --http.corsdomain="*" \
  --http.vhosts="*" \
  --http.addr=0.0.0.0 \
  --http.api=web3,debug,eth,txpool,net,engine \
  --ws \
  --ws.addr=0.0.0.0 \
  --ws.port=8546 \
  --ws.origins="*" \
  --ws.api=debug,eth,txpool,net,engine \
  --syncmode=full \
  --gcmode=archive \
  --nodiscover \
  --maxpeers=0 \
  --networkid=42069 \
  --authrpc.vhosts="*" \
  --authrpc.addr=0.0.0.0 \
  --authrpc.port=8551 \
  --authrpc.jwtsecret=./jwt.txt \
  --rollup.disabletxpoolgossip=true
```

Your op-geth is now up and running. Hurray!🎉

## ☋ Op-node

Follow the commands one by one to run a successful build for op-node.

```
cd ~/Documents/optimism2/op-node
```
To build the op-node, run:
```
just op-node 
```
To Start Op-node, run:
----------------------------------
```
./bin/op-node \
  --rollup.config=./rollup.json \
  --l1=<your $L1_RPC_URL> \
  --l1.beacon=https://ethereum-sepolia-beacon-api.publicnode.com \
  --l2=ws://localhost:8546 \
  --l2.jwt-secret=./jwt-secret.txt \
  --syncmode=execution-layer \
  --p2p.priv.path=opnode_p2p_priv.txt \
  --p2p.peerstore.path=opnode_peerstore_db
```

## Op-batcher

Follow the commands one by one to run a successful build for op-batcher.

```
cd optimism2/op-batcher/
```
To build the op-batcher, run: ⁠
```
just op-batcher ⁠
```
Then start op-batcher by running this command:
```
./bin/op-batcher \
  --l2-eth-rpc=http://localhost:8545 \
  --rollup-rpc=http://localhost:9545 \
  --poll-interval=1s \
  --sub-safety-margin=6 \
  --num-confirmations=1 \
  --safe-abort-nonce-too-low-count=3 \
  --resubmission-timeout=30s \
  --rpc.addr=0.0.0.0 \
  --rpc.port=8548 \
  --rpc.enable-admin \
  --max-channel-duration=25 \
  --l1-eth-rpc=<your $l1_RPC_URL> \
  --private-key=<your $BATCHER_PRIVATE_KEY>
```

## Op-proposer

Follow the commands one by one to run a successful build for op-proposer.

```
cd optimism2/op-proposer/
```

To build the op-proposer, run:
```
just op-proposer ⁠
```
Then start op-proposer by running the command below:
```
./bin/op-proposer \
  --poll-interval=12s \
  --rpc.port=8560 \
  --rollup-rpc=http://localhost:9545 \
  --l2oo-address=$(cat ~/Documents/optimism/packages/contracts-bedrock/deployments/getting-started/.deploy | jq -r .L2OutputOracleProxy) \
  --private-key=<your $GS_PROPOSER_PRIVATE_KEY> \
  --l1-eth-rpc=<your $L1_RPC_UR>L

```

Note:
> ~/Documents/optimism/packages/contracts-bedrock/deployments/getting-started/.deploy

This should be path to your `.deploy` file you created in the first `optimism/` folder and replace on line 5 above.

## Connect Your wallet to your chain

To connect your wallet to your chain, Open a browser where you have a wallet extension and open this url to configure
[wallect connection link](https://chainid.link/?network=opstack-getting-started)

## 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`.

NB: navigate to your first optimism folder we cloned.

```
cd ~/optimism/packages/contracts-bedrock
```

To get contract address, run this command:

```
cat deployments/getting-started/.deploy | jq -r .L1StandardBridgeProxy
```

## Interact with Your Rollup
You can now interact with your Rollup like any other EVM chain. Send transactions, deploy contracts, and explore!