Skip to main content

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.

warning

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 TypePrivate Key PrefixPublic Key PrefixPurpose
BLSBLS_SIGNING_KEY~BLS_VER_KEY~Consensus voting (staking)
SchnorrSCHNORR_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.

danger

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]"
}
}
}
important

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.

warning

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​

FieldRequiredNotes
pub_keyYesBLS public verification key, exactly BLS_VER_KEY~...
nameNoHuman-readable validator name
descriptionNoLonger validator description
company_nameNoTeam, company, or individual operator name
company_websiteNoValid URL
client_versionNoClient/image version, for example 20260407
iconNoOptional 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:

MethodExample URL
GitHub Rawhttps://raw.githubusercontent.com/yourorg/repo/main/metadata.json
S3 / R2https://your-bucket.s3.amazonaws.com/validator-metadata.json
Your domainhttps://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​

ParameterDescription
L1_PROVIDEREthereum mainnet HTTP RPC URL
STAKE_TABLE_ADDRESS0xCeF474D372B5b09dEfe2aF187bf17338Dc704451 (Mainnet)
MNEMONICEthereum mnemonic (BIP-39) for deriving the registration address
ACCOUNT_INDEXBIP-44 derivation index (m/44'/60'/0'/0/{index}), starts from 0
CONSENSUS_PRIVATE_KEYBLS private key (BLS_SIGNING_KEY~...)
STATE_PRIVATE_KEYSchnorr private key (SCHNORR_SIGNING_KEY~...)
--commissionCommission rate in percentage (e.g. 5.00 = 5%, up to 2 decimal places)
--metadata-uriPublic 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...
tip

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_INDEX values 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
info

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.