Validator Registration
Before running your node, you must register it in the Espresso Stake Table contract on Ethereum L1. This associates your consensus keys with a unique Ethereum address that will receive commission rewards.
Registration requires an Ethereum L1 transaction. Ensure your account has enough ETH for gas.
Prerequisites
- An Ethereum wallet with a mnemonic and enough ETH for gas
- A running Ethereum L1 RPC endpoint (e.g. Infura, Alchemy)
- The Espresso container image:
ghcr.io/espressosystems/espresso-sequencer/sequencer:20260407 - The staking CLI image:
ghcr.io/espressosystems/espresso-network/staking-cli:main
Step 1: Generate Consensus Keys
Espresso nodes require two cryptographic key pairs for consensus:
| Key Type | Private Key Prefix | Public Key Prefix | Purpose |
|---|---|---|---|
| BLS | BLS_SIGNING_KEY~ | BLS_VER_KEY~ | Consensus voting (staking) |
| Schnorr | SCHNORR_SIGNING_KEY~ | SCHNORR_VER_KEY~ | State signatures |
Generate both key pairs using the built-in keygen utility:
docker run --rm -v $(pwd)/keys:/keys \
ghcr.io/espressosystems/espresso-sequencer/sequencer:20260407 \
keygen -o /keys
This creates a key file at ./keys/0.env containing:
ESPRESSO_SEQUENCER_PRIVATE_STAKING_KEY=BLS_SIGNING_KEY~<your-bls-private-key>
ESPRESSO_SEQUENCER_PRIVATE_STATE_KEY=SCHNORR_SIGNING_KEY~<your-schnorr-private-key>
You will also see the corresponding public keys in the output. Save the BLS public key (BLS_VER_KEY~...) — you'll need it for the metadata file.
NEVER share or expose your private keys (BLS_SIGNING_KEY~... and SCHNORR_SIGNING_KEY~...). Keep them secure. Only public keys (BLS_VER_KEY~..., SCHNORR_VER_KEY~...) are safe to share.
Step 2: Prepare Validator Metadata
The METADATA_URI points to a publicly hosted JSON file describing your validator. This metadata is displayed in the Espresso staking UI and helps delegators identify your node.
Required JSON Schema
Based on the Espresso source code and the staking CLI README, a custom metadata JSON file has the following structure:
{
"pub_key": "BLS_VER_KEY~...",
"name": "Your Validator Name",
"description": "High-performance Espresso validator operated by your team",
"company_name": "Your Team or Company",
"company_website": "https://your-domain.com",
"client_version": "20260407",
"icon": {
"14x14": {
"@1x": "https://your-domain.com/espresso/icon-14.png",
"@2x": "https://your-domain.com/espresso/[email protected]",
"@3x": "https://your-domain.com/espresso/[email protected]"
},
"24x24": {
"@1x": "https://your-domain.com/espresso/icon-24.png",
"@2x": "https://your-domain.com/espresso/[email protected]",
"@3x": "https://your-domain.com/espresso/[email protected]"
}
}
}
pub_key is the only required JSON field. It must be the BLS public verification key of the registering node and must keep the BLS_VER_KEY~ prefix. This value must match the BLS key derived from CONSENSUS_PRIVATE_KEY; otherwise staking-cli register-validator metadata validation fails with a metadata pub_key mismatch error.
Do not put BLS_SIGNING_KEY~... or SCHNORR_VER_KEY~... in pub_key. The metadata key is the public BLS verification key only: BLS_VER_KEY~....
Metadata Field Reference
| Field | Required | Notes |
|---|---|---|
pub_key | Yes | BLS public verification key, exactly BLS_VER_KEY~... |
name | No | Human-readable validator name |
description | No | Longer validator description |
company_name | No | Team, company, or individual operator name |
company_website | No | Valid URL |
client_version | No | Client/image version, for example 20260407 |
icon | No | Optional 14x14 and 24x24 image URLs with @1x, @2x, @3x entries |
The current schema does not define operating_system, node_type, or network_type. Extra JSON fields are ignored by the current parser, but they should not be used in examples because the staking UI will not consume them.
Obtaining Your BLS Public Key
If you only have the private key and need to derive the public key:
docker run --rm \
ghcr.io/espressosystems/espresso-sequencer/sequencer:20260407 \
pub-key -l "BLS_SIGNING_KEY~your-private-key"
Use the resulting BLS_VER_KEY~... value verbatim in metadata:
{
"pub_key": "BLS_VER_KEY~output-from-pub-key-command"
}
Do not strip the BLS_VER_KEY~ tag. Espresso keys use tagged-base64 encoding, and the tag is part of the parseable key format.
Hosting the Metadata
Host the JSON file at any publicly accessible URL:
| Method | Example URL |
|---|---|
| GitHub Raw | https://raw.githubusercontent.com/yourorg/repo/main/metadata.json |
| S3 / R2 | https://your-bucket.s3.amazonaws.com/validator-metadata.json |
| Your domain | https://blocknth.com/espresso/metadata.json |
Preview and validate the metadata before registering:
docker run --rm \
ghcr.io/espressosystems/espresso-network/staking-cli:main \
staking-cli preview-metadata \
--metadata-uri "https://your-domain.com/validator-metadata.json"
Step 3: Register the Validator
Run the registration command using the staking CLI:
docker run \
-e L1_PROVIDER="https://mainnet.infura.io/v3/<API-KEY>" \
-e STAKE_TABLE_ADDRESS=0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 \
-e MNEMONIC="your twelve or twenty four word mnemonic phrase here" \
-e ACCOUNT_INDEX=0 \
-e CONSENSUS_PRIVATE_KEY="BLS_SIGNING_KEY~your-bls-private-key" \
-e STATE_PRIVATE_KEY="SCHNORR_SIGNING_KEY~your-schnorr-private-key" \
ghcr.io/espressosystems/espresso-network/staking-cli:main \
staking-cli register-validator \
--commission 5.00 \
--metadata-uri "https://your-domain.com/validator-metadata.json"
Parameter Reference
| Parameter | Description |
|---|---|
L1_PROVIDER | Ethereum mainnet HTTP RPC URL |
STAKE_TABLE_ADDRESS | 0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 (Mainnet) |
MNEMONIC | Ethereum mnemonic (BIP-39) for deriving the registration address |
ACCOUNT_INDEX | BIP-44 derivation index (m/44'/60'/0'/0/{index}), starts from 0 |
CONSENSUS_PRIVATE_KEY | BLS private key (BLS_SIGNING_KEY~...) |
STATE_PRIVATE_KEY | Schnorr private key (SCHNORR_SIGNING_KEY~...) |
--commission | Commission rate in percentage (e.g. 5.00 = 5%, up to 2 decimal places) |
--metadata-uri | Public URL to your validator metadata JSON |
Successful Output
A successful registration looks like:
INFO staking_cli: Registering validator 0xYourAddress with commission 5.00 %
INFO staking_cli: Success! transaction hash: 0xabc123...
You can verify the transaction on Etherscan by searching the transaction hash. The To address should be the Stake Table contract 0xCeF474D372B5b09dEfe2aF187bf17338Dc704451.
Important Rules
- Each BLS key can only be registered once
- Each Ethereum account (
MNEMONIC + ACCOUNT_INDEX) can only register one validator - For multiple validators, use different
ACCOUNT_INDEXvalues or different mnemonics - The Ethereum account must have enough ETH to pay for the L1 registration gas
Step 4: Delegate Stake
After registration, your node needs ESP tokens delegated to it to participate in consensus. You (or any token holder) can delegate using:
docker run \
-e MNEMONIC="delegator-mnemonic-phrase" \
-e ACCOUNT_INDEX=0 \
-e L1_PROVIDER="https://mainnet.infura.io/v3/<API-KEY>" \
-e ESP_TOKEN_ADDRESS=0x031De51F3E8016514Bd0963d0B2AB825A591Db9A \
-e STAKE_TABLE_ADDRESS=0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 \
ghcr.io/espressosystems/espresso-network/staking-cli:main \
staking-cli delegate --validator-address $VALIDATOR_ETH_ADDRESS --amount $DELEGATION_AMOUNT
The top 100 nodes by delegated stake form the active validator set each epoch (~24 hours). You must attract enough delegation to be in the top 100 to participate.
Managing Your Registration
Update Commission
Commission can be updated with the following constraints:
- Limited to once per 7 days
- Increases capped at 5% per update
- Decreases have no limit
docker run \
-e L1_PROVIDER="https://mainnet.infura.io/v3/<API-KEY>" \
-e STAKE_TABLE_ADDRESS=0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 \
-e MNEMONIC="your-mnemonic" \
-e ACCOUNT_INDEX=0 \
-e CONSENSUS_PRIVATE_KEY="BLS_SIGNING_KEY~..." \
-e STATE_PRIVATE_KEY="SCHNORR_SIGNING_KEY~..." \
ghcr.io/espressosystems/espresso-network/staking-cli:main \
staking-cli update-commission --new-commission 2.54
Deregister
To stop participating and remove all delegators:
docker run \
-e MNEMONIC="your-mnemonic" \
-e ACCOUNT_INDEX=0 \
-e L1_PROVIDER="https://mainnet.infura.io/v3/<API-KEY>" \
-e ESP_TOKEN_ADDRESS=0x031De51F3E8016514Bd0963d0B2AB825A591Db9A \
-e STAKE_TABLE_ADDRESS=0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 \
ghcr.io/espressosystems/espresso-network/staking-cli:main \
staking-cli deregister-validator
Undelegate
To remove a delegation:
docker run \
-e MNEMONIC="delegator-mnemonic" \
-e ACCOUNT_INDEX=0 \
-e L1_PROVIDER="https://mainnet.infura.io/v3/<API-KEY>" \
-e ESP_TOKEN_ADDRESS=0x031De51F3E8016514Bd0963d0B2AB825A591Db9A \
-e STAKE_TABLE_ADDRESS=0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 \
ghcr.io/espressosystems/espresso-network/staking-cli:main \
staking-cli undelegate --validator-address $VALIDATOR_ETH_ADDRESS --amount $AMOUNT
Ledger Support
The staking CLI supports Ledger hardware wallets for signing transactions. See the staking-cli README for details.