Operators
Spinning up the proposer

Spinning up the proposer

After you have spun up your sequencer, 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.

This guide assumes you already have a functioning sequencer and the necessary L1 contracts deployed using op-deployer. If you haven't set up your sequencer yet, please refer to the sequencer guide first.

To see configuration info for the proposer, check out the configuration page.

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)

Software installation

Build from source

Clone and build op-proposer

# 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.10.0
 
# Build op-proposer
cd op-proposer
just
 
# Binary will be available at ./bin/op-proposer

This uses op-proposer/v1.10.0 which is compatible with op-node/v1.13.3 and op-geth/v1.101511.0 from spinning up the sequencer guide. Always check the release notes (opens in a new tab) for compatibility.

Verify installation

Run this command to verify the installation.

./bin/op-proposer --version

Configuration setup

1. Organize your workspace

at the same level as your sequencer from the [sequencer tutorial](link-to-the sequencer tutorial):

# Create proposer directory at the same level as your sequencer
mkdir proposer-node
cd proposer-node
 
# Create scripts directory
mkdir scripts

2. Extract DisputeGameFactory address

Extract the DisputeGameFactory contract address from your op-deployer output:

# Navigate to proposer directory
cd proposer-node
 
# Copy the state.json from .deployer directory created while using op-deployer
# Update the path if your .deployer directory is located elsewhere
cp ../.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.

3. Set up environment variables

Create your .env file with the actual values:

# 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=0xYOUR_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!

4. Get your private key

Get a private key from your wallet.

Proposer configuration

Create scripts/start-proposer.sh:

#!/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:

~/
├── optimism/               # Contains op-proposer binary
├── sequencer-node/         # Your sequencer setup
├── .deployer/              # From op-deployer
   └── state.json
└── proposer-node/          # Your proposer working directory
    ├── state.json          # Copied from .deployer
    ├── .env
    └── scripts/
        └── start-proposer.sh

Starting the proposer

1. Verify prerequisites

Ensure your sequencer and op-node are running:

 
# Test L1 connectivity
# Note: Make sure you have exported these environment variables to your current shell session:
# export L1_RPC_URL="https://sepolia.infura.io/v3/YOUR_KEY"
# export L2_RPC_URL="http://localhost:8545"  
# export ROLLUP_RPC_URL="http://localhost:8547"
 
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  $L1_RPC_URL
 
# Test L2 connectivity  
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
  $L2_RPC_URL
 
# Test rollup node connectivity
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' \
  $ROLLUP_RPC_URL

2. Start the proposer

# Make the script executable
chmod +x scripts/start-proposer.sh
 
# Start the proposer
./scripts/start-proposer.sh

Verification

Verify your proposer is working correctly:

Check proposer status

 
# Monitor proposal activity
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"optimism_outputAtBlock","params":["latest"],"id":1}' \
  http://localhost:8547
 
# Check if your proposer address has enough ETH for gas
# (Replace with your actual proposer address)
curl -X POST -H "Content-Type: application/json" \
  --data '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0xYOUR_PROPOSER_ADDRESS","latest"],"id":1}' \
  $L1_RPC_URL

Your proposer is now operational!

Next steps