Deploy L1 contracts with op-deployer

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/ (opens in a new tab) 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 (opens in a new tab).

Quick Setup Available

For automated installation, you can use the download script from the code directory (opens in a new tab). This script automatically downloads the latest version for your system.

Download the correct binary

  1. Go to the release page (opens in a new tab)
  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

Create deployer directory and install binary

  1. Create the rollup directory structure and enter the deployer directory:
# 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:

rollup/
└── deployer/    # You are here
  1. 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
# 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 (opens in a new tab) to avoid manual version management. It automatically detects your platform and downloads the latest version.

L1 network requirements

Before deploying your L1 contracts, you'll need:

L1 RPC URL: An Ethereum RPC endpoint for your chosen L1 network

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

Generate deployment addresses

Your rollup needs several addresses for different roles. Let's generate them first:

Create address directory

# Create a address directory inside the deployer directory
mkdir -p address
cd address

Your directory structure will now look like this:

rollup/
└── deployer/
    └── address/    # You are here

Generate address for each role

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

Initialize intent file

Inside the deployer folder, run this command:

#You can use a 2-7 digit random number for your `<YOUR_CHAIN_ID>` 
op-deployer init \
  --l1-chain-id 11155111 \
  --l2-chain-ids <YOUR_CHAIN_ID> \
  --workdir .deployer \
  --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 (opens in a new tab) for version compatibility information.

Understanding intent types

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.

Update the intent file

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:

configType = "standard-overrides"
l1ChainID = 11155111  # Sepolia
fundDevAccounts = false  # Set to false for production/testnet
useInterop = false
 
# 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"
  baseFeeVaultRecipient = "0x..."    # base_Fee_Vault_Recipient address
  l1FeeVaultRecipient = "0x..."      # l1_Fee_Vault_Recipient address
  sequencerFeeVaultRecipient = "0x..." # sequencer_Fee_Vault_Recipient address
  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"
Understanding the configuration values

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)

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 (Advanced):

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 protocol fees
  • eip1559*: Parameters for dynamic gas price calculation

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:

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

echo ".env" >> .gitignore

Load the environment variables:

source .env

Deploy L1 Contracts

Now that your intent file and environment variables are configured, let's deploy the L1 contracts:

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:

# Generate genesis and rollup configs
op-deployer inspect genesis --workdir .deployer <YOUR_CHAIN_ID> > .deployer/genesis.json
op-deployer inspect rollup --workdir .deployer <YOUR_CHAIN_ID> > .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:

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
        ├── 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
        └── admin.txt
 

Now you can move on to setting up your sequencer node.

Spin up sequencer →

Need Help?