Deploy OP Stack chain with Avail DA
Introduction
Embark on setting up your own OP stack chain in alt-da mode using Avail as Data Availabilty layer . This guide targets Ethereum’s Sepolia testnet and Avail Turing testnet. For a detailed understanding, review the Optimism Documentation.
Prerequisites
Ensure you have installed the following software.
Installation commands are based on Ubuntu 24.04 LTS:
Software | Version | Version Check Command |
---|---|---|
git | ^2 | git --version |
go | ^1.21 | go version |
node | ^20 | node --version |
pnpm | ^8 | pnpm --version |
foundry | ^0.2.0 | forge --version |
make | ^3 | make --version |
jq | ^1.6 | jq --version |
direnv | ^2 | direnv --version |
Just | 1.35.0 | just --version |
Use the following releases while following the guide:
Component | Version |
---|---|
op-node | v1.13.3 |
op-geth | v1.101511.0 |
op-batcher | v1.13.2 |
op-deployer | v0.3.2 |
op-proposer | v1.10.0 |
Get Access to a Sepolia Node
For deploying to Sepolia, access an L1 node using a provider like Alchemy or run your own Sepolia node.
Setup Avail DA server
This introduces a sidecar DA Server for Optimism that interacts with Avail DA for posting and retrieving data.
Run avail-da server(binary).
-
Build Avail DA Server
git clone https://github.com/availproject/avail-alt-da-server.git cd avail-alt-da-server make da-server
-
Run your DA sidecar:
./bin/avail-da-server ./cmd/avail --addr=localhost --port=8000 --avail.rpc=<WS url to an avail node> --avail.seed=<> --avail.appid=<>
Run using docker
-
Copy
.env.example
to.env
. Fill the values inside. -
Run the following commands:
docker-compose build docker-compose up
Configure op-stack
-
Clone the optimism repositroy:
git clone https://github.com/ethereum-optimism/optimism.git cd optimism git fetch --tag --all git submodule update --init --recursive
-
Add
env
variables at root of the optimism directlyfile-name:
.envrc
################################################## # Getting Started # ################################################## # Admin account export GS_ADMIN_ADDRESS= export GS_ADMIN_PRIVATE_KEY= # Batcher account export GS_BATCHER_ADDRESS= export GS_BATCHER_PRIVATE_KEY= # Proposer account export GS_PROPOSER_ADDRESS= export GS_PROPOSER_PRIVATE_KEY= # Sequencer account export GS_SEQUENCER_ADDRESS= export GS_SEQUENCER_PRIVATE_KEY= ################################################## # op-node Configuration # ################################################## # The kind of RPC provider, used to inform optimal transactions receipts # fetching. Valid options: alchemy, quicknode, infura, parity, nethermind, # debug_geth, erigon, basic, any. export L1_RPC_KIND= export L1_CHAIN_ID= export L2_CHAIN_ID= export L1_BLOCK_TIME=12 export L2_BLOCK_TIME=5 export DA_SERVER_HTTP_URL=http://localhost:8000 export GAME_FACTORY_ADDRESS="" ################################################## # Contract Deployment # ################################################## # RPC URL for the L1 network to interact with export L1_RPC_URL=
Fill out the required details
Variable Name Description L1_RPC_URL URL for your L1 node (a Sepolia node in this case). L1_RPC_KIND Kind of L1 RPC you’re connecting to, used to inform optimal transactions receipts fetching. Valid options: alchemy
,quicknode
,infura
,parity
,nethermind
,debug_geth
,erigon
,basic
,any
.L1_BLOCK_TIME Block time of your respective settlement layer for this L2 layer L2_BLOCK_TIME Block time of your L2 layer L1_CHAIN_ID Chain id of your respective settlement layer for this L2 layer L2_CHAIN_ID Chain id of your L2 layer -
Generate and Secure Keys
Create and fill these four essential accounts with private keys to above created
.envrc
file:-
Admin
(contract upgrade authority) -
Batcher
(publishes Sequencer data to L1) -
Proposer
(publishes L2 results to L1) -
Sequencer
(signs blocks on the p2p network)echo "Admin:" cast wallet new echo "Proposer:" cast wallet new echo "Batcher:" cast wallet new echo "Sequencer:" cast wallet new
You should see an output similar to:
Admin: Successfully created new keypair. Address: 0xc4A01194958DE0D90A876e8A5fc9D7B530072148 Private key: 0xb8e39bd94a210e410c4024e1cc91014de45a5eb1e42f3aa99a368b5a5ac19b45 Proposer: Successfully created new keypair. Address: 0xFC0374Ae658e46cA4022acA179d3cb6D8e1A4934 Private key: 0xa9bc1b3f5deb1e00251df68bf86e3493b25bc5430665433546f2f9aacc748d1a Batcher: Successfully created new keypair. Address: 0xD6857B5BE9468Be67d64ABaB48459378d5329b96 Private key: 0xe9cd8960fc7984a301d567b819e0c62871eb2c7239c2e66b8f319eaa45c3cbd5 Sequencer: Successfully created new keypair. Address: 0x33348817E4B1192D576C4f157e9a5EC93dc5392D Private key: 0xd98b49e11e4e0be9931017831395e6644a50c36285d08e14d1a479af5ee08675
You’ll need to fund
Admin
,Proposer
, andBatcher
with Sepolia ETH (0.5 ETH forAdmin
, 0.2 ETH forProposer
, 0.1 ETH forBatcher
).
⚠️NOTE FOR PRODUCTION
Use secure hardware for key management in production environments.cast wallet
is not designed for production deployments. -
-
Generate a
jwt.txt
file, which is crucial for the secure interaction betweenop-node
andop-geth
client:openssl rand -hex 32 > jwt.txt
Contract Deployment using op-deployer
Deploy essential L1 contracts for the chain’s functionality:
-
Build
op-deplyer
.## op-deployer cd op-deployer git checkout op-deployer/v0.3.2 just build
-
Generate
intent.toml
andstate.json
file for deployment configuration:## Intent and state file intialization ./bin/op-deployer init --l1-chain-id <YOUR_L1_CHAIN_ID> --l2-chain-ids <YOUR_L2_CHAIN_ID> --workdir .deployer --intent-type custom
Edit
intent.toml
fileconfigType = "custom" l1ChainID = 11155111 fundDevAccounts = false useInterop = false l1ContractsLocator = "tag://op-contracts/v3.0.0" l2ContractsLocator = "tag://op-contracts/v3.0.0" [superchainRoles] proxyAdminOwner = "ENTER_ADMIN_ADDRESS" protocolVersionsOwner = "ENTER_ADMIN_ADDRESS" guardian = "ENTER_ADMIN_ADDRESS" [[chains]] id = "ENTER_CHAIN_ID" baseFeeVaultRecipient = "ENTER_ADMIN_ADDRESS" l1FeeVaultRecipient = "ENTER_ADMIN_ADDRESS" sequencerFeeVaultRecipient = "ENTER_ADMIN_ADDRESS" eip1559DenominatorCanyon = 250 eip1559Denominator = 50 eip1559Elasticity = 6 operatorFeeScalar = 0 operatorFeeConstant = 0 [chains.roles] l1ProxyAdminOwner = "ENTER_ADMIN_ADDRESS" l2ProxyAdminOwner = "ENTER_ADMIN_ADDRESS" systemConfigOwner = "ENTER_ADMIN_ADDRESS" unsafeBlockSigner = "ENTER_SEQUENCER_ADDRESS" batcher = "ENTER_BATCHER_ADDRESS" proposer = "ENTER_PROPOSER_ADDRESS" challenger = "ENTER_ADMIN_ADDRESS" [chains.dangerousAltDAConfig] useAltDA = true daCommitmentType = "GenericCommitment" daChallengeWindow = 160 daResolveWindow = 160 daBondSize = 1000000 daResolverRefundPercentage = 0
-
Deploy contracts:
## L1 contract deployment ./bin/op-deployer apply --workdir .deployer \ --l1-rpc-url <RPC_URL_FOR_L1> \ --private-key <DEPLOYER_PRIVATE_KEY_HEX>
-
Generate
genesis.json
,rollup.json
andl1-addresses.json
:./bin/op-deployer inspect genesis --workdir .deployer <L2_CHAIN_ID> > .deployer/genesis.json ./bin/op-deployer inspect rollup --workdir .deployer <L2_CHAIN_ID> > .deployer/rollup.json ./bin/op-deployer inspect l1 --workdir .deployer <L2_CHAIN_ID> > .deployer/l1-addresses.json
Build and Run the op-stack
components
op-geth
-
Clone op-geth :
git clone https://github.com/ethereum-optimism/op-geth.git cd op-geth git checkout v1.101511.0 make geth
-
Move the
genesis.json
andjwt.txt
files into its directory:cp genesis.json ~/op-geth cp jwt.txt ~/op-geth
-
Create and configure data directory
mkdir datadir ./build/bin/geth init --state.scheme=hash --datadir=datadir genesis.json
-
Run
op-geth
# Set environment variables export L2_CHAIN_ID= ## Run op-geth ./build/bin/geth \ --datadir ./datadir \ --http \ --http.corsdomain="*" \ --http.vhosts="*" \ --http.addr=0.0.0.0 \ --http.port=8545 \ --http.api=web3,debug,eth,txpool,net,engine \ --ws \ --ws.addr=0.0.0.0 \ --ws.port=8546 \ --ws.origins="*" \ --ws.api=debug,eth,txpool,net,engine \ --syncmode=full \ --gcmode=archive \ --nodiscover \ --maxpeers=0 \ --networkid=$L2_CHAIN_ID \ --authrpc.vhosts="*" \ --authrpc.addr=0.0.0.0 \ --authrpc.port=8551 \ --authrpc.jwtsecret=./jwt.txt \ --rollup.disabletxpoolgossip=true \ --state.scheme=hash
op-geth
is now active, but block creation will begin onceop-node
is operational.
op-node
-
Build
op-node
from optimismcd op-node git checkout op-node/v1.13.3 make op-node
-
Move the
rollup.json
andjwt.txt
files into its directory:cp rollup.json ~/op-node cp jwt.txt ~/op-node
-
Run
op-node
# Set env variable from `.envrc` direnv allow . ## Run op-node ./bin/op-node \ --l2=http://localhost:8551 \ --l2.jwt-secret=./jwt.txt \ --sequencer.enabled \ --sequencer.l1-confs=5 \ --verifier.l1-confs=4 \ --rollup.config=./rollup.json \ --rpc.addr=0.0.0.0 \ --rpc.port=8547 \ --p2p.disable \ --rpc.enable-admin \ --p2p.sequencer.key=$GS_SEQUENCER_PRIVATE_KEY \ --l1=$L1_RPC_URL \ --l1.rpckind=$L1_RPC_KIND \ --altda.enabled=true \ --altda.da-server=$DA_SERVER_HTTP_URL \ --altda.da-service=true \ --l1.beacon.ignore=true
Block creation will commence once
op-node
starts processing L1 information and interfaces withop-geth
.To optimize synchronization and avoid network resource waste:
- Disable p2p sync (
--p2p.disable
) by default. - Use specific command line parameters for synchronization among multiple nodes.
- Disable p2p sync (
op-batcher
-
Build
op-batcher
from optimismcd op-batcher git checkout op-batcher/v1.13.2 make op-batcher
-
Run
op-batcher
# Set env variable from `.envrc` direnv allow . ## Run op-batcher ./bin/op-batcher \ --l2-eth-rpc=http://localhost:8545 \ --rollup-rpc=http://localhost:8547 \ --poll-interval=1s \ --sub-safety-margin=6 \ --num-confirmations=1 \ --safe-abort-nonce-too-low-count=3 \ --resubmission-timeout=30s \ --rpc.addr=0.0.0.0 \ --rpc.port=8548 \ --rpc.enable-admin \ --max-channel-duration=25 \ --l1-eth-rpc=$L1_RPC_URL \ --private-key=$GS_BATCHER_PRIVATE_KEY \ --altda.enabled=true \ --altda.da-service=true \ --altda.da-server=$DA_SERVER_HTTP_URL \ --throttle-threshold=0
Adjust the
--max-channel-duration=n
setting to balance transaction frequency on L1 and the operational costs of the batcher. Recommended is a minumum of 2 since avail block time is 20s and ethereum’s 12sec.
op-proposer
-
Build
op-proposer
from optimismcd op-proposer git checkout op-proposer/v1.10.0 make op-proposer
-
Run
op-proposer
# Set env variable from `.envrc` direnv allow . ## Run op-proposer ./bin/op-proposer \ --poll-interval=10s \ --rpc.port=8560 \ --rollup-rpc=http://localhost:8547 \ --game-factory-address=$GAME_FACTORY_ADDRESS \ --game-type 1 \ --private-key=$GS_PROPOSER_PRIVATE_KEY \ --l1-eth-rpc=$L1_RPC_URL \ --allow-non-finalized \ --num-confirmations=1 \ --proposal-interval=10s \ --wait-node-sync=true \ --log.level=debug
Appendix
Acquire Sepolia ETH for Layer 2
To obtain ETH on your Rollup:
-
Go to
contracts-bedrock
:cd ~/optimism/packages/contracts-bedrock
-
Find the L1 standard bridge contract address:
cat deployments/avail-optimism/L1StandardBridgeProxy.json | jq -r .address
-
Send Sepolia ETH to the bridge contract address.
Why Archive Mode?
Archive mode on op-geth
, requiring more disk space than full mode, is essential for:
op-proposer
to access the full state history.- The explorer’s functionality.
Reinitializing op-geth
In cases of database corruption indicated by op-node
errors or failure to find L2 heads, follow these steps:
- Stop
op-geth
. - Remove the existing data:
cd ~/op-geth rm -rf datadir/geth
- Reinitialize:
build/bin/geth init --datadir=datadir genesis.json
- Restart
op-geth
and thenop-node
.
See your rollup in action
You now have a fully operational Avail-Powered Optimism-based EVM Rollup. You can interact with your Rollup the same way you’d interact with any other EVM chain. Send some transactions, deploy some contracts, and see what happens!