Documentation · Network
Minerva testnet launch runbook (chain id 77138)
network/docs/testnet/minerva-launch.md · commit e561109 · 2026-10-23
What a public Minerva testnet needs, in the order it happens. Every step has a check that fails loudly; nothing here holds an EOA key.
1. Freeze the genesis
forge build --root contracts
node scripts/devnet/install-system-contracts.mjs --network minerva-testnet --skip-generated-sources
bash scripts/coregeth-build.sh
./build/bin/minerva-geth genesis-hash clients/minerva-node/config/minerva-testnet/genesis.json
clients/minerva-node/config/minerva-testnet/genesis.json carries the four Minerva rules at
block 0, the system contracts 0x...0201 to 0x...0205, a genesis difficulty sized for the
first miner (0x600000: a difficulty of 1 lets a modern CPU mine several blocks per second, the
1 s timestamp floor then pushes block times into the future and peers refuse them) and no
prefunds. Testnet liquidity
and the faucet treasury are added only when MErva/docs/05-supply-statement.md names them, and
minerva-launch-preflight.sh refuses any other prefund (MINERVA_ALLOWED_PREFUNDS lists the
named ones). Commit the genesis; its hash is what everyone pins.
2. Generate the launch material
MINERVA_BOOTNODE_PUBLIC_HOSTS=203.0.113.10,203.0.113.11 pnpm testnet:minerva:material
Writes artifacts/testnet/minerva-launch/<stamp>/ (git-ignored): keys/bootnode-N.key
(mode 600), bootnodes.json (network, chain id, genesis hash, enodes), the env file
.env.minerva-testnet.local for the compose file, and SHA256SUMS of the material. The env file
has MINERVA_MINER_ETHERBASE empty: fill it with a Minerva (PQ-controlled) account before
mining; the node refuses nothing here, but the coverage matrix's claim does.
3. Preflight
MINERVA_MINERVA_ENV_FILE=artifacts/testnet/minerva-launch/<stamp>/.env.minerva-testnet.local pnpm testnet:minerva:preflight
Checks the profile and chain id, the genesis (chain id, Minerva forks, system contracts,
prefunds), that the pinned genesis hash equals what the binary computes, the bootnodes manifest
(count, enode syntax, same genesis hash), the key file modes, and, when MINERVA_RPC_URL
answers, runs minerva doctor against the node. Exit code 1 on any failure.
4. Build and start a node
docker build -f infra/Dockerfile.node -t minerva/minerva-geth:<commit> .
docker compose -f infra/docker-compose.minerva-testnet.yml \
--env-file artifacts/testnet/minerva-launch/<stamp>/.env.minerva-testnet.local up -d
The image builds the node exactly like scripts/release/build-node.sh, so its binary hashes
like the published release. The entrypoint pins the genesis hash (MINERVA_GENESIS_HASH):
a node started on a different genesis exits before it opens a port. RPC listens on
127.0.0.1:8545 only; put a reverse proxy in front for public RPC.
Bare metal: minerva-geth --datadir <dir> init genesis.json, then the flags the entrypoint
builds (--networkid 77138 --http.api eth,net,web3,minerva,minerva,txpool --bootnodes ... --nodekey keys/bootnode-1.key --nat extip:<ip>); add --authrpc.port <free port> when another
node on the host already holds 8551.
5. Publish
With the release (see docs/release-process.md): the node version and SHA256SUMS with both
signatures, the genesis hash, the bootnode enodes, the chain id, and the fork schedule (all four
at block 0). Operators verify with pnpm release:verify and pnpm node:doctor
(MINERVA_NETWORK=minerva-testnet).
6. After launch
pnpm node:doctoron every node: zero peers outside the devnet is a warning, a future head is a failure (clock), a foreign genesis is a failure.- Faucet and PQ relayer (
services/faucet-api,services/pq-relayer-api) run withMINERVA_NETWORK=minerva-testnetagainst the node's RPC; the faucet treasury is a Minerva account whose seed never enters the launch material. - Bridge: deploy the Ethereum side with
MErva/bridgescripts and install the native vault through its genesis installer before the testnet genesis is frozen, or deploy it later with a PQ-controlled deployer.
Rehearsal evidence (2026-10-20): material generated for 127.0.0.1, preflight green; a bare
node started from the testnet genesis with the generated node key and minerva doctor passed
against it (chain id 77138, genesis hash 0xcb67cfd0...5fbf); the image built from
infra/Dockerfile.node (162 MB) prints the same genesis hash, its entrypoint refused a wrong
pinned hash, and the compose stack started from the rehearsal env file answered minerva doctor
with 7 pass, 0 fail (the one warning is "no peers", expected for a single node).
7. The live testnet (2026-10-20)
Deployed on a dedicated Ubuntu 24.04 server (16 cores, 62 GB, 830 GB free) with Docker from the
official repository and the image built from infra/Dockerfile.node (loaded from the local
build, same genesis hash). Layout on the server: /opt/minerva/{infra,clients/...,artifacts/...}
mirroring this repository, so the compose file and the generated env file run unchanged:
cd /opt/minerva && sudo docker compose -f infra/docker-compose.minerva-testnet.yml \
--env-file artifacts/testnet/minerva-launch/ns5030792-20261020b/.env.minerva-testnet.local up -d --no-build
- Genesis hash
0x364c9c46ce915db7a4bbd746fabbab83a07ae3626773830090b4704a9799af38, chain id 77138. - Bootnode
enode://4137d64994dc7bc3d4cb86df2cfe7958ee8c55d004dc716594301b82b237734c0ed34a77853274884753f24bac653dec9232f01ad8789f14eaed8c826961fe16@148.113.213.71:30303(P2P 30303 tcp/udp public; RPC on the server loopback only). - Mining to
0x0EF097003411a6E1e086940653B75686f833aa49, a Minerva account whose ML-DSA seed is kept off the server (operator machine, mode 600) until the faucet is deployed. - First attempt used difficulty 1: the server mined about 2.7 blocks per second and the chain
ran 7 minutes into the future within four minutes, so the genesis was reset to
0x600000(same bootnode key, new hash) before any external peer had joined. - Relaunched on 2026-10-23 from a genesis built from the Minerva sources with metadata-free
contract bytecode (
bytecode_hash = "none",cbor_metadata = falseincontracts/foundry.toml, so source edits no longer move the system-contract code or CREATE2 account addresses): same bootnode key, new genesis hash, new treasury account; the previous chain's data volume is archived on the server under/opt/minerva/backup/. minerva doctorinside the container and from the operator machine through an SSH tunnel: 7 pass, 0 fail; the only warning was "no peers" before the first peer connected.
Not yet done: public RPC (needs a reverse proxy with TLS and rate limits in front of
127.0.0.1:8545), the faucet and PQ relayer services, a second bootnode on another host, and
the explorer.
8. Public endpoints (testnet.minerva.build)
One host, routed by path by nginx on the server (infra/nginx/testnet.minerva.build.conf):
| Path | Backend | Notes |
|---|---|---|
POST /rpc |
node 127.0.0.1:8545 |
JSON-RPC incl. the minerva namespace; 20 req/s per IP, burst 40, GET refused |
GET /faucet, GET /faucet/status, POST /faucet/request |
faucet API 127.0.0.1:18546 |
10 req/min per IP; pays 1 MRV from the treasury account; can deploy the requester's account |
POST /pq-dev/send-raw (+ /pq-dev/status, /pq-dev/account) |
PQ relayer 127.0.0.1:18548 |
raw MinervaPQTxV1 gateway for wallets, 5 req/s per IP |
GET /, /genesis.json, /bootnodes.json |
static | network facts for operators |
These are the values in @minerva/chain-config (MINERVA_TESTNET_RPC_URL,
MINERVA_TESTNET_FAUCET_URL, MINERVA_TESTNET_PQ_RELAYER_URL), so the wallet apps resolve chain
77138 to them without configuration.
Services run from infra/Dockerfile.services (minerva/network-services:local) as the
faucet-api and pq-relayer-api compose services; the faucet reads its ML-DSA seed from
/opt/minerva/secrets/faucet.env (operator-created, mode 600, outside the repository) and pays
from the same Minerva account that receives block rewards.
DNS and TLS: testnet.minerva.build must be an A record for the server; then
sudo certbot --nginx -d testnet.minerva.build --non-interactive --agree-tos --register-unsafely-without-email --redirect
installs the certificate, adds the 443 server block and redirects HTTP to HTTPS; certbot's systemd timer renews it.
Done on 2026-10-20: testnet.minerva.build resolves to the server (Cloudflare DNS, proxy off),
Let's Encrypt certificate issued (valid to 2027-01-18, auto-renewal timer active), HTTP redirects
to HTTPS, and minerva doctor --rpc https://testnet.minerva.build/rpc passes from an outside
machine with the pinned genesis hash.
