Skip to main content

Register a stake pool

This is the second of two guides. It assumes you have already installed and synced a relay.

This is the adventurous path. Running a relay confirms your node can follow the chain; registering a stake pool lets it forge blocks — both ordinary Praos ranking blocks and Leios endorser blocks — and makes you a full participant in the Earth-phase work.

First, get cardano-cli and cardano-node on your PATH

Every command in this guide uses cardano-cli, and the final step runs cardano-node directly — so you need both available.

  • Installed with Nix? Drop into the dev-testnet shell, which puts the tools on your PATH:
    nix develop github:input-output-hk/ouroboros-leios#dev-testnet
  • Installed the prebuilt binaries? They are already on your PATH — nothing extra to do.

What you need first

  • Test ada from the faucet. It sends a fixed amount automatically (10,000 test ada) — far more than enough to cover the stake-pool deposit (500 ada), the stake-address deposit (2 ada), your pledge, and transaction fees.
  • A public IP address and an open port so other nodes can reach your node.
  • An accurate clock — a block producer must keep precise time. Install and enable NTP:
    sudo apt install -y chrony
    sudo systemctl enable --now chrony

Keep the environment from the previous guide set in your shell — $WORKING_DIR points at the relay's working directory, and with CARDANO_NODE_NETWORK_ID exported every cardano-cli command targets magic 164 automatically (no --testnet-magic flag needed):

export WORKING_DIR=~/leios-testnet # or wherever you put the relay
export CARDANO_NODE_NETWORK_ID=164
export CARDANO_NODE_SOCKET_PATH="$WORKING_DIR/node.socket"

Work in a dedicated keys folder under $WORKING_DIR and back it up — these keys control your pool:

mkdir -p "$WORKING_DIR/keys" && cd "$WORKING_DIR/keys"
Era command group

The commands below use the dijkstra era command group (cardano-cli dijkstra ...), because the testnet is currently in the Dijkstra era at the chain tip. Confirm with the era field of cardano-cli query tip; if it ever reads something else, switch the era word in these commands to match.

Payment and stake keys

# Payment key pair (holds funds)
cardano-cli dijkstra address key-gen \
--verification-key-file payment.vkey \
--signing-key-file payment.skey

# Stake key pair (controls delegation)
cardano-cli dijkstra stake-address key-gen \
--verification-key-file stake.vkey \
--signing-key-file stake.skey

Fund a payment address

cardano-cli dijkstra address build \
--payment-verification-key-file payment.vkey \
--stake-verification-key-file stake.vkey \
--out-file payment.addr

cat payment.addr

Copy that address into the faucet to receive test ada. Confirm it arrived:

cardano-cli dijkstra query utxo --address "$(cat payment.addr)"

You should see one or more UTxO entries (a TxHash#TxIx and an amount).

Node operational keys

# Cold keys (your pool's identity — keep offline / backed up)
cardano-cli dijkstra node key-gen \
--cold-verification-key-file cold.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter

# KES keys (hot keys, rotated periodically)
cardano-cli dijkstra node key-gen-KES \
--verification-key-file kes.vkey \
--signing-key-file kes.skey

# VRF keys (used to win block-production slots)
cardano-cli dijkstra node key-gen-VRF \
--verification-key-file vrf.vkey \
--signing-key-file vrf.skey

BLS keys

BLS keys are keys that pools use to vote on and certify endorser blocks. You need them to register a Leios-enabled stake pool.

# BLS key pair (Leios voting/certification key)
cardano-cli dijkstra node key-gen-BLS \
--verification-key-file bls.vkey \
--signing-key-file bls.skey

Operational certificate

Compute the current KES period from the chain tip and the genesis parameter, then issue the certificate that binds your KES key to your cold key:

slotsPerKESPeriod=$(jq -r '.slotsPerKESPeriod' "$WORKING_DIR/config/shelley-genesis.json")
slotNo=$(cardano-cli query tip | jq -r '.slot')
kesPeriod=$(( slotNo / slotsPerKESPeriod ))

cardano-cli dijkstra node issue-op-cert \
--kes-verification-key-file kes.vkey \
--cold-signing-key-file cold.skey \
--operational-certificate-issue-counter-file opcert.counter \
--kes-period "$kesPeriod" \
--out-file opcert.cert

Register stake address and pool

Two things go on-chain together: your stake address (a 2 ada deposit) and your pool (a 500 ada deposit). Build both certificates, then submit them in a single transaction.

Stake-address registration certificate:

cardano-cli dijkstra stake-address registration-certificate \
--stake-verification-key-file stake.vkey \
--key-reg-deposit-amt "$(cardano-cli dijkstra query gov-state | jq .currentPParams.stakeAddressDeposit)" \
--out-file stake-reg.cert

Pool registration certificate — replace <YOUR_PUBLIC_IP> with your node's public IP (the address other nodes will use to reach it):

cardano-cli dijkstra stake-pool registration-certificate \
--cold-verification-key-file cold.vkey \
--vrf-verification-key-file vrf.vkey \
--bls-signing-key-file bls.skey \
--pool-pledge 1000000000 \
--pool-cost 170000000 \
--pool-margin 0.05 \
--pool-reward-account-verification-key-file stake.vkey \
--pool-owner-stake-verification-key-file stake.vkey \
--pool-relay-ipv4 <YOUR_PUBLIC_IP> \
--pool-relay-port 3010 \
--out-file pool-reg.cert
BLS key included but not yet active

The cardano-cli command for creating a pool registration certificate now requires a BLS key. The Musashi testnet does not utilize it yet at the time of writing, but transactions carrying it are already accepted. Reach out on the Musashi Dōjō Discord if you need help to register a pool.

tip

--pool-pledge 1000000000 is 1000 test ada — a reasonable pledge for a testnet pool. --pool-cost 170000000 (170 ada) and --pool-margin 0.05 (5%) are typical values; adjust to taste. --pool-relay-port must match the port your node listens on (3010 by default in this guide).

Submit both certificates in one transaction. transaction build queries the node for protocol parameters and your UTxOs to balance the fee and return the change automatically — you just pick an input and a change address. Pull your funded input straight from query utxo (this assumes a single UTxO at the address — true right after the faucet payment), then sign with three keys (payment, stake, cold) and submit:

TXIN=$(cardano-cli dijkstra query utxo --address "$(cat payment.addr)" | jq -r 'keys[0]')

cardano-cli dijkstra transaction build \
--tx-in "$TXIN" \
--change-address "$(cat payment.addr)" \
--certificate-file stake-reg.cert \
--certificate-file pool-reg.cert \
--out-file pool-reg-tx.raw

cardano-cli dijkstra transaction sign \
--tx-body-file pool-reg-tx.raw \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--signing-key-file cold.skey \
--out-file pool-reg-tx.signed

cardano-cli dijkstra transaction submit \
--tx-file pool-reg-tx.signed

Delegate stake to your pool

Your pledge only counts once your own stake is delegated to your pool. Build a delegation certificate and submit it in its own transaction. Your UTxO set changed in the previous step, so the snippet re-queries it for the current input. Two signatures here — payment and stake:

cardano-cli dijkstra stake-address stake-delegation-certificate \
--stake-verification-key-file stake.vkey \
--cold-verification-key-file cold.vkey \
--out-file delegation.cert

TXIN=$(cardano-cli dijkstra query utxo --address "$(cat payment.addr)" | jq -r 'keys[0]')

cardano-cli dijkstra transaction build \
--tx-in "$TXIN" \
--change-address "$(cat payment.addr)" \
--certificate-file delegation.cert \
--out-file delegation-tx.raw

cardano-cli dijkstra transaction sign \
--tx-body-file delegation-tx.raw \
--signing-key-file payment.skey \
--signing-key-file stake.skey \
--out-file delegation-tx.signed

cardano-cli dijkstra transaction submit \
--tx-file delegation-tx.signed
Get real stake from the faucet

Your pledge alone (1000 test ada) is far too little for the pool to be selected to forge. The faucet can also delegate ~1,000,000 test ada to your pool, giving it meaningful active stake. The faucet's delegate widget needs your bech32 pool id (pool1…) — get it with:

cardano-cli dijkstra stake-pool id --output-bech32 --cold-verification-key-file cold.vkey

Verify registration

Capture your pool id (from the cold key) and your stake address:

POOL_ID=$(cardano-cli dijkstra stake-pool id --cold-verification-key-file cold.vkey --output-format hex)
STAKE_ADDR=$(cardano-cli dijkstra stake-address build --stake-verification-key-file stake.vkey)
echo "pool id: $POOL_ID"
echo "stake address: $STAKE_ADDR"

Check the pool is registered on-chain — this should print your pool's parameters (pledge, cost, margin, VRF):

cardano-cli dijkstra query pool-state --stake-pool-id "$POOL_ID"

Check the delegation took effect — stakeDelegation should point at your pool id:

cardano-cli dijkstra query stake-address-info --address "$STAKE_ADDR"

If both look right, your pool is registered.

Restart as block producer

Stop the relay and restart it with the KES key, VRF key, and operational certificate so it can forge — extending the cardano-node run invocation from the previous guide (or, on the Nix path, replacing nix run …#leios-testnet-relay since that wrapper only runs a non-producing relay).

Keep it up

By now you should have settled on a way to keep the node running in the background and watch its uptime — tmux/screen, a systemd unit, or the Docker invocation below. A block producer that drifts offline silently mints no blocks and earns no rewards, so make sure something is watching it.

Run it from $WORKING_DIR, which holds the config and database:

cd "$WORKING_DIR"
cardano-node run \
--config config/config.json \
--topology config/topology.json \
--database-path db \
--socket-path node.socket \
--host-addr 0.0.0.0 \
--port 3010 \
--shelley-kes-key keys/kes.skey \
--shelley-vrf-key keys/vrf.skey \
--shelley-operational-certificate keys/opcert.cert

Once your pool is registered and your node is forging, you are a block producer on the testnet. Block production begins after the stake snapshot takes effect — roughly two epochs after registration.

What to send back

The testnet is where the protocol practices in public, and what you see is part of the practice. If your node will not sync, a command here fails, a trace event looks wrong, or the chain behaves in a way you did not expect — that is exactly the signal the team wants.

When you report, include three things: the command or action you took, what you expected, and what actually happened. Attach your node version (cardano-node --version) and the relevant log lines.