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.
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).
Download the correct binary
- Go to the release page (opens in a new tab)
- Use the search bar to find the latest release that includes
op-deployer
. - 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
- Apple Silicon (M1/M2):
- For Windows:
op-deployer-windows-amd64.exe
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
- 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
- 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 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 2: Make the binary executable
chmod +x op-deployer-0.2.6-darwin-arm64
# 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
# Step 4: Move the binary to your PATH
# For Intel Macs:
sudo mv op-deployer-0.2.6-darwin-arm64 /usr/local/bin/
# For Apple Silicon Macs:
# sudo mv op-deployer-0.2.6-darwin-arm64 /opt/homebrew/bin/
# Step 7: Verify installation (should print version info)
op-deployer --version
L1 network requirements
Before deploying your L1 contracts, you'll need:
L1 RPC URL: An Ethereum RPC endpoint for your chosen L1 network
# Examples:
# Sepolia (recommended for testing)
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR-PROJECT-ID
# or https://eth-sepolia.g.alchemy.com/v2/YOUR-API-KEY
# Local network
L1_RPC_URL=http://localhost:8545
For testing, we recommend using Sepolia testnet. You can get free RPC access from:
- Infura (opens in a new tab) (create account, get API key)
- Alchemy (opens in a new tab) (create account, get API key)
- Ankr (opens in a new tab) (create account, get API key)
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
Save the addresses for your intent file
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
Understanding intent types
op-deployer
supports three intent types:
standard
: Uses default OP Stack configuration, minimal customizationstandard-overrides
: Recommended. Uses defaults but allows overriding specific valuescustom
: 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:
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
[[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 trueuseInterop
: Enable interoperability featuresl1ContractsLocator
: Version of L1 contracts to deployl2ContractsLocator
: Version of L2 contracts to deploy
Superchain Roles:
proxyAdminOwner
: Can upgrade Superchain-wide contractsprotocolVersionsOwner
: Can update protocol versionsguardian
: Can pause withdrawals and manage disputes
Chain Configuration:
id
: Unique identifier for your chain*FeeVaultRecipient
: Addresses receiving various feeseip1559*
: Parameters for gas price calculation
Chain Roles:
l1ProxyAdminOwner
: Updates L1 contract implementationsl2ProxyAdminOwner
: Updates L2 contract implementationssystemConfigOwner
: Manages system configurationunsafeBlockSigner
: Signs pre-confirmation blocksbatcher
: Submits L2 transactions to L1proposer
: Submits L2 state roots to L1challenger
: Monitors and challenges invalid 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:
- Deploy all required L1 contracts
- Configure them according to your intent file
- 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:
- Installed
op-deployer
using theinit
andapply
command. - Created and configured your intent file
- Deployed L1 smart contracts
- 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?
- Community Support: Join the Optimism Discord (opens in a new tab)
- op-deployer Repository: GitHub (opens in a new tab)
- OPCM Documentation: OP Contracts Manager