Installation
Install Dependencies:
sudo apt update && sudo apt upgrade -y
sudo apt-get install git curl build-essential make jq gcc snapd chrony lz4 tmux unzip make bc -y
Install Docker:
# Remove conflicting packages
sudo apt-get remove -y docker.io docker-doc docker-compose podman-docker containerd runc 2>/dev/null
sudo apt-get remove -y docker-compose-v2 2>/dev/null
# Add Docker's official GPG key
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Enable and start Docker
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
newgrp docker
Generate Validator Keys
Create the working directory structure:
mkdir -p ~/espresso-mainnet/keys
mkdir -p ~/espresso-mainnet/data/postgres
Generate validator keys - BLS consensus key and Schnorr state key:
docker run --rm \
-v ~/espresso-mainnet/keys:/output \
ghcr.io/espressosystems/espresso-sequencer/sequencer:20260710 \
keygen --out /output
View the generated keys:
cat ~/espresso-mainnet/keys/0.env
Output:
# Seed: <random seed>
ESPRESSO_SEQUENCER_PUBLIC_STAKING_KEY=BLS_VER_KEY~...
ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=BLS_SIGNING_KEY~...
ESPRESSO_SEQUENCER_PUBLIC_STATE_KEY=SCHNORR_VER_KEY~...
ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=SCHNORR_SIGNING_KEY~...
⚠️ Back up your seed and all private keys immediately. They cannot be recovered if lost.
Note: Mainnet uses a Seed instead of a Mnemonic for key generation. The validator wallet is created separately.
Create Your Validator Wallet
pip3 install eth-account --break-system-packages
python3 -c "
from eth_account import Account
import secrets
Account.enable_unaudited_hdwallet_features()
mnemonic = Account.create_with_mnemonic()[1]
acct = Account.from_mnemonic(mnemonic, account_path=\"m/44'/60'/0'/0/0\")
print('Mnemonic:', mnemonic)
print('Address:', acct.address)
"
⚠️ Set it accordingly if your ACCOUNT_INDEX differs from the default one. "m/44'/60'/0'/0/0\ ⚠️ Send it a small amount of gas fees, around 0.004 $ETH on Mainnet.
Create Validator Metadata
{
"pub_key": "LS_VER_KEY~...............",
"name": "MONIKER",
"description": "DESSCRIPTION",
"company_name": "COMPANY-NAME",
"company_website": "WEBSITE",
"client_version": "v1.0.0",
"icon": {
"14x14": { "@1x": "https://raw.githubusercontent.com/XXXX/espresso-metadata/refs/heads/main/icon-3.jpg", "@3x": "https://raw.githubusercontent.com/XXXX/espresso-metadata/refs/heads/main/icon-5.jpg" },
"24x24": { "@1x": "https://raw.githubusercontent.com/XXXX/espresso-metadata/refs/heads/main/icon-2.jpg", "@2x": "https://raw.githubusercontent.com/XXXX/espresso-metadata/refs/heads/main/icon-4.jpg", "@3x": "https://raw.githubusercontent.com/XXXX/espresso-metadata/refs/heads/main/icon-6.jpg" }
}
}
Register as Validator On-Chain
source ~/espresso-mainnet/keys/0.env
docker run --rm \
-e L1_PROVIDER="<YOUR_ETHEREUM_MAINNET_RPC>" \
-e STAKE_TABLE_ADDRESS=0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 \
-e MNEMONIC="<YOUR_12_WORD_MNEMONIC>" \
-e ACCOUNT_INDEX=0 \
-e CONSENSUS_PRIVATE_KEY="$ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY" \
-e STATE_PRIVATE_KEY="$ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY" \
ghcr.io/espressosystems/espresso-sequencer/staking-cli:main \
staking-cli register-validator \
--commission 10 \
--metadata-uri "<YOUR_RAW_GIST_URL>"
⚠️ Remember, each Ethereum account can only be used to register a single validator once. For multiple validators, at a minimum, different account indices (or mnemonics) must be used. ⚠️ Load your keys and run the registration. This is a one-time on-chain transaction on Ethereum Mainnet:
Set Up the Non DA Node
cat > ~/espresso-mainnet/docker-compose.yml << 'EOF'
services:
espresso-node:
image: ghcr.io/espressosystems/espresso-sequencer/sequencer:20260605
restart: unless-stopped
command: sequencer -- http -- catchup -- status
ports:
- "30080:80"
- "1769:1769/udp"
volumes:
- /root/espresso-mainnet/keys:/keys:ro
environment:
ESPRESSO_SEQUENCER_CDN_ENDPOINT: "cdn.main.net.espresso.network:1737"
ESPRESSO_STATE_RELAY_SERVER_URL: "https://state-relay.main.net.espresso.network"
ESPRESSO_SEQUENCER_GENESIS_FILE: "/genesis/mainnet.toml"
ESPRESSO_SEQUENCER_EMBEDDED_DB: "true"
ESPRESSO_SEQUENCER_CONFIG_PEERS: "https://cache.main.net.espresso.network"
ESPRESSO_SEQUENCER_STATE_PEERS: "https://query.main.net.espresso.network"
ESPRESSO_SEQUENCER_API_PEERS: "https://query.main.net.espresso.network/v1"
ESPRESSO_SEQUENCER_KEY_FILE: "/keys/0.env"
ESPRESSO_SEQUENCER_API_PORT: "80"
ESPRESSO_SEQUENCER_STORAGE_PATH: "/mount/sequencer/store/"
ESPRESSO_SEQUENCER_LIBP2P_BIND_ADDRESS: "0.0.0.0:1769"
ESPRESSO_SEQUENCER_LIBP2P_ADVERTISE_ADDRESS: "YOUR-IP:1769"
ESPRESSO_SEQUENCER_L1_PROVIDER: "RPC-ETH-MAINNET"
ESPRESSO_SEQUENCER_L1_WS_PROVIDER: "WSS-ETH-MAINNET"
ESPRESSO_SEQUENCER_L1_RETRY_DELAY: "20s"
RUST_LOG: "warn,libp2p=off"
RUST_LOG_FORMAT: "json"
sysctls:
net.ipv4.tcp_congestion_control: "bbr"
net.ipv4.tcp_rmem: "8192 262144 67108864"
net.ipv4.tcp_wmem: "4096 16384 536870912"
net.ipv4.tcp_adv_win_scale: "0"
net.ipv4.tcp_notsent_lowat: "131072"
net.ipv4.tcp_slow_start_after_idle: "0"
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "3"
EOF
Start the Node
cd ~/espresso-mainnet && docker compose up -d