diff --git a/create-l2-rollup-example/.example.env b/create-l2-rollup-example/.example.env
new file mode 100644
index 000000000..75a31144b
--- /dev/null
+++ b/create-l2-rollup-example/.example.env
@@ -0,0 +1,62 @@
+# OP Stack L2 Rollup Environment Configuration
+# Copy this file to .env and fill in your actual values
+# This uses direnv for automatic environment loading
+
+# ==========================================
+# REQUIRED: L1 Configuration (Sepolia testnet)
+# ==========================================
+# Option 1: Public endpoint (no API key required - recommended for testing)
+L1_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
+L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com"
+
+# Option 2: Private endpoint (requires API key from Infura/Alchemy)
+# L1_RPC_URL="https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY"
+# L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com"
+
+# ==========================================
+# REQUIRED: Private Key for Deployment & Operations
+# ==========================================
+# IMPORTANT: Never commit this to version control!
+# Get this from your MetaMask or other self-custody wallet
+# Remove the 0x prefix if present
+PRIVATE_KEY="YOUR_PRIVATE_KEY_WITHOUT_0X_PREFIX"
+
+# ==========================================
+# OPTIONAL: Public IP for P2P Networking
+# ==========================================
+# For local testing, defaults to 127.0.0.1
+# For production/testnet, run `curl ifconfig.me` to get your public IP
+P2P_ADVERTISE_IP="127.0.0.1"
+
+# ==========================================
+# OPTIONAL: Custom Configuration
+# ==========================================
+# Default L2 Chain ID (can be any number between 10000-99999 for testing)
+L2_CHAIN_ID="16584"
+
+# L1 Beacon API URL for op-node (usually same provider as L1_RPC_URL)
+L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com"
+
+# ==========================================
+# ADVANCED: Component Environment Variables
+# ==========================================
+# These follow OP Stack conventions and can be overridden in the main .env file
+# The setup script creates service-specific .env files with these OP_* prefixed variables
+
+# Batcher Environment Variables
+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"
+
+# Proposer Environment Variables
+OP_PROPOSER_PROPOSAL_INTERVAL="3600s"
+OP_PROPOSER_GAME_TYPE="0"
+OP_PROPOSER_POLL_INTERVAL="20s"
+
+# ==========================================
+# DEVELOPMENT: Local Network (Alternative to Sepolia)
+# ==========================================
+# Uncomment these for local development with a local L1 network
+# L1_RPC_URL="http://host.docker.internal:8545"
+# L1_BEACON_URL="http://host.docker.internal:5052"
\ No newline at end of file
diff --git a/create-l2-rollup-example/.gitignore b/create-l2-rollup-example/.gitignore
new file mode 100644
index 000000000..b82e9ff7a
--- /dev/null
+++ b/create-l2-rollup-example/.gitignore
@@ -0,0 +1,45 @@
+# Environment files
+.env
+.env.local
+**/.env
+
+# Deployment artifacts
+deployer/
+
+# Generated service directories
+sequencer/
+batcher/
+proposer/
+challenger/
+
+# Optimism monorepo
+optimism/
+
+# Downloaded binaries
+op-deployer
+
+# Logs
+*.log
+logs/
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# IDE files
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# Node modules (if any)
+node_modules/
+
+# Temporary files
+tmp/
+temp/
diff --git a/create-l2-rollup-example/Makefile b/create-l2-rollup-example/Makefile
new file mode 100644
index 000000000..c74670370
--- /dev/null
+++ b/create-l2-rollup-example/Makefile
@@ -0,0 +1,89 @@
+# OP Stack L2 Rollup Makefile
+
+.PHONY: help setup up down logs clean reset
+
+# Default target
+help: ## Show this help message
+ @echo "OP Stack L2 Rollup Management Commands:"
+ @grep -E '^[a-zA-Z0-9_-]+:.*?## .*' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
+
+setup: ## Deploy L1 contracts and configure rollup components
+ mkdir -p deployer
+ @if [ ! -f op-deployer ]; then \
+ echo "❌ Error: op-deployer not found. Run 'make download' first."; \
+ exit 1; \
+ fi
+ @if [ ! -f .env ]; then \
+ echo "❌ Error: .env file not found. Copy .example.env to .env and configure it."; \
+ exit 1; \
+ fi
+ @echo "Running setup script..."
+ @./scripts/setup-rollup.sh
+
+up: ## Start all services
+ docker-compose up -d --wait
+ @make test-l2
+
+down: ## Stop all services
+ docker-compose down
+
+logs: ## View logs from all services
+ docker-compose logs -f
+
+logs-%: ## View logs from a specific service (e.g., make logs-sequencer)
+ docker-compose logs -f $*
+
+status: ## Show status of all services
+ docker-compose ps
+
+restart: ## Restart all services
+ docker-compose restart
+
+restart-%: ## Restart a specific service (e.g., make restart-batcher)
+ docker-compose restart $*
+
+clean: ## Remove all containers and volumes
+ @# Create minimal env files if they don't exist to avoid docker-compose errors
+ @mkdir -p batcher proposer challenger sequencer
+ @echo "# Minimal env for cleanup" > batcher/.env 2>/dev/null || true
+ @echo "# Minimal env for cleanup" > proposer/.env 2>/dev/null || true
+ @echo "# Minimal env for cleanup" > challenger/.env 2>/dev/null || true
+ docker-compose down -v --remove-orphans 2>/dev/null || true
+
+reset: ## Complete reset - removes all data and redeploys
+ @echo "This will completely reset your L2 rollup deployment!"
+ @read -p "Are you sure? (y/N) " confirm && [ "$$confirm" = "y" ] || exit 1
+ make clean
+ rm -rf deployer sequencer batcher proposer challenger
+ @echo "Reset complete. Run 'make setup' to redeploy."
+
+test-l2: ## Test L2 connectivity
+ @echo "Testing L2 RPC connection..."
+ @curl -s -X POST -H "Content-Type: application/json" \
+ --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
+ http://localhost:8545 | jq -r '.result' 2>/dev/null && echo "✅ L2 connection successful" || echo "❌ L2 connection failed"
+
+test-l1: ## Test L1 connectivity
+ @echo "Testing L1 RPC connection..."
+ @if [ -f .env ]; then \
+ set -a; source .env; set +a; \
+ fi; \
+ curl -s -X POST -H "Content-Type: application/json" \
+ --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
+ "$$L1_RPC_URL" | jq -r '.result' 2>/dev/null && echo "✅ L1 connection successful" || echo "❌ L1 connection failed"
+
+deps: ## Install development dependencies with mise (optional)
+ mise install
+
+download: ## Download op-deployer binary
+ ./scripts/download-op-deployer.sh
+
+init: ## Initialize the project
+ ./scripts/download-op-deployer.sh
+ @if [ ! -f .env ]; then \
+ cp .example.env .env; \
+ echo "Please edit .env with your actual configuration values"; \
+ else \
+ echo ".env already exists, skipping copy"; \
+ fi
+ @echo "Then run: make setup"
diff --git a/create-l2-rollup-example/README.md b/create-l2-rollup-example/README.md
new file mode 100644
index 000000000..5d3c60f84
--- /dev/null
+++ b/create-l2-rollup-example/README.md
@@ -0,0 +1,215 @@
+# Create L2 Rollup - Code Example
+
+This directory contains the complete working implementation that accompanies the [Create L2 Rollup tutorial](/operators/chain-operators/tutorials/create-l2-rollup). It provides automated deployment of an OP Stack L2 rollup testnet using official published Docker images.
+
+## Overview
+
+This implementation deploys a fully functional OP Stack L2 rollup testnet, including:
+
+- **L1 Smart Contracts** deployed on Sepolia testnet (via op-deployer)
+- **Execution Client** (op-geth) 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
+
+## Prerequisites
+
+### Software Dependencies
+
+| Dependency | Version | Install Command | Purpose |
+|------------|---------|-----------------|---------|
+| [Docker](https://docs.docker.com/get-docker/) | ^24 | Follow Docker installation guide | Container runtime for OP Stack services |
+| [Docker Compose](https://docs.docker.com/compose/install/) | latest | Usually included with Docker Desktop | Multi-container orchestration |
+| [jq](https://github.com/jqlang/jq) | latest | `brew install jq` (macOS) / `apt install jq` (Ubuntu) | JSON processing for deployment data |
+| [git](https://git-scm.com/) | latest | Usually pre-installed | Cloning repositories for prestate generation |
+
+### Recommended: Tool Management
+
+For the best experience with correct tool versions, we recommend installing [mise](https://mise.jdx.dev/):
+
+```bash
+# Install mise (manages all tool versions automatically)
+curl https://mise.jdx.dev/install.sh | bash
+
+# Install all required tools with correct versions
+cd docs/create-l2-rollup-example
+mise install
+```
+
+**Why mise?** It automatically handles tool installation and version management, preventing compatibility issues.
+
+### Network Access
+
+- **Sepolia RPC URL**: Get from [Infura](https://infura.io), [Alchemy](https://alchemy.com), or another provider
+- **Sepolia ETH**: At least 2-3 ETH for contract deployment and operations
+- **Public IP**: For P2P networking (use `curl ifconfig.me` to find your public IP)
+
+## Quick Start
+
+1. **Navigate to this code directory**:
+ ```bash
+ cd docs/pages/operators/chain-operators/tutorials/create-l2-rollup/code
+ ```
+
+2. **Configure environment variables**:
+ ```bash
+ cp .example.env .env
+ # Edit .env with your actual values (L1_RPC_URL, PRIVATE_KEY, L2_CHAIN_ID)
+ ```
+
+3. **Download op-deployer**:
+ ```bash
+ make init # Download op-deployer
+ ```
+
+4. **Deploy and start everything**:
+ ```bash
+ make setup # Deploy contracts and configure all services
+ make up # Start all services
+ ```
+
+5. **Verify deployment**:
+ ```bash
+ make status # Check service health
+ make test-l1 # Verify L1 connectivity
+ make test-l2 # Verify L2 functionality
+
+ # Or manually check:
+ docker-compose ps
+ docker-compose logs -f op-node
+ ```
+
+## Environment Configuration
+
+Copy `.example.env` to `.env` and configure the following variables:
+
+```bash
+# L1 Configuration (Sepolia testnet)
+# Option 1: Public endpoint (no API key required)
+L1_RPC_URL="https://ethereum-sepolia-rpc.publicnode.com"
+L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com"
+
+# Option 2: Private endpoint (requires API key)
+# L1_RPC_URL="https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY"
+# L1_BEACON_URL="https://ethereum-sepolia-beacon-api.publicnode.com"
+
+# Private key for deployment and operations
+# IMPORTANT: Never commit this to version control
+PRIVATE_KEY="YOUR_PRIVATE_KEY_WITHOUT_0x_PREFIX"
+
+# Optional: Public IP for P2P networking (defaults to 127.0.0.1 for local testing)
+P2P_ADVERTISE_IP="127.0.0.1"
+
+# Optional: Custom L2 Chain ID (default: 16584)
+L2_CHAIN_ID="16584"
+```
+
+The `.env` file will be automatically loaded by Docker Compose.
+
+## Manual Setup (Alternative)
+
+For detailed manual setup instructions, see the [Create L2 Rollup tutorial](/operators/chain-operators/tutorials/create-l2-rollup). The tutorial provides step-by-step guidance for setting up each component individually if you prefer not to use the automated approach.
+
+## Directory Structure
+
+```
+code/
+├── .example.env # Environment variables template
+├── docker-compose.yml # Service orchestration
+├── Makefile # Automation commands
+├── scripts/
+│ ├── setup-rollup.sh # Automated deployment script
+│ └── download-op-deployer.sh # op-deployer downloader
+└── README.md # This file
+```
+
+**Generated directories** (created during deployment):
+```
+deployer/ # op-deployer configuration and outputs
+├── .deployer/ # Deployment artifacts (genesis.json, rollup.json, etc.)
+├── addresses/ # Generated wallet addresses
+└── .env # Environment variables
+batcher/ # op-batcher configuration
+├── .env # Batcher-specific environment variables
+proposer/ # op-proposer configuration
+├── .env # Proposer-specific environment variables
+challenger/ # op-challenger configuration
+├── .env # Challenger-specific environment variables
+└── data/ # Challenger data directory
+```
+
+## Service Ports
+
+| Service | Port | Description |
+|---------|------|-------------|
+| op-geth | 8545 | HTTP RPC endpoint |
+| op-geth | 8546 | WebSocket RPC endpoint |
+| op-geth | 8551 | Auth RPC for op-node |
+| op-node | 8547 | op-node RPC endpoint |
+| op-node | 9222 | P2P networking |
+
+## Monitoring and Logs
+
+```bash
+# View all service logs
+make logs
+
+# View specific service logs
+docker-compose logs -f op-node
+docker-compose logs -f op-geth
+
+# Check service health
+make status
+
+# Restart all services
+make restart
+
+# Restart a specific service
+docker-compose restart op-batcher
+```
+
+## Troubleshooting
+
+### Common Issues
+
+1. **Port conflicts**: Ensure ports 8545-8551 and 9222 are available
+2. **Insufficient ETH**: Make sure your deployment wallet has enough Sepolia ETH
+3. **Network timeouts**: Check your L1 RPC URL and network connectivity
+4. **Docker issues**: Ensure Docker daemon is running and you have sufficient resources
+
+### Reset Deployment
+
+To reset and redeploy:
+
+```bash
+# Stop all services and clean up
+make clean
+
+# Re-run setup
+make setup
+make up
+```
+
+## Security Notes
+
+- **Never commit private keys** to version control
+- **Use hardware security modules (HSMs)** for production deployments
+- **Monitor gas costs** on Sepolia testnet
+- **Backup wallet addresses** and deployment artifacts
+
+## About This Code
+
+This code example accompanies the [Create L2 Rollup tutorial](/operators/chain-operators/tutorials/create-l2-rollup) in the Optimism documentation. It provides a complete, working implementation that demonstrates the concepts covered in the tutorial.
+
+## Contributing
+
+This code is part of the Optimism documentation. For issues or contributions:
+
+- **Documentation issues**: Report on the main [Optimism repository](https://github.com/ethereum-optimism/optimism)
+- **Code improvements**: Submit pull requests to the Optimism monorepo
+- **Tutorial feedback**: Use the documentation feedback mechanisms
+
+## License
+
+This code is provided as-is for educational and testing purposes. See the Optimism monorepo for licensing information.
diff --git a/create-l2-rollup-example/docker-compose.yml b/create-l2-rollup-example/docker-compose.yml
new file mode 100644
index 000000000..edb43c96a
--- /dev/null
+++ b/create-l2-rollup-example/docker-compose.yml
@@ -0,0 +1,153 @@
+services:
+ # Sequencer (op-geth + op-node)
+ op-node:
+ image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-node:v1.13.5
+ container_name: op-node
+ volumes:
+ - ./sequencer:/workspace
+ - op_geth_data:/workspace/op-geth-data
+ working_dir: /workspace
+ env_file:
+ - .env
+ ports:
+ - "8547:8547"
+ - "9222:9222"
+ command: >
+ op-node
+ --l1=${L1_RPC_URL}
+ --l1.beacon=${L1_BEACON_URL}
+ --l2=http://op-geth:8551
+ --l2.jwt-secret=/workspace/jwt.txt
+ --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
+ depends_on:
+ - op-geth
+ healthcheck:
+ test: ["CMD", "wget", "--post-data={\"jsonrpc\":\"2.0\",\"method\":\"opp2p_self\",\"params\":[],\"id\":1}", "--header=Content-Type: application/json", "--quiet", "--tries=1", "--timeout=10", "--output-document=-", "http://localhost:8547"]
+ interval: 30s
+ timeout: 10s
+ retries: 5
+ start_period: 10s
+
+ op-geth:
+ image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101511.1
+ container_name: op-geth
+ volumes:
+ - ./sequencer:/workspace
+ - op_geth_data:/workspace/op-geth-data
+ working_dir: /workspace
+ env_file:
+ - .env
+ ports:
+ - "8545:8545"
+ - "8546:8546"
+ - "8551:8551"
+ entrypoint: ["/bin/sh", "-c"]
+ command:
+ - |
+ set -e
+ if [ ! -d /workspace/op-geth-data/geth/chaindata ]; then
+ echo "Initializing geth datadir..."
+ geth init --datadir=/workspace/op-geth-data --state.scheme=hash /workspace/genesis.json
+ fi
+ echo "Starting geth..."
+ exec geth --datadir=/workspace/op-geth-data --http --http.addr=0.0.0.0 --http.port=8545 --ws --ws.addr=0.0.0.0 --ws.port=8546 --authrpc.addr=0.0.0.0 --authrpc.port=8551 --authrpc.jwtsecret=/workspace/jwt.txt --syncmode=full --gcmode=archive --rollup.disabletxpoolgossip=true --rollup.sequencerhttp=http://op-node:8547 --http.vhosts=* --http.corsdomain=* --http.api=eth,net,web3,debug,txpool,admin --ws.origins=* --ws.api=eth,net,web3,debug,txpool,admin --authrpc.vhosts=*
+
+
+ # Batcher
+ batcher:
+ image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-batcher:v1.14.0
+ container_name: op-batcher
+ volumes:
+ - ./batcher:/workspace
+ working_dir: /workspace
+ env_file:
+ - ./batcher/.env
+ - .env
+ environment:
+ - OP_BATCHER_L1_ETH_RPC=${L1_RPC_URL}
+ - OP_BATCHER_PRIVATE_KEY=${PRIVATE_KEY}
+ command: >
+ op-batcher
+ --rpc.addr=0.0.0.0
+ --rpc.port=8548
+ --rpc.enable-admin
+ --max-channel-duration=1
+ --data-availability-type=calldata
+ --resubmission-timeout=30s
+ --log.level=info
+ --log.format=json
+ depends_on:
+ op-node:
+ condition: service_healthy
+
+ # Proposer
+ proposer:
+ image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-proposer:v1.10.0
+ container_name: op-proposer
+ volumes:
+ - ./proposer:/workspace
+ working_dir: /workspace
+ env_file:
+ - ./proposer/.env
+ - .env
+ environment:
+ - OP_PROPOSER_L1_ETH_RPC=${L1_RPC_URL}
+ - OP_PROPOSER_PRIVATE_KEY=${PRIVATE_KEY}
+ command: >
+ op-proposer
+ --rpc.port=8560
+ --rollup-rpc=http://op-node:8547
+ --allow-non-finalized=true
+ --wait-node-sync=true
+ --log.level=info
+ --log.format=json
+ depends_on:
+ op-node:
+ condition: service_healthy
+
+ # Challenger
+ challenger:
+ image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.1
+ container_name: op-challenger
+ volumes:
+ - ./challenger:/workspace
+ working_dir: /workspace
+ env_file:
+ - ./challenger/.env
+ - .env
+ environment:
+ - OP_CHALLENGER_L1_ETH_RPC=${L1_RPC_URL}
+ - OP_CHALLENGER_L1_BEACON=${L1_BEACON_URL}
+ - OP_CHALLENGER_PRIVATE_KEY=${PRIVATE_KEY}
+ command: >
+ op-challenger run-trace
+ --trace-type=cannon
+ --cannon-l2-genesis=/workspace/genesis.json
+ --cannon-rollup-config=/workspace/rollup.json
+ --l2-eth-rpc=http://op-geth:8545
+ --rollup-rpc=http://op-node:8547
+ --datadir=/workspace/data
+ --log.level=info
+ --log.format=json
+ depends_on:
+ op-node:
+ condition: service_healthy
+
+volumes:
+ op_geth_data:
diff --git a/create-l2-rollup-example/mise.toml b/create-l2-rollup-example/mise.toml
new file mode 100644
index 000000000..d22bdca6e
--- /dev/null
+++ b/create-l2-rollup-example/mise.toml
@@ -0,0 +1,8 @@
+[tools]
+"jq" = "latest"
+"git" = "latest"
+"docker" = "latest"
+"foundry" = "latest"
+
+[settings]
+experimental = true
diff --git a/create-l2-rollup-example/scripts/download-op-deployer.sh b/create-l2-rollup-example/scripts/download-op-deployer.sh
new file mode 100755
index 000000000..c49a7540c
--- /dev/null
+++ b/create-l2-rollup-example/scripts/download-op-deployer.sh
@@ -0,0 +1,251 @@
+#!/bin/bash
+
+# Download op-deployer pre-built binary
+# Based on the tutorial instructions
+
+set -e
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+log_info() {
+ echo -e "${BLUE}[INFO]${NC} $1"
+}
+
+log_success() {
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
+}
+
+log_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+log_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Detect platform
+detect_platform() {
+ case "$(uname -s)" in
+ Darwin)
+ case "$(uname -m)" in
+ arm64)
+ echo "darwin-arm64"
+ ;;
+ x86_64)
+ echo "darwin-amd64"
+ ;;
+ *)
+ log_error "Unsupported macOS architecture: $(uname -m)"
+ exit 1
+ ;;
+ esac
+ ;;
+ Linux)
+ echo "linux-amd64"
+ ;;
+ *)
+ log_error "Unsupported platform: $(uname -s)"
+ exit 1
+ ;;
+ esac
+}
+
+# Download op-deployer
+download_op_deployer() {
+ # Detect host platform
+ local os
+ local arch
+ case "$(uname -s)" in
+ Darwin)
+ os="darwin"
+ ;;
+ Linux)
+ os="linux"
+ ;;
+ *)
+ log_error "Unsupported OS: $(uname -s)"
+ exit 1
+ ;;
+ esac
+
+ case "$(uname -m)" in
+ aarch64|arm64)
+ arch="arm64"
+ ;;
+ x86_64|amd64)
+ arch="amd64"
+ ;;
+ *)
+ log_error "Unsupported architecture: $(uname -m)"
+ exit 1
+ ;;
+ esac
+ local platform="$os-$arch"
+ local releases_url="https://api.github.com/repos/ethereum-optimism/optimism/releases"
+
+ log_info "Detecting platform: $platform"
+
+ # Find the latest op-deployer release
+ log_info "Finding latest op-deployer release..."
+
+ # Get all releases and find the latest one with op-deployer assets
+ local latest_deployer_release
+ latest_deployer_release=$(curl -s "$releases_url?per_page=50" | jq -r '.[] | select(.tag_name | startswith("op-deployer/")) | .tag_name' | sort -V | tail -1)
+
+ if [ -z "$latest_deployer_release" ]; then
+ log_error "Could not find any op-deployer releases"
+ exit 1
+ fi
+
+ local tag_name="$latest_deployer_release"
+ log_info "Found latest op-deployer release: $tag_name"
+
+ # Get the release info
+ local release_info
+ release_info=$(curl -s "$releases_url/tags/$tag_name")
+
+ if [ -z "$release_info" ] || [ "$release_info" = "null" ] || echo "$release_info" | jq -e '.message' >/dev/null 2>&1; then
+ log_error "Could not fetch release information for $tag_name"
+ exit 1
+ fi
+
+ local has_deployer_assets
+ has_deployer_assets=$(echo "$release_info" | jq -r '.assets[] | select(.name | contains("op-deployer")) | .name' | wc -l)
+
+ if [ "$has_deployer_assets" -eq 0 ]; then
+ log_error "Release $tag_name does not have op-deployer assets"
+ exit 1
+ fi
+
+ log_info "Release $tag_name has op-deployer assets"
+
+ # Check if this release has op-deployer assets for our platform
+ local asset_name
+ asset_name=$(echo "$release_info" | jq -r ".assets[] | select(.name | contains(\"op-deployer\") and contains(\"$platform\")) | .name")
+
+ # If arm64 is not available, fall back to amd64 (Rosetta compatibility)
+ if [ -z "$asset_name" ] && [ "$platform" = "linux-arm64" ]; then
+ log_info "linux-arm64 not available, trying linux-amd64 (Rosetta compatible)"
+ platform="linux-amd64"
+ asset_name=$(echo "$release_info" | jq -r ".assets[] | select(.name | contains(\"op-deployer\") and contains(\"$platform\")) | .name")
+ fi
+
+ if [ -z "$asset_name" ]; then
+ log_error "Could not find op-deployer asset for platform $platform in release $tag_name"
+ log_info "Available op-deployer assets in this release:"
+ echo "$release_info" | jq -r '.assets[] | select(.name | contains("op-deployer")) | .name'
+ log_info "Please check: https://github.com/ethereum-optimism/optimism/releases/tag/$tag_name"
+ exit 1
+ fi
+
+ local download_url="https://github.com/ethereum-optimism/optimism/releases/download/$tag_name/$asset_name"
+
+ log_info "Downloading op-deployer $tag_name for $platform..."
+ log_info "From: $download_url"
+
+ # Download the asset
+ if ! curl -L -o "op-deployer.tar.gz" "$download_url"; then
+ log_error "Failed to download op-deployer"
+ exit 1
+ fi
+
+ # Extract (assuming it's a tar.gz)
+ log_info "Extracting op-deployer..."
+ tar -xzf op-deployer.tar.gz
+
+ # Find the binary (it might be in a subdirectory)
+ local binary_path
+
+ # Look for the extracted directory first
+ local extracted_dir
+ extracted_dir=$(find . -name "op-deployer-*" -type d | head -1)
+
+ if [ -n "$extracted_dir" ] && [ -f "$extracted_dir/op-deployer" ]; then
+ binary_path="$extracted_dir/op-deployer"
+ elif [ -f "op-deployer" ]; then
+ binary_path="op-deployer"
+ elif [ -f "op-deployer-$platform" ]; then
+ binary_path="op-deployer-$platform"
+ else
+ # Look for it anywhere (macOS compatible)
+ binary_path=$(find . -name "op-deployer*" -type f -perm +111 | head -1)
+ fi
+
+ if [ -z "$binary_path" ]; then
+ log_error "Could not find op-deployer binary in extracted files"
+ ls -la
+ exit 1
+ fi
+
+ # Make executable and move to current directory
+ chmod +x "$binary_path"
+ mv "$binary_path" ./op-deployer
+
+ # Cleanup
+ rm -f op-deployer.tar.gz
+ rm -rf "${binary_path%/*}" 2>/dev/null || true
+
+ # Test the binary (only if we're downloading for the same platform)
+ local current_platform=$(detect_platform)
+ if [ "$platform" = "$current_platform" ]; then
+ if ! ./op-deployer --version >/dev/null 2>&1; then
+ log_error "Downloaded op-deployer binary appears to be invalid"
+ exit 1
+ fi
+ log_success "op-deployer downloaded and ready: $(./op-deployer --version)"
+ else
+ # Cross-platform download - just verify the file exists and is executable
+ if [ ! -x "./op-deployer" ]; then
+ log_error "Downloaded op-deployer binary is not executable"
+ exit 1
+ fi
+ log_success "op-deployer downloaded for $platform platform (cannot test on $current_platform host)"
+ fi
+}
+
+# Alternative: Manual download instructions
+show_manual_instructions() {
+ log_warning "Automatic download failed. Please download manually:"
+ echo ""
+ echo "1. Go to: https://github.com/ethereum-optimism/optimism/releases"
+ echo "2. Find the latest release that includes op-deployer"
+ echo "3. Download the appropriate binary for your system:"
+ echo " - Linux: op-deployer-linux-amd64"
+ echo " - macOS Intel: op-deployer-darwin-amd64"
+ echo " - macOS Apple Silicon: op-deployer-darwin-arm64"
+ echo "4. Extract and place the binary as './op-deployer'"
+ echo "5. Run: chmod +x ./op-deployer"
+ echo ""
+ exit 1
+}
+
+# Main execution
+main() {
+ log_info "Downloading op-deployer..."
+
+ if ! command -v curl &> /dev/null; then
+ log_error "curl is required but not installed"
+ exit 1
+ fi
+
+ if ! command -v jq &> /dev/null; then
+ log_error "jq is required but not installed"
+ exit 1
+ fi
+
+ # Try automatic download
+ if download_op_deployer; then
+ log_success "op-deployer setup complete!"
+ exit 0
+ else
+ show_manual_instructions
+ fi
+}
+
+# Run main function
+main "$@"
diff --git a/create-l2-rollup-example/scripts/setup-rollup.sh b/create-l2-rollup-example/scripts/setup-rollup.sh
new file mode 100755
index 000000000..efca586f2
--- /dev/null
+++ b/create-l2-rollup-example/scripts/setup-rollup.sh
@@ -0,0 +1,488 @@
+#!/bin/bash
+
+# OP Stack L2 Rollup Setup Script
+# This script automates the deployment of a complete L2 rollup testnet
+
+set -e
+
+# Check if running in Docker
+if [ -f "/.dockerenv" ]; then
+ log_error "This script should not be run inside Docker. Run it on the host system."
+ exit 1
+fi
+
+# Colors for output
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+BLUE='\033[0;34m'
+NC='\033[0m' # No Color
+
+# Configuration
+L1_CHAIN_ID=11155111 # Sepolia
+L2_CHAIN_ID_DECIMAL=${L2_CHAIN_ID:-16584} # Default test chain ID (decimal)
+L2_CHAIN_ID=$(printf "0x%064x" "$L2_CHAIN_ID_DECIMAL") # Convert to full 64-char hex format for TOML
+P2P_ADVERTISE_IP=${P2P_ADVERTISE_IP:-127.0.0.1} # Default to localhost for local testing
+WORKSPACE_DIR="$(pwd)"
+ROLLUP_DIR="$WORKSPACE_DIR"
+DEPLOYER_DIR="$ROLLUP_DIR/deployer"
+SEQUENCER_DIR="$ROLLUP_DIR/sequencer"
+BATCHER_DIR="$ROLLUP_DIR/batcher"
+PROPOSER_DIR="$ROLLUP_DIR/proposer"
+CHALLENGER_DIR="$ROLLUP_DIR/challenger"
+
+# Logging functions
+log_info() {
+ echo -e "${BLUE}[INFO]${NC} $1"
+}
+
+log_success() {
+ echo -e "${GREEN}[SUCCESS]${NC} $1"
+}
+
+log_warning() {
+ echo -e "${YELLOW}[WARNING]${NC} $1"
+}
+
+log_error() {
+ echo -e "${RED}[ERROR]${NC} $1"
+}
+
+# Check prerequisites
+check_prerequisites() {
+ log_info "Checking prerequisites..."
+
+ if ! command -v op-deployer &> /dev/null; then
+ log_error "op-deployer not found. Please ensure it's downloaded"
+ log_info "Run: make download"
+ exit 1
+ fi
+
+ log_success "Prerequisites check passed"
+}
+
+# Generate wallet addresses
+generate_addresses() {
+ log_info "Generating wallet addresses..."
+
+ mkdir -p "$DEPLOYER_DIR/addresses"
+ log_info "Created addresses directory: $DEPLOYER_DIR/addresses"
+
+ cd "$DEPLOYER_DIR/addresses"
+ log_info "Changed to directory: $(pwd)"
+
+ # Generate addresses for different roles using openssl
+ for role in admin base_fee_vault_recipient l1_fee_vault_recipient sequencer_fee_vault_recipient system_config unsafe_block_signer batcher proposer challenger; do
+ # Generate a random 32-byte private key, ensuring it's not zero
+ private_key=""
+ while [ -z "$private_key" ] || [ "$private_key" = "0000000000000000000000000000000000000000000000000000000000000000" ]; do
+ private_key=$(openssl rand -hex 32)
+ done
+
+ # For this demo, we'll create a fake but valid Ethereum address
+ # In a real scenario, you'd derive the actual Ethereum address from the private key
+ # Create a 40-character hex address (20 bytes)
+ address="0x$(echo "$private_key" | head -c 40)"
+ echo "$address" > "${role}_address.txt"
+ log_info "Created wallet for $role: $address"
+ done
+
+ log_success "Wallet addresses generated"
+ log_info "Addresses generated successfully, continuing to init..."
+}
+
+# Initialize op-deployer
+init_deployer() {
+ log_info "Initializing op-deployer..."
+
+ cd "$DEPLOYER_DIR"
+
+ # Create .env file
+ cat > .env << EOF
+L1_RPC_URL=$L1_RPC_URL
+PRIVATE_KEY=$PRIVATE_KEY
+EOF
+
+ # Remove any existing .deployer directory for clean state
+ rm -rf .deployer
+
+ # Initialize intent
+ op-deployer init \
+ --l1-chain-id $L1_CHAIN_ID \
+ --l2-chain-ids "$L2_CHAIN_ID_DECIMAL" \
+ --workdir .deployer \
+ --intent-type standard-overrides
+
+ log_success "op-deployer initialized"
+}
+
+# Update intent configuration
+update_intent() {
+ log_info "Updating intent configuration..."
+
+ # Read generated addresses
+ BASE_FEE_VAULT_ADDR=$(cat addresses/base_fee_vault_recipient_address.txt)
+ L1_FEE_VAULT_ADDR=$(cat addresses/l1_fee_vault_recipient_address.txt)
+ SEQUENCER_FEE_VAULT_ADDR=$(cat addresses/sequencer_fee_vault_recipient_address.txt)
+ SYSTEM_CONFIG_ADDR=$(cat addresses/system_config_address.txt)
+ UNSAFE_BLOCK_SIGNER_ADDR=$(cat addresses/unsafe_block_signer_address.txt)
+ BATCHER_ADDR=$(cat addresses/batcher_address.txt)
+ PROPOSER_ADDR=$(cat addresses/proposer_address.txt)
+ CHALLENGER_ADDR=$(cat addresses/challenger_address.txt)
+
+ # Keep the default contract locators and opcmAddress from op-deployer init
+
+ # Update only the chain-specific fields in the existing intent.toml
+ L2_CHAIN_ID_HEX=$(printf "0x%064x" "$L2_CHAIN_ID")
+ sed -i.bak "s|id = .*|id = \"$L2_CHAIN_ID_HEX\"|" .deployer/intent.toml
+ sed -i.bak "s|baseFeeVaultRecipient = .*|baseFeeVaultRecipient = \"$BASE_FEE_VAULT_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|l1FeeVaultRecipient = .*|l1FeeVaultRecipient = \"$L1_FEE_VAULT_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|sequencerFeeVaultRecipient = .*|sequencerFeeVaultRecipient = \"$SEQUENCER_FEE_VAULT_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|systemConfigOwner = .*|systemConfigOwner = \"$SYSTEM_CONFIG_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|unsafeBlockSigner = .*|unsafeBlockSigner = \"$UNSAFE_BLOCK_SIGNER_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|batcher = .*|batcher = \"$BATCHER_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|proposer = .*|proposer = \"$PROPOSER_ADDR\"|" .deployer/intent.toml
+ sed -i.bak "s|challenger = .*|challenger = \"$CHALLENGER_ADDR\"|" .deployer/intent.toml
+
+ log_success "Intent configuration updated"
+}
+
+# Deploy L1 contracts
+deploy_contracts() {
+ log_info "Deploying L1 contracts..."
+
+ cd "$DEPLOYER_DIR"
+
+ op-deployer apply \
+ --workdir .deployer \
+ --l1-rpc-url "$L1_RPC_URL" \
+ --private-key "$PRIVATE_KEY"
+
+ log_success "L1 contracts deployed"
+}
+
+# Generate chain configuration
+generate_config() {
+ log_info "Generating chain configuration..."
+
+ cd "$DEPLOYER_DIR"
+
+ op-deployer inspect genesis --workdir .deployer "$L2_CHAIN_ID" > .deployer/genesis.json
+ op-deployer inspect rollup --workdir .deployer "$L2_CHAIN_ID" > .deployer/rollup.json
+
+ log_success "Chain configuration generated"
+}
+
+# Setup sequencer
+setup_sequencer() {
+ log_info "Setting up sequencer..."
+
+ mkdir -p "$SEQUENCER_DIR"
+ cd "$SEQUENCER_DIR"
+
+ # Copy configuration files
+ cp "$DEPLOYER_DIR/.deployer/genesis.json" .
+ cp "$DEPLOYER_DIR/.deployer/rollup.json" .
+
+ # Generate JWT secret
+ openssl rand -hex 32 > jwt.txt
+ chmod 600 jwt.txt
+
+ # Create .env file
+ cat > .env << EOF
+L1_RPC_URL=$L1_RPC_URL
+L1_BEACON_URL=$L1_BEACON_URL
+PRIVATE_KEY=$PRIVATE_KEY
+P2P_ADVERTISE_IP=$P2P_ADVERTISE_IP
+L2_CHAIN_ID=$L2_CHAIN_ID
+EOF
+
+ log_success "Sequencer setup complete"
+}
+
+# Setup batcher
+setup_batcher() {
+ log_info "Setting up batcher..."
+
+ mkdir -p "$BATCHER_DIR"
+ cd "$BATCHER_DIR"
+
+ # Copy state file
+ cp "$DEPLOYER_DIR/.deployer/state.json" .
+ INBOX_ADDRESS=$(cat state.json | jq -r '.opChainDeployments[0].SystemConfigProxy')
+
+ # Create .env file with OP_BATCHER prefixed variables
+ cat > .env << EOF
+OP_BATCHER_L2_ETH_RPC=http://op-geth:8545
+OP_BATCHER_ROLLUP_RPC=http://op-node:8547
+OP_BATCHER_PRIVATE_KEY=$PRIVATE_KEY
+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_INBOX_ADDRESS=$INBOX_ADDRESS
+EOF
+
+ log_success "Batcher setup complete"
+}
+
+# Setup proposer
+setup_proposer() {
+ log_info "Setting up proposer..."
+
+ mkdir -p "$PROPOSER_DIR"
+ cd "$PROPOSER_DIR"
+
+ # Copy state file
+ cp "$DEPLOYER_DIR/.deployer/state.json" .
+
+ # Extract dispute game factory address
+ GAME_FACTORY_ADDR=$(cat state.json | jq -r '.opChainDeployments[0].DisputeGameFactoryProxy')
+
+ # Create .env file with OP_PROPOSER prefixed variables
+ cat > .env << EOF
+OP_PROPOSER_GAME_FACTORY_ADDRESS=$GAME_FACTORY_ADDR
+OP_PROPOSER_PRIVATE_KEY=$PRIVATE_KEY
+OP_PROPOSER_POLL_INTERVAL=20s
+OP_PROPOSER_GAME_TYPE=0
+OP_PROPOSER_PROPOSAL_INTERVAL=3600s
+EOF
+
+ log_success "Proposer setup complete"
+}
+
+# Setup challenger
+setup_challenger() {
+ log_info "Setting up challenger..."
+
+ mkdir -p "$CHALLENGER_DIR"
+ cd "$CHALLENGER_DIR"
+
+ # Copy configuration files
+ cp "$DEPLOYER_DIR/.deployer/genesis.json" .
+ cp "$DEPLOYER_DIR/.deployer/rollup.json" .
+
+ # Extract dispute game factory address
+ GAME_FACTORY_ADDR=$(cat ../deployer/.deployer/state.json | jq -r '.opChainDeployments[0].DisputeGameFactoryProxy')
+
+ # Check for existing prestate file
+ CHALLENGER_PRESTATE_FILE=""
+ if [ -d "$CHALLENGER_DIR" ]; then
+ # Look for any .bin.gz file in the challenger directory
+ EXISTING_PRESTATE=$(find "$CHALLENGER_DIR" -maxdepth 1 -name "*.bin.gz" -print -quit 2>/dev/null)
+ if [ -n "$EXISTING_PRESTATE" ]; then
+ CHALLENGER_PRESTATE_FILE=$(basename "$EXISTING_PRESTATE")
+ log_info "Found existing prestate file: $CHALLENGER_PRESTATE_FILE"
+ fi
+ fi
+
+ # Ensure prestate file exists
+ if [ -z "$CHALLENGER_PRESTATE_FILE" ] || [ ! -f "$CHALLENGER_DIR/$CHALLENGER_PRESTATE_FILE" ]; then
+ log_error "Challenger prestate file not found in $CHALLENGER_DIR/"
+ log_error "Make sure generate_challenger_prestate runs before setup_challenger"
+ return 1
+ fi
+
+ # Create .env file with OP_CHALLENGER prefixed variables
+ cat > .env << EOF
+OP_CHALLENGER_GAME_FACTORY_ADDRESS=$GAME_FACTORY_ADDR
+OP_CHALLENGER_PRIVATE_KEY=$PRIVATE_KEY
+OP_CHALLENGER_CANNON_PRESTATE=/workspace/$CHALLENGER_PRESTATE_FILE
+EOF
+
+ log_success "Challenger setup complete"
+}
+
+# Validate main .env file for docker-compose
+validate_main_env() {
+ log_info "Validating .env file..."
+
+ # Load user-provided .env file
+ USER_ENV_FILE="$(dirname "$0")/../.env"
+ if [ ! -f "$USER_ENV_FILE" ]; then
+ log_error ".env file not found. Please run 'make init' first."
+ log_info "Run: make init"
+ return 1
+ fi
+
+ log_info "Loading .env file..."
+ set -a # automatically export all variables
+ source "$USER_ENV_FILE"
+ set +a
+
+ # Validate required environment variables
+ if [ -z "$L1_RPC_URL" ]; then
+ log_error "L1_RPC_URL is not set. Please set it in your .env file."
+ log_info "Example: L1_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com"
+ return 1
+ fi
+
+ if [ -z "$L1_BEACON_URL" ]; then
+ log_error "L1_BEACON_URL is not set. Please set it in your .env file."
+ log_info "Example: L1_BEACON_URL=https://ethereum-sepolia-beacon-api.publicnode.com"
+ return 1
+ fi
+
+ if [ -z "$PRIVATE_KEY" ]; then
+ log_error "PRIVATE_KEY is not set. Please set it in your .env file."
+ log_info "Example: PRIVATE_KEY=0x..."
+ return 1
+ fi
+
+ if [ -z "$L2_CHAIN_ID" ]; then
+ log_error "L2_CHAIN_ID is not set. Please set it in your .env file."
+ log_info "Example: L2_CHAIN_ID=16584"
+ return 1
+ fi
+
+ # Set defaults for optional values
+ P2P_ADVERTISE_IP=${P2P_ADVERTISE_IP:-127.0.0.1}
+
+ log_success ".env file validated"
+}
+
+# Generate challenger prestate
+generate_challenger_prestate() {
+ log_info "Generating challenger prestate..."
+
+ # Get the chain ID from the rollup config
+ if [ -f "$DEPLOYER_DIR/.deployer/rollup.json" ]; then
+ CHAIN_ID=$(jq -r '.l2_chain_id' "$DEPLOYER_DIR/.deployer/rollup.json")
+ else
+ log_error "Could not find rollup.json to determine chain ID"
+ return 1
+ fi
+
+ log_info "Chain ID for prestate generation: $CHAIN_ID"
+
+ # Create optimism directory for prestate generation
+ OPTIMISM_DIR="$ROLLUP_DIR/optimism"
+ if [ ! -d "$OPTIMISM_DIR" ]; then
+ log_info "Cloning Optimism repository..."
+ git clone https://github.com/ethereum-optimism/optimism.git "$OPTIMISM_DIR"
+ cd "$OPTIMISM_DIR"
+
+ # Find the latest op-program tag
+ log_info "Finding latest op-program version..."
+ OP_PROGRAM_TAG=$(git tag --list "op-program/v*" | sort -V | tail -1)
+ if [ -z "$OP_PROGRAM_TAG" ]; then
+ log_error "Could not find any op-program tags"
+ return 1
+ fi
+ log_info "Using op-program version: $OP_PROGRAM_TAG"
+
+ git checkout "$OP_PROGRAM_TAG"
+ git submodule update --init --recursive
+ else
+ log_info "Optimism repository already exists, checking configuration..."
+ cd "$OPTIMISM_DIR"
+
+ # Check if we're on the correct op-program tag
+ CURRENT_TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
+ LATEST_TAG=$(git tag --list "op-program/v*" | sort -V | tail -1)
+
+ if [ "$CURRENT_TAG" != "$LATEST_TAG" ]; then
+ log_info "Updating to latest op-program version: $LATEST_TAG"
+ git checkout "$LATEST_TAG"
+ git submodule update --init --recursive
+ else
+ log_info "Already on correct op-program version: $CURRENT_TAG"
+ fi
+ fi
+
+ # Copy configuration files
+ log_info "Copying chain configuration files..."
+ mkdir -p op-program/chainconfig/configs
+ cp "$DEPLOYER_DIR/.deployer/rollup.json" "op-program/chainconfig/configs/${CHAIN_ID}-rollup.json"
+ cp "$DEPLOYER_DIR/.deployer/genesis.json" "op-program/chainconfig/configs/${CHAIN_ID}-genesis.json"
+
+ # Generate prestate
+ log_info "Generating reproducible prestate..."
+ make reproducible-prestate
+
+ # Extract the prestate hash from the JSON file
+ PRESTATE_HASH=$(jq -r '.pre' op-program/bin/prestate-proof-mt64.json)
+ if [ -z "$PRESTATE_HASH" ] || [ "$PRESTATE_HASH" = "null" ]; then
+ log_error "Could not extract prestate hash from prestate-proof-mt64.json"
+ return 1
+ fi
+
+ log_info "Prestate hash: $PRESTATE_HASH"
+
+ # Move the prestate file
+ log_info "Moving prestate file to challenger directory..."
+ mkdir -p "$ROLLUP_DIR/challenger"
+ mv "op-program/bin/prestate-mt64.bin.gz" "$ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz"
+
+ # Verify the prestate file was created successfully
+ if [ ! -f "$ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz" ]; then
+ log_error "Failed to create prestate file: $ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz"
+ return 1
+ fi
+
+ # Clean up
+ cd "$ROLLUP_DIR"
+ # rm -rf "$OPTIMISM_DIR" # Uncomment to clean up after successful generation
+
+ log_success "Challenger prestate generation complete: $ROLLUP_DIR/challenger/${PRESTATE_HASH}.bin.gz"
+}
+
+# Add op-deployer to PATH if it exists in the workspace
+if [ -f "$(dirname "$0")/../op-deployer" ]; then
+ OP_DEPLOYER_PATH="$(cd "$(dirname "$0")/.." && pwd)/op-deployer"
+ export PATH="$(dirname "$OP_DEPLOYER_PATH"):$PATH"
+ log_info "Added op-deployer to PATH: $OP_DEPLOYER_PATH"
+fi
+
+# Main execution
+main() {
+ log_info "Starting OP Stack L2 Rollup deployment..."
+ log_info "L2 Chain ID: $L2_CHAIN_ID"
+ log_info "L1 Chain ID: $L1_CHAIN_ID"
+
+ # Clean start - remove any existing deployer directory
+ log_info "Cleaning up any existing deployment..."
+ rm -rf "$DEPLOYER_DIR"
+ mkdir -p "$DEPLOYER_DIR"
+
+ validate_main_env
+ check_prerequisites
+ generate_addresses
+ init_deployer
+ update_intent
+ deploy_contracts
+ generate_config
+ setup_sequencer
+ setup_batcher
+ setup_proposer
+ generate_challenger_prestate
+ setup_challenger
+
+ log_success "OP Stack L2 Rollup deployment complete!"
+ log_info "Run 'docker-compose up -d' to start all services"
+}
+
+# Handle command line arguments for standalone function calls
+if [ $# -gt 0 ]; then
+ case "$1" in
+ "prestate"|"generate-prestate")
+ log_info "Running standalone prestate generation..."
+
+ # Set up required variables for standalone execution
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ ROLLUP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
+ DEPLOYER_DIR="$ROLLUP_DIR/deployer"
+
+ generate_challenger_prestate
+ exit $?
+ ;;
+ *)
+ log_error "Unknown command: $1"
+ log_info "Available commands: prestate"
+ exit 1
+ ;;
+ esac
+fi
+
+# Run main function if no arguments provided
+main "$@"
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup.mdx b/pages/operators/chain-operators/tutorials/create-l2-rollup.mdx
index a840a2819..8d11c432f 100644
--- a/pages/operators/chain-operators/tutorials/create-l2-rollup.mdx
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup.mdx
@@ -25,8 +25,8 @@ import { Callout, Card, Steps } from 'nextra/components'
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.
+ 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.
@@ -41,6 +41,51 @@ By the end of this tutorial, you'll have a complete OP Stack testnet with:
* **Proposer** (op-proposer) submitting state root proposals
* **Challenger** (op-challenger) monitoring for disputes
+## Quick setup (Recommended)
+
+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/docs/tree/main/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
+ git clone https://github.com/ethereum-optimism/docs.git
+ cd docs/create-l2-rollup-example/
+ ```
+
+2. **Configure your environment:**
+ ```bash
+ cp .example.env .env
+ # Edit .env with your L1_RPC_URL, PRIVATE_KEY, and other settings
+ ```
+
+3. **Run the automated setup:**
+ ```bash
+ 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
+ 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 (Step-by-Step)
+
+If you prefer to understand each component in detail or need custom configurations, follow the step-by-step guide below.
+
## Before you start
### Software dependencies
@@ -153,9 +198,9 @@ Each component's documentation will show you how the directory structure evolves
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.
-## Tutorial overview
+## Manual Tutorial Overview
-This tutorial is organized into sequential steps that build upon each other:
+If you're following the manual setup path, this tutorial is organized into sequential steps that build upon each other:
### **[Spin up op-deployer](/operators/chain-operators/tutorials/create-l2-rollup/op-deployer-setup)**
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup/_meta.json b/pages/operators/chain-operators/tutorials/create-l2-rollup/_meta.json
index 43db0cad0..7d618b2b5 100644
--- a/pages/operators/chain-operators/tutorials/create-l2-rollup/_meta.json
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup/_meta.json
@@ -4,5 +4,6 @@
"op-geth-setup": "Spin up sequencer",
"op-batcher-setup": "Spin up batcher",
"op-proposer-setup": "Spin up proposer",
- "op-challenger-setup": "Spin up challenger"
+ "op-challenger-setup": "Spin up challenger",
+ "code-setup": "Code examples"
}
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup/code-setup.mdx b/pages/operators/chain-operators/tutorials/create-l2-rollup/code-setup.mdx
new file mode 100644
index 000000000..39067ef2f
--- /dev/null
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup/code-setup.mdx
@@ -0,0 +1,70 @@
+---
+title: L2 Rollup Code Examples
+description: Complete working code examples for the Create L2 Rollup tutorial
+lang: en-US
+content_type: reference
+topic: create-l2-rollup-code
+personas:
+ - chain-operator
+categories:
+ - testnet
+ - mainnet
+ - op-stack
+ - code-examples
+is_imported_content: 'false'
+---
+
+import { useEffect } from 'react'
+import { Callout } from 'nextra/components'
+
+# L2 Rollup Code Examples
+
+export default function Page() {
+ useEffect(() => {
+ if (typeof window !== 'undefined') {
+ window.location.replace('https://github.com/ethereum-optimism/docs/tree/main/create-l2-rollup-example/')
+ }
+ }, [])
+ return (
+ <>
+
+ Redirecting to the code on GitHub… If you are not redirected automatically, use this link:{' '}
+ Create L2 Rollup code on GitHub.
+
+ >
+ )
+}
+
+{/* Fallback content only if client-side redirect fails; keep minimal */}
+
+## Quick Start
+
+```bash
+# 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](/operators/chain-operators/tutorials/create-l2-rollup).
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx
index c3015cd20..b4a00ec09 100644
--- a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-batcher-setup.mdx
@@ -26,6 +26,12 @@ After you have spun up your sequencer, you need to configure a batcher to submit
**Step 3 of 5**: This tutorial is designed to be followed step-by-step. Each step builds on the [previous one](/operators/chain-operators/tutorials/create-l2-rollup/op-geth-setup).
+
+ **Automated Setup Available**
+
+ For a complete working setup with all components, check out the [automated approach](https://github.com/ethereum-optimism/docs/tree/main/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:
@@ -77,32 +83,37 @@ For setting up the batcher, we recommend using Docker as it provides a consisten
### Create environment variables file
+
+ **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
# 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
+ 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
- L2_RPC_URL=http://op-geth:8545
- ROLLUP_RPC_URL=http://op-node:8547
+ OP_BATCHER_L2_ETH_RPC=http://op-geth:8545
+ OP_BATCHER_ROLLUP_RPC=http://op-node: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
+ OP_BATCHER_BATCH_INBOX_ADDR=YOUR_ACTUAL_BATCH_INBOX_ADDRESS
# 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
+ 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
- BATCHER_RPC_PORT=8548
+ OP_BATCHER_RPC_PORT=8548
EOF
```
@@ -129,25 +140,13 @@ For setting up the batcher, we recommend using Docker as it provides a consisten
- .env
networks:
- sequencer-node_default
- command:
- - "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"
- - "--max-pending-tx=0"
+ 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:
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx
index 312ad4c33..e66bd32c2 100644
--- a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-challenger-setup.mdx
@@ -26,6 +26,12 @@ After you have spun up your sequencer, batcher, and proposer, the final step is
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/docs/tree/main/create-l2-rollup-example/) in the code directory.
+
+
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](/stack/fault-proofs/challenger) for a general overview of this fault proofs feature.
@@ -138,28 +144,32 @@ For challenger deployment, we recommend using Docker as it provides a consistent
### Create environment file
- First, create a `.env` file with your configuration values. This file will be used by Docker Compose to set up the environment variables:
+
+ **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
# 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=https://ethereum-sepolia-beacon-api.publicnode.com
-
+ # 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
- L2_RPC_URL=http://op-geth:8545
- ROLLUP_RPC_URL=http://op-node:8547
+ OP_CHALLENGER_L2_ETH_RPC=http://op-geth:8545
+ OP_CHALLENGER_ROLLUP_RPC=http://op-node:8547
- # Private key - Replace with your actual private key
- PRIVATE_KEY=YOUR_ACTUAL_PRIVATE_KEY
+ # OP Stack challenger configuration (optional - defaults provided)
+ OP_CHALLENGER_GAME_FACTORY_ADDRESS=YOUR_GAME_FACTORY_ADDRESS
+ OP_CHALLENGER_CANNON_L2_GENESIS=/workspace/genesis.json
+ OP_CHALLENGER_CANNON_ROLLUP_CONFIG=/workspace/rollup.json
- # Network configuration
- NETWORK=op-sepolia
- GAME_FACTORY_ADDRESS=YOUR_GAME_FACTORY_ADDRESS
- # Prestate configuration - Replace with the hash from 'make reproducible-prestate'
- PRESTATE_HASH=YOUR_PRESTATE_HASH
+ # Prestate configuration - Replace with the file from 'make reproducible-prestate'
+ OP_CHALLENGER_CANNON_PRESTATE=/workspace/${PRESTATE_HASH}.bin.gz
EOF
```
@@ -174,7 +184,7 @@ For challenger deployment, we recommend using Docker as it provides a consistent
services:
challenger:
- image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.0
+ image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-challenger:v1.5.1
user: "1000"
volumes:
- ./challenger-data:/data
@@ -182,28 +192,12 @@ For challenger deployment, we recommend using Docker as it provides a consistent
- ./genesis-l2.json:/workspace/genesis-l2.json:ro
- ./prestate-proof-mt64.json:/workspace/prestate-proof.json:ro
- ./${PRESTATE_HASH}.bin.gz:/workspace/${PRESTATE_HASH}.bin.gz:ro
- environment:
- - L1_RPC_URL=${L1_RPC_URL}
- - L1_BEACON=${L1_BEACON}
- - L2_RPC_URL=${L2_RPC_URL}
- - ROLLUP_RPC_URL=${ROLLUP_RPC_URL}
- - PRIVATE_KEY=${PRIVATE_KEY}
- - NETWORK=${NETWORK}
- - GAME_FACTORY_ADDRESS=${GAME_FACTORY_ADDRESS}
- command:
- - "op-challenger"
- - "--trace-type=cannon,asterisc-kona"
- - "--l1-eth-rpc=${L1_RPC_URL}"
- - "--l1-beacon=${L1_BEACON}"
- - "--l2-eth-rpc=${L2_RPC_URL}"
- - "--rollup-rpc=${ROLLUP_RPC_URL}"
- - "--selective-claim-resolution"
- - "--private-key=${PRIVATE_KEY}"
- - "--game-factory-address=${GAME_FACTORY_ADDRESS}"
- - "--datadir=/data"
- - "--cannon-prestate=/workspace/prestate-proof.json"
- - "--cannon-bin=/workspace/${PRESTATE_HASH}.bin.gz"
- - "--asterisc-kona-prestate=/workspace/prestate-proof.json"
+ command: >
+ op-challenger run-trace
+ --trace-type=cannon
+ --datadir=/data
+ --log.level=info
+ --log.format=json
restart: unless-stopped
networks:
- sequencer-node_default
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx
index 24798f405..96a46e117 100644
--- a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-deployer-setup.mdx
@@ -23,6 +23,12 @@ Welcome to the first step of creating your own L2 rollup testnet! In this sectio
**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/docs/tree/main/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.
@@ -35,11 +41,17 @@ 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/docs/tree/main/create-l2-rollup-example/). This script automatically downloads the latest version for your system.
+
+
### Download the correct binary
1. Go to the [release page](https://github.com/ethereum-optimism/optimism/releases)
- 2. Use the search bar to find the latest release that includes `op-deployer`.
+ 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`
@@ -48,6 +60,10 @@ There are a couple of ways to install `op-deployer`:
* 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?
@@ -84,25 +100,30 @@ There are a couple of ways to install `op-deployer`:
```bash
- # Step 1: Extract the tar.gz archive in the deployer directory
- # Replace USERNAME with your username and adjust the version/arch if needed
- tar -xvzf /Users/USERNAME/Downloads/op-deployer-0.2.6-darwin-arm64.tar.gz
+ # 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
- chmod +x op-deployer-0.2.6-darwin-arm64
+ # 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 op-deployer-0.2.6-darwin-arm64
+ sudo xattr -dr com.apple.quarantine FILENAME
# Step 4: Move the binary to your PATH
# For Intel Macs:
- sudo mv op-deployer-0.2.6-darwin-arm64 /usr/local/bin/
+ sudo mv FILENAME /usr/local/bin/op-deployer
# For Apple Silicon Macs:
- sudo mv op-deployer-0.2.6-darwin-arm64 /opt/homebrew/bin/
+ sudo mv FILENAME /opt/homebrew/bin/op-deployer
- # Step 7: Verify installation (should print version info)
+ # 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/docs/tree/main/create-l2-rollup-example/) to avoid manual version management. It automatically detects your platform and downloads the latest version.
+
@@ -206,6 +227,12 @@ The intent file defines your chain's configuration.
--intent-type standard-overrides
```
+
+ **Version Compatibility**
+
+ Each `op-deployer` version is bound to specific `op-contracts` versions. The `l1ContractsLocator` and `l2ContractsLocator` values in your intent file must be compatible with your `op-deployer` version. Check the [op-deployer release notes](https://github.com/ethereum-optimism/optimism/releases) for version compatibility information.
+
+
Understanding intent types
@@ -220,20 +247,25 @@ The intent file defines your chain's configuration.
### Update the intent file
- Edit `.deployer/intent.toml` with your generated addresses:
+ Edit `.deployer/intent.toml` with your generated addresses. The example below shows the typical configuration for a standard OP Stack deployment, with advanced options commented out:
```toml
configType = "standard-overrides"
l1ChainID = 11155111 # Sepolia
fundDevAccounts = false # Set to false for production/testnet
useInterop = false
- l1ContractsLocator = "tag://op-contracts/v2.0.0"
- l2ContractsLocator = "tag://op-contracts/v1.7.0-beta.1+l2-contracts"
- [superchainRoles]
- proxyAdminOwner = "0x..." # admin address
- protocolVersionsOwner = "0x..." # admin address
- guardian = "0x..." # admin address
+ # Contract locators are automatically determined by your op-deployer version
+ # Only uncomment and modify if you need specific contract versions (advanced users only)
+ # l1ContractsLocator = "tag://op-contracts/v2.0.0"
+ # l2ContractsLocator = "tag://op-contracts/v1.7.0-beta.1+l2-contracts"
+
+ # Superchain roles - only define if creating a standalone chain not part of OP Stack superchain
+ # For standard OP Stack deployments, these are predefined and should not be set
+ # [superchainRoles]
+ # proxyAdminOwner = "0x..." # admin address
+ # protocolVersionsOwner = "0x..." # admin address
+ # guardian = "0x..." # admin address
[[chains]]
id = "0x000000000000000000000000000000000000000000000000000000000016de8d"
@@ -245,7 +277,7 @@ The intent file defines your chain's configuration.
eip1559Elasticity = 6
[chains.roles]
l1ProxyAdminOwner = "0x1eb2ffc903729a0f03966b917003800b145f56e2"
- l2ProxyAdminOwner = "0x2fc3ffc903729a0f03966b917003800b145f67f3"
+ l2ProxyAdminOwner = "0x2fc3ffc903729a0f03966b917003800b145f67f3"
systemConfigOwner = "0x..." # system_config address
unsafeBlockSigner = "0x..." # unsafe_block_signer address
batcher = "0x..." # batcher address
@@ -259,32 +291,32 @@ The intent file defines your chain's configuration.
**Global Settings:**
* `l1ChainID`: The L1 network ID (11155111 for Sepolia)
- * `fundDevAccounts`: Creates test accounts with ETH if true
- * `useInterop`: Enable interoperability features
- * `l1ContractsLocator`: Version of L1 contracts to deploy
- * `l2ContractsLocator`: Version of L2 contracts to deploy
+ * `fundDevAccounts`: Creates test accounts with ETH if true (set to false for production)
+ * `useInterop`: Enable interoperability features (false for standard deployments)
+
+ **Contract Locators (Advanced):**
+
+ These are commented out because `op-deployer` automatically determines compatible contract versions. Only uncomment and modify if you need to pin to specific contract versions for advanced use cases.
- **Superchain Roles:**
+ **Superchain Roles (Advanced):**
- * `proxyAdminOwner`: Can upgrade Superchain-wide contracts
- * `protocolVersionsOwner`: Can update protocol versions
- * `guardian`: Can pause withdrawals and manage disputes
+ These are commented out because for standard OP Stack deployments, superchain 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 superchain.
**Chain Configuration:**
* `id`: Unique identifier for your chain
- * `*FeeVaultRecipient`: Addresses receiving various fees
- * `eip1559*`: Parameters for gas price calculation
+ * `*FeeVaultRecipient`: Addresses receiving various protocol fees
+ * `eip1559*`: Parameters for dynamic gas price calculation
**Chain Roles:**
- * `l1ProxyAdminOwner`: Updates L1 contract implementations
- * `l2ProxyAdminOwner`: Updates L2 contract implementations
- * `systemConfigOwner`: Manages system configuration
- * `unsafeBlockSigner`: Signs pre-confirmation blocks
- * `batcher`: Submits L2 transactions to L1
- * `proposer`: Submits L2 state roots to L1
- * `challenger`: Monitors and challenges invalid states
+ * `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
diff --git a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx
index 23b0bdd08..172aa01eb 100644
--- a/pages/operators/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx
+++ b/pages/operators/chain-operators/tutorials/create-l2-rollup/op-proposer-setup.mdx
@@ -26,6 +26,12 @@ After you have spun up your sequencer and batcher, you need to attach a proposer
**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/docs/tree/main/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-geth-setup) and [batcher guide](./op-batcher-setup) first.
To see configuration info for the proposer, check out the [configuration page](/operators/chain-operators/configuration/proposer).
@@ -78,29 +84,33 @@ For setting up the proposer, we recommend using Docker as it provides a consiste
### Create environment variables file
+
+ **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
# 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 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
- L2_RPC_URL=http://op-geth:8545
- ROLLUP_RPC_URL=http://op-node:8547
+ OP_PROPOSER_ROLLUP_RPC=http://op-node:8547
# Contract addresses - Extract from your op-deployer output
- GAME_FACTORY_ADDRESS=YOUR_ACTUAL_GAME_FACTORY_ADDRESS
+ OP_PROPOSER_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
+ 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
```
@@ -130,21 +140,11 @@ For setting up the proposer, we recommend using Docker as it provides a consiste
- "8560:8560"
env_file:
- .env
- command:
- - "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"
+ command: >
+ op-proposer
+ --rpc.port=8560
+ --log.level=info
+ --log.format=json
restart: unless-stopped
networks:
- sequencer-node_default