Skip to content
Minerva. Post-quantum, proof of work.

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:doctor on 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 with MINERVA_NETWORK=minerva-testnet against 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/bridge scripts 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 = false in contracts/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 doctor inside 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.