Skip to content
Minotaur
+ 02 · Validator

Validator Quickstart

Six-step setup for a Minotaur validator on Bittensor Subnet 112 — hardware, prerequisites, on-chain ValidatorRegistry handshake, canonical Docker stack, and peer discovery.

This guide walks you through setting up and running a Minotaur validator on Bittensor Subnet 112. By the end you’ll have a registered hotkey, an EVM signing address in the on-chain ValidatorRegistry, the canonical Docker stack running, and your /identity endpoint discoverable by other validators.

Hardware requirements

Every validator must be leader-capable. Leadership on Subnet 112 is awarded by stake at the metagraph level — the highest-stake validator runs the leader role, ties broken by hotkey lexicographic order. Leadership rotates whenever stakes shift on chain, with no advance notice. A validator that cannot immediately accept the leader role on stake change is a free-rider.

Baseline (today)

Subnet 112 currently operates one App (DexAggregatorApp) across two real chains (Base + BT EVM, Ethereum in Phase 4). User volume is light.

SpecValue
vCPU4 (modern x86_64)
RAM8 GB
Storage100 GB SSD/NVMe
GPUnone
Network out~100 GB/month
Public IPv4yes — ports 9100/tcp and 8080/tcp must be reachable

Steady-state at this spec uses ~4–5 GB RAM and well under 1 vCPU. Re-simulation bursts (when the leader proposes a new order) peak around 6–7 GB RAM and 2–3 vCPU. 8 GB / 4 vCPU leaves a healthy margin.

Growth path

Trigger eventRecommended spec
Baseline (today)4 vCPU / 8 GB / 100 GB SSD
+1 chain added (one extra Anvil fork = ~1.5–2 GB RAM)4 vCPU / 12 GB / 150 GB SSD
2+ new chains, or sustained user volume making JS scoring run continuously8 vCPU / 16 GB / 200 GB SSD
Multi-app phase (several Apps live, each with independent benchmarks)8 vCPU / 32 GB / 200 GB SSD

Scaling is vertical — no horizontal sharding needed at the validator level. Plan to upsize at the announcement of each new chain integration.

Required maintenance cron CRITICAL

Anvil’s overlay filesystem grows fast — production deployments measure ~40–50 GB per fork per day. With three forks that’s ~150 GB/day of bloat. Without a frequent recycle, a 100 GB volume fills in well under a day and the host OS hangs once the disk hits 100% — SSH is dead and the only recovery is a force stop/start at the hypervisor.

Install this cron — every 6 hours, not daily:

/etc/cron.d/minotaur-anvil-recycle SH
0 */6 * * * root docker compose -f /opt/minotaur/platform/validator/docker-compose.yml \
rm -fsv anvil anvil-base anvil-btevm && \
docker compose -f /opt/minotaur/platform/validator/docker-compose.yml \
up -d anvil anvil-base anvil-btevm

Each recycle window drops in-flight Anvil state for ~60 seconds. If you’re running a high-stake validator, stagger your cron a few minutes from peers to avoid simultaneous reorg pauses.

Third-party APIs

ProviderUsed forFree tier sufficient?
Alchemy / Infura (Ethereum)Source RPC for the Anvil ETH fork; archive endpoint neededYes for moderate load. Premium recommended once you take leader for non-trivial periods.
Alchemy / Infura (Base, chain 8453)Source RPC for the Anvil Base forkSame as above, same account works
Public BT EVM RPChttps://lite.chain.opentensor.ai (chain 964)Public endpoint, no signup
Public Finney WSwss://entrypoint-finney.opentensor.ai:443 — metagraph readsPublic endpoint, no signup
GitHub API (read-only)Cloning miner submissions during leader benchmarkingAnonymous works for small subnets; provision a PAT before you take leader to raise rate limits

No GPU compute or LLM API is required. The JS scoring engine is pure Node.js, deterministic, CPU-bound.

Ports

Inbound (must be reachable from the public internet)

PortServiceNotes
9100/tcpValidator daemon — order-consensus signing + /identityReached by the current leader for proposal broadcast and by peers for discovery.
8080/tcpAPI service — champion-consensus signing + /identityReached by the current leader for champion-certification proposals.
Ubuntu / ufw SH
sudo ufw allow 9100/tcp comment "minotaur validator daemon"
sudo ufw allow 8080/tcp comment "minotaur api service"

For AWS security groups, allow inbound TCP 9100 and 8080 from 0.0.0.0/0.

Outbound (egress, no special configuration)

DestinationPortPurpose
entrypoint-finney.opentensor.ai443 (WSS)Subtensor metagraph reads
lite.chain.opentensor.ai443 (HTTPS)BT EVM RPC
Alchemy / Infura443 (HTTPS)ETH + Base fork source RPCs
Other validator peers9100 (HTTPS)Order-consensus signing
Leader API host8080 (HTTPS)Proposal pull (followers)
github.com, ghcr.io443 (HTTPS)Image and repo pulls

Internal only (must NEVER be exposed)

Anvil ports (8545 ETH, 8546 Base, 8547 BT EVM) are bound to the Docker network. Do not expose them to the public internet.

Prerequisites

Two host-level tools:

  • Docker (≥25, for Watchtower compatibility) + Docker Compose plugin
  • Foundry (cast) — used to generate your EVM signing key and read on-chain state
  • Bittensor CLI (btcli) — register your hotkey on subnet 112

And account-level things you provide:

  • Bittensor wallet with a registered hotkey on subnet 112 (Step 3)
  • EVM private key for EIP-712 consensus signing (Step 4 — a fresh key, does NOT hold funds)
  • Ethereum + Base RPC URLs from Alchemy or Infura
  • Coordination with the subnet operator to be added to the on-chain ValidatorRegistry (Step 4)

Step 1 — Clone the repo

You only need the repo for the canonical compose file + the .env.example. The validator + api Python code runs inside the Docker image (no local venv / pip install needed):

terminal SH
git clone https://github.com/subnet112/minotaur_subnet.git
cd minotaur_subnet

Step 2 — Install Foundry

terminal SH
curl -L https://foundry.paradigm.xyz | bash
foundryup
anvil --version && forge --version && cast --version

Step 3 — Register on Subnet 112

If your hotkey is not yet registered:

terminal SH
btcli subnet register --netuid 112 --subtensor.network finney \
--wallet.name my-validator --wallet.hotkey my-hotkey

Verify registration:

terminal SH
btcli subnet metagraph --netuid 112 --subtensor.network finney

Your hotkey should appear in the metagraph. Ensure you have sufficient TAO staked to participate in leader election.

Step 4 — Get onboarded to the on-chain ValidatorRegistry

Send the following three values to the registry owner (via the project’s GitHub issue template or current contact channel — check the project README):

Validator hotkey (SS58): 5...
EVM signing address: 0x... (derived from your VALIDATOR_PRIVATE_KEY)
Public axon URL: http://your-host:9100

The owner runs, once per chain:

registry owner SH
# Read the current set
cast call $VALIDATOR_REGISTRY 'getValidators()(address[])' --rpc-url $RPC_URL

# Add you (full new list, sorted ascending)
cast send $VALIDATOR_REGISTRY \
'updateValidators(address[])' \
'[0xExistingValidator1,0xExistingValidator2,0xYourEvmAddress]' \
--rpc-url $RPC_URL --private-key $REGISTRY_OWNER_KEY

Repeat per chain (Base + BT EVM today; Ethereum in Phase 4 — addresses in the Network Reference).

Verify before continuing:

terminal SH
cast call $VALIDATOR_REGISTRY 'isValidator(address)(bool)' \
0xYourEvmAddress --rpc-url $RPC_URL

If this returns true on every chain, you’re cleared. If false, your consensus signatures will be ignored.

Step 5 — Configure environment

The recommended path (Step 6) reads from platform/validator/.env. The variables below also work as shell exports if you’re running the daemon directly under systemd.

platform/validator/.env SH
# Bittensor identity
WALLET_NAME=my-validator
HOTKEY_NAME=my-hotkey
NETUID=112
SUBTENSOR_URL=wss://entrypoint-finney.opentensor.ai:443

# Anvil forks the validator CONNECTS TO. It does NOT spawn them — Step 6
# brings them up as separate compose services.
ANVIL_RPC_URL=http://localhost:8545          # Ethereum fork
BASE_RPC_URL=http://localhost:8546           # Base fork
BITTENSOR_EVM_RPC_URL=http://localhost:8547  # BT EVM fork

# Upstream RPCs — used by the validator to advance each fork to the current
# chain head between simulations. Without these the fork stays frozen.
ETH_UPSTREAM_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY
BASE_UPSTREAM_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_KEY
BITTENSOR_EVM_UPSTREAM_RPC_URL=https://lite.chain.opentensor.ai

# Consensus signing
VALIDATOR_PRIVATE_KEY=0xYOUR_EVM_PRIVATE_KEY

# Public URL where your daemon serves /identity. Required for peer discovery
# — other validators sign and verify against this.
VALIDATOR_AXON_URL=http://your-public-host:9100

# On-chain ValidatorRegistry per chain — see the Network Reference.
VALIDATOR_REGISTRY_8453=0x88a08d1105393EACE9B6f5ff678DbE508B8639aC
VALIDATOR_REGISTRY_964=0x0B5fE44e90515571761D86C28c4855F325EDE098

CHAIN_ID=8453
LOG_LEVEL=INFO

See Configuration for the full list of options and Quorum Management for changing the network-wide quorum value.

Step 6 — Bring up the validator stack

The validator role for a third-party operator is six containers:

  • Three Anvil forks (Ethereum mainnet, Base, BT EVM)
  • The validator daemon (order-consensus signer + weight emitter, port 9100)
  • The api service (champion-consensus signer, port 8080)
  • A docker-socket-proxy (limits the api’s Docker access to spawning sandboxed solver containers — used during reactive benchmarking)

Plus an opt-in seventh container: Watchtower, for auto-update on :stable tag promotion.

The canonical compose ships at platform/validator/:

terminal SH
cd platform/validator
cp .env.example .env
$EDITOR .env   # fill in every YOUR_* placeholder

docker compose --profile autoupdate up -d   # with Watchtower auto-update
# ── or ──
docker compose up -d                          # without (manual pulls)

The first cold start takes ~60–90 seconds while the three Anvil forks fetch initial state from the upstream RPCs.

Check the state:

terminal SH
docker compose ps                     # all six should be (healthy) after ~90s
docker compose logs -f validator api

You should see:

validator | ProtocolConfig: loaded quorum_bps=6666 from ValidatorRegistry ...
validator | Consensus enabled (id=0x..., peer-mode=discovered, quorum=6666 bps)
validator | Validator starting as ORDER CONSENSUS PEER ...
validator | BlockLoop started (tick_interval=12.0s)
api | Champion consensus enabled (validator=0x..., quorum=N bps from
ChampionRegistry 0x..., validator-set from VR 0x... on chain 964)

Verify

The bundled script runs every endpoint + checks registry state:

terminal SH
bash scripts/check_validator.sh

Or manually:

terminal SH
# Validator health (order-consensus)
curl http://localhost:9100/health

# Api health (champion-consensus)
curl http://localhost:8080/health

# Quorum read from chain
curl http://localhost:9100/consensus/info

# /identity served on both ports (peer-discovery cross-checks both)
curl http://localhost:9100/identity
curl http://localhost:8080/identity

If /consensus/info or the api’s champion_consensus.quorum_required looks wrong, your VALIDATOR_REGISTRY_* envs may point at a stale contract — check the Network Reference.

What each service does

ServiceRole
validatorOrder-consensus signing (re-simulates leader’s order proposals on Anvil, signs approvals if both JS + on-chain scores meet threshold). Emits weights every ~20 min.
apiChampion-consensus signing (reactively re-benchmarks the leader’s champion candidate inside a sandboxed Docker container, signs the certification if scores hold up).
anvil-eth, anvil-base, anvil-btevmLocal forks of each chain so independent re-simulation doesn’t touch mainnet.
docker-socket-proxyFiltered Docker socket access for the api service. Only CONTAINERS + IMAGES + NETWORKS + POST allowed.
watchtower (opt-in)Polls GHCR every hour for a new :stable SHA; recreates validator + api when the tag moves.

What this stack does NOT include

  • Relayer: the transaction submitter is a singleton operated by the subnet team. Your validator signs proposals; the leader’s submission path uses the team’s relayer. You never deal with a gas wallet.
  • Order/champion proposing: only the highest-stake validator’s leader-api does that. As a follower, you receive proposals, verify, and sign.

Peer discovery

Order-consensus and champion-consensus peers are both discovered automatically — no peer-list env required for either.

The flow:

  1. Your daemon publishes its identity

    GET /identity on both :9100 and :8080 returns a fresh EIP-712 signed payload binding (evm_address, hotkey, axon_url). Each request regenerates the signature so it’s never stale.

  2. You publish your axon URL

    Set VALIDATOR_AXON_URL=http://your-host:9100 in the daemon env. The signed payload includes this. Also use btcli to register your axon on the Bittensor metagraph.

  3. Other validators discover you

    Their ProtocolConfig.refresh_loop (default 60s tick) walks the metagraph axon list, probes each /identity, verifies the EIP-712 signature, and cross-checks the recovered EVM address is in ValidatorRegistry.getValidators() and the hotkey matches the metagraph.

What this gives operators:

  • No coordinated restart when a new validator joins — others pick them up within one refresh tick.
  • No peer-list env to maintain across the cluster.
  • IP changes are self-served — update VALIDATOR_AXON_URL, restart, the signed payload re-publishes automatically.

Required env for discovery

VariablePurpose
VALIDATOR_AXON_URLPublic URL serving /identity, typically http://<public-ip>:9100. Signed into the payload — if missing, /identity returns 503.
SUBTENSOR_URLRequired to read the metagraph for axon discovery.
VALIDATOR_REGISTRY_ADDRESSRequired to read the authorized EVM set.

Auto-update

The default MINOTAUR_IMAGE_TAG=stable (in .env.example) and the optional Watchtower container give hands-off updates:

  1. New commit lands on main:latest and :sha-<short> pushed to GHCR.
  2. The new image runs on the subnet team’s prod for a soak period.
  3. The team runs promote-stable.yml to re-tag :sha-<short> as :stable.
  4. Your Watchtower polls GHCR within the next hour, pulls the new image, recreates validator + api. ~30–60s downtime during the recreate.

Manual control instead of Watchtower:

terminal SH
docker compose pull validator api
docker compose up -d --force-recreate validator api

Pin a specific SHA (opt out of auto-update entirely without removing Watchtower):

.env SH
MINOTAUR_IMAGE_TAG=sha-abc1234

Running without Docker

If you prefer Anvil under systemd plus the daemon as a native process:

terminal SH
anvil --host 0.0.0.0 --port 8545 --fork-url "$ETH_UPSTREAM_RPC_URL" --block-time 2
anvil --host 0.0.0.0 --port 8546 --fork-url "$BASE_UPSTREAM_RPC_URL" --chain-id 8453 --no-storage-caching --block-time 2
anvil --host 0.0.0.0 --port 8547 --fork-url "$BITTENSOR_EVM_UPSTREAM_RPC_URL" --chain-id 964 --no-storage-caching --block-time 2

python -m minotaur_subnet.validator.main \
--port 9100 \
--netuid 112 \
--wallet-name "$WALLET_NAME" \
--hotkey-name "$HOTKEY_NAME" \
--subtensor-url "$SUBTENSOR_URL" \
--validator-key "$VALIDATOR_PRIVATE_KEY" \
--tick-interval 12.0 \
--epoch-seconds 1200

Wrap each in its own systemd unit with Restart=on-failure. The Anvil disk-bloat cron above applies either way — adjust it to bounce your systemd units instead of docker compose rm -fsv.

/etc/systemd/system/minotaur-validator.service INI
[Unit]
Description=Minotaur Validator Daemon
After=network-online.target

[Service]
EnvironmentFile=/etc/minotaur/env
ExecStart=/opt/minotaur/.venv/bin/python -m minotaur_subnet.validator.main --port 9100 --epoch-seconds 1200
Restart=on-failure
RestartSec=5
User=minotaur

[Install]
WantedBy=multi-user.target

Local testnet (development only)

For local development of the protocol itself, the full stack ships in Docker Compose:

terminal SH
cd platform/local_testnet

cp .env.example .env
# Edit .env and set ALCHEMY_RPC_URL and BASE_ALCHEMY_RPC_URL

make testnet-up

Local testnet services

ServicePortURL
API8080http://localhost:8080
Validator9100(internal, via Docker network)
Relayer8091http://localhost:8091
Anvil (ETH fork)8545http://localhost:8545
Anvil (Base fork)8546http://localhost:8546
Anvil (BT EVM fork)8547http://localhost:8547
Subtensor9944ws://localhost:9944

The init container automatically registers the subnet (netuid=1 on local), registers validator and miner neurons, and deploys contracts. The validator starts with FORCE_LEADER=1.

Stop with make testnet-down.

Running tests

terminal SH
make test           # unit + app tests (no Docker/Anvil needed)
make test-all       # full suite including emulation and E2E
make test-testnet   # live local_testnet smoke (recreates the Docker stack first)
make test-e2e       # E2E tests (requires Foundry/Anvil)
make test-fork      # mainnet-fork-only E2E (requires ALCHEMY_API_KEY)

Next steps