Develop and test subnets locally with zero cost and instant feedback. No TAO needed, no testnet delays.
- Docker installed and running
- agcli built:
cargo build --release
One command gives you a fully-configured test environment with a local chain, funded accounts, and registered neurons:
agcli localnet scaffoldThis starts a local subtensor chain (fast-block mode, 250ms blocks) and returns a JSON manifest with:
- WebSocket endpoint (
ws://127.0.0.1:9944) - 1 subnet with tuned hyperparameters (tempo=100, weights_rate_limit=0, commit_reveal=false)
- 3 funded neurons:
validator1(1000 TAO),miner1(100 TAO),miner2(100 TAO) - Deterministic keypairs derived from neuron names (e.g.,
//validator1_sn1— reproducible across runs, same keys every time)
# Default scaffold: 1 subnet, 3 neurons
agcli localnet scaffold
# Or start just the chain (no subnet setup)
agcli localnet start# Check chain status
agcli localnet status
# View your subnet
agcli subnet list --network local
# View the metagraph
agcli subnet metagraph 1 --network local
# Check account balances
agcli balance --address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY --network local# Register another neuron
agcli subnet register-neuron 1 --network local
# Set weights as a validator
agcli weights set --netuid 1 "0:100,1:200" --network local
# Serve an axon endpoint
agcli serve axon --netuid 1 --ip 127.0.0.1 --port 8091 --network local
# View hyperparameters
agcli subnet hyperparams 1 --network localThe scaffold command prints a JSON manifest you can use in scripts:
# Capture output for scripting
OUTPUT=$(agcli localnet scaffold 2>/dev/null)
# Extract neuron SS58 addresses with jq
VALIDATOR=$(echo "$OUTPUT" | jq -r '.subnets[0].neurons[0].ss58')
MINER=$(echo "$OUTPUT" | jq -r '.subnets[0].neurons[1].ss58')
NETUID=$(echo "$OUTPUT" | jq -r '.subnets[0].netuid')
# Now use them in subsequent commands
agcli balance --address "$VALIDATOR" --network local
agcli weights set --netuid "$NETUID" "0:100,1:200" --network localuse agcli::chain::Client;
use agcli::types::balance::Balance;
use agcli::types::network::NetUid;
use sp_core::{sr25519, Pair};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Connect to local chain
let client = Client::connect("ws://127.0.0.1:9944").await?;
// Use scaffold keypairs (deterministic from name)
let validator = sr25519::Pair::from_string("//validator1_sn1", None)?;
let miner = sr25519::Pair::from_string("//miner1_sn1", None)?;
// Query subnet state
let neurons = client.get_neurons_lite(NetUid(1)).await?;
println!("Neurons on SN1: {}", neurons.len());
// Set weights
let uids = vec![0u16, 1u16];
let weights = vec![32768u16, 32768u16];
client.set_weights(&validator, NetUid(1), &uids, &weights, 0).await?;
// Query metagraph
let mg = client.get_metagraph(NetUid(1)).await?;
println!("Metagraph: n={}, block={}", mg.n, mg.block);
Ok(())
}agcli localnet stopCreate a scaffold.toml to customize your test environment:
[chain]
image = "ghcr.io/opentensor/subtensor-localnet:devnet-ready"
port = 9944
start = true
timeout = 120
# Subnet with fast tempo for rapid testing
[[subnet]]
tempo = 50
max_allowed_validators = 8
min_allowed_weights = 1
weights_rate_limit = 0
commit_reveal = false
[[subnet.neuron]]
name = "validator1"
fund_tao = 1000.0
register = true
[[subnet.neuron]]
name = "miner1"
fund_tao = 100.0
register = true
[[subnet.neuron]]
name = "miner2"
fund_tao = 100.0
register = trueThen run:
agcli localnet scaffold --config scaffold.tomlAdd multiple [[subnet]] blocks to test cross-subnet operations:
[[subnet]]
tempo = 100
[[subnet.neuron]]
name = "val_sn1"
fund_tao = 500.0
register = true
[[subnet]]
tempo = 200
[[subnet.neuron]]
name = "val_sn2"
fund_tao = 500.0
register = trueTwo accounts are pre-funded in the genesis block:
| Account | SS58 | Seed | TAO | Notes |
|---|---|---|---|---|
| Alice | 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY |
//Alice |
1,000,000 | Sudo key |
| Bob | 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty |
//Bob |
1,000,000 | Standard dev account |
Alice has sudo privileges and can call AdminUtils to configure subnet hyperparameters.
Scaffold-created neurons use deterministic keypairs formatted as //{name}_sn{netuid} (e.g., //validator1_sn1). This means every scaffold run produces the same keys — your test scripts stay stable across sessions. You can derive the keypair in Rust with sr25519::Pair::from_string("//validator1_sn1", None)?.
- Block time: 250ms (fast-block mode, ~240x faster than mainnet)
- Validators: 3 nodes in the Docker container
- Docker image:
ghcr.io/opentensor/subtensor-localnet:devnet-ready - WebSocket:
ws://127.0.0.1:9944(default)
Some runtime features require configuration on localnet:
| Feature | Default | Fix |
|---|---|---|
| Staking (subtoken) | Disabled per-subnet | Enable via sudo: AdminUtils::sudo_set_subtoken_enabled(netuid, true). The scaffold command does this automatically. |
| Commit-reveal weights | Enabled per-subnet | Disable via sudo: AdminUtils::sudo_set_commit_reveal_weights_enabled(netuid, false). Scaffold sets this. |
| Rate limits | Non-zero defaults | Zero out via sudo: AdminUtils::sudo_set_weights_set_rate_limit(netuid, 0) etc. Scaffold handles this. |
| State pruning | Fast blocks prune old state quickly | Query recent blocks only; use --at-block with recent block numbers |
| RPC stability | Fast blocks can cause WebSocket drops | Use Client::reconnect() in Rust SDK, or retry failed RPC calls |
A typical development cycle:
- Start:
agcli localnet scaffold(once per session) - Develop: Write your validator/miner code against
ws://127.0.0.1:9944 - Iterate: Changes take effect in <1 second (250ms blocks)
- Reset:
agcli localnet resetto wipe state and start fresh - Graduate: Once working locally, deploy to testnet (
--network test)
"Docker not installed or not running" — Install Docker and start the daemon.
"Container failed to start" — Pull the image first: docker pull ghcr.io/opentensor/subtensor-localnet:devnet-ready
"Chain did not become ready" — The container started but isn't producing blocks. Check logs: agcli localnet logs
Port conflict — Another service is using port 9944. Use --port 9955 to pick a different port.
"Transaction is outdated" — Fast-block mode can cause mortal-era transactions to expire. Retry the transaction.
- Localnet Commands — Full CLI reference
- Subnet Builder Guide — Production subnet operations
- Getting Started — CLI basics and wallet setup