thirty spkesDOCS
HomeDashboardGet an API key

Mine the Subnet

You compete to build the conductor with the highest benchmark quality per dollar. You ship weights; the validator runs them. This page is the operational guide; the authoritative version lives in the repository as docs/MINER.md.

Status. v3 is live on Bittensor mainnet, netuid 99, and open for submissions. Everything below is the live command set, and the netuid in the examples is mainnet's. To rehearse first, the same commands take NETUID=526 NETWORK=test.

1. What you submit

Full weights against the pinned reference architecture — not an adapter, not a LoRA.

architecture Qwen/Qwen3.6-35B-A3B, pinned revision
dtype bfloat16, pinned — two precisions are not the same experiment
format .safetensors only; .bin / .pt / .pth are refused
size ~70 GB per submission

Weights are free, the architecture is not. Reach your weights however you like — full fine-tune, continued pretraining, distillation, RL, merging, or LoRA training followed by a merge back into full weights. What is uploaded is always the merged full model, and every tensor's name and shape must match the reference inventory. Anything unmatched is refused, not coerced.

A hotkey gets exactly one submission, ever. Not one per window — one. A malformed upload, a wrong dtype, a stray .bin spends it. Use the dev kit before you spend the shot; it runs the same admission checks the validator will.

Copy config.json and the tokenizer files from the reference tree verbatim. save_pretrained rewrites both, and a different transformers version writes them differently — renames fields, moves them between blocks, reorders keys. Your weights can be perfect and the tree still refused, because the tokenizer is compared by digest and the architectural fields value by value. Train however you like; when you save, put the reference's own config.json and tokenizer back. Then run check until it says admitted.

2. How you submit — step by step

Three commands spend nothing (hotkey, identity, check) and one spends the shot (submit). The order is what protects you: every check that can fail runs before anything that costs money.

0. What you need

git clone https://github.com/thirtyspokes99/thirtyspokes && cd thirtyspokes
uv venv --python 3.12 && uv pip install -e ".[dev,miner]"   # includes btcli 11 (uv run btcli --version)
hf download Qwen/Qwen3.6-35B-A3B --revision 995ad96eacd98c81ed38be0c5b274b04031597b0 --local-dir ./reference

A coldkey with TAO for the registration burn (btcli subnets burn-cost $NETUID -n $NETWORK prints it; btcli subnets register shows the fee and asks before spending it), the pinned reference tree above (~70 GB — check and submit compare against it), and your model tree as safetensors in bfloat16.

WALLET=your-coldkey-wallet     # or: btcli wallet new-coldkey -w "$WALLET"
HOTKEY=v3-1                   # a NEW hotkey — it gets one submission, ever
NETUID=99 NETWORK=finney       # testnet rehearsal: NETUID=526 NETWORK=test

1. Create an ed25519 hotkey

btcli wallet new-hotkey -w "$WALLET" -H "$HOTKEY" --crypto-type ed25519
btcli wallet show -w "$WALLET" -H "$HOTKEY"        # hotkey_crypto_type must read ed25519

--crypto-type ed25519 is the whole point. btcli's default is sr25519, and the credential envelope the owner publishes for you is sealed to your hotkey's own public key (an X25519 key derived from the ed25519 one) — a sr25519 hotkey can never open it. -w is the coldkey wallet and -H the hotkey inside it (global options of the btcli that ships inside the bittensor 11 package; the separately published bittensor-cli 9.x spells them --wallet-name/--hotkey and is not what this page uses). btcli 11 stores hotkeys unencrypted (its wallet unlock is for the coldkey), which is what submit needs: it reads the seed out of the keyfile to open the envelope. A hotkey keyfile that is encrypted came from another tool; decrypt it in place first, which prompts for its password: uv run python -c "from bittensor_wallet import Wallet; Wallet(name='$WALLET', hotkey='$HOTKEY').hotkey_file.decrypt()". The coldkey stays encrypted; it is never read here.

Verify before you pay — reads the keyfile and nothing else:

thirtyspokes-miner --netuid $NETUID --network $NETWORK --wallet "$WALLET" --hotkey "$HOTKEY" hotkey
hotkey             5Dm…N9Mt
scheme             ed25519 — the mailbox envelope can be sealed to it
keyfile            readable
safe to register:  btcli subnets register --netuid 99 -n finney -w … -H …

A sr25519 hotkey is refused here, before the burn, with the command to create the right one.

2. Fund the coldkey, then register

The burn, the transaction fee and the MEV-shield carrier fee all come out of the coldkey's free balance, and burned_register cannot be submitted unshielded, so an unfunded coldkey stops with MEV-shielded submission needs free TAO for the outer carrier fee before anything is sent. Free TAO must cover the burn (btcli subnets burn-cost $NETUID -n $NETWORK) plus about τ0.01:

btcli wallet balance -w "$WALLET" -n $NETWORK
btcli wallet transfer -w <funded-wallet> -n $NETWORK --dest "$WALLET" --amount-tao 0.05   # if it does not; --dest takes a wallet name or an ss58
btcli subnets register --netuid $NETUID -n $NETWORK -w "$WALLET" -H "$HOTKEY" --dry-run   # fee and effect, nothing sent
btcli subnets register --netuid $NETUID -n $NETWORK -w "$WALLET" -H "$HOTKEY"             # asks, then spends

Pays the burn from the coldkey (prompts for its password; -y skips only the confirmation) and assigns a uid. btcli wallet overview -w "$WALLET" -n $NETWORK --netuid $NETUID shows it once finalised.

3. Send the owner your identity

thirtyspokes-miner --netuid $NETUID --network $NETWORK --wallet "$WALLET" --hotkey "$HOTKEY" identity
hotkey             5Dm…N9Mt
uid                17
registration block 7953840
registration id    3f9c…e2a1
upload prefix      submissions/3f9c…e2a1/
  mailbox keys to poll:
    generation 1: mailbox/v1/3f9c…e2a1/00000000000000000001.bin

Every line derives from four chain facts by a function the owner runs too, so neither side chooses your prefix. Send the owner the whole block. You need nothing back from them: the owner's ed25519 public key is pinned in the tools (config.OWNER_MAILBOX_KEY), and every command refuses an envelope signed by anything else, so nobody can hand you a different one. They publish a credential at generation 1's key on the subnet's public store, https://store.thirtyspokes.ai — sealed to your hotkey, signed by their mailbox key, and polled by the tools by default.

4. Register your OpenRouter key

Your arm runs on your own OpenRouter key (whitepaper §4, D18). Create one on your account — give it a credit limit there if you want a hard ceiling on your exposure — put it in a file, and register it with the cap one window may spend:

printf '%s' 'sk-or-v1-…' > ~/.openrouter-key && chmod 600 ~/.openrouter-key
thirtyspokes-miner --netuid $NETUID --network $NETWORK --wallet "$WALLET" --hotkey "$HOTKEY" register-key \
  --key-file ~/.openrouter-key --cap-usd 20

The key is sealed to the owner's key before it leaves your machine and the record is signed by your hotkey; it lands beside your tree, and only the owner's validator opens it. It uses the same envelope submit opens, so run it any time that credential is valid — before or after step 7 — and run it again to rotate the key or move the cap. Do it before your window opens: an entry with no key is deferred with its shot intact but is not judged, and an arm that exhausts its cap mid-slice scores zero on every task it never reached (§6 below).

5. Prepare the tree

safetensors shards only, bfloat16; copy config.json and every tokenizer file from ./reference verbatim after you save; no *.py, no auto_map, no trust_remote_code. Run the dev kit until the endings look like routing rather than looping.

6. check — the validator's gate, locally, free

thirtyspokes-miner --netuid $NETUID --network $NETWORK --wallet "$WALLET" --hotkey "$HOTKEY" check \
  --model ./weights --reference ./reference

admitted: this tree passes the checks the validator runs (§1.1), or the validator's refusal verbatim and a non-zero exit. Run it until it says admitted.

7. submit — this spends the shot

thirtyspokes-miner --netuid $NETUID --network $NETWORK --wallet "$WALLET" --hotkey "$HOTKEY" submit \
  --model ./weights --reference ./reference

In order: admission locally (a refused tree costs nothing) → the chain is read (a spent hotkey is refused before a byte moves) → the envelope is fetched with no credentials and checked field by field (signed by the pinned owner key, for this hotkey, this registration, this prefix, not expired) → the manifest is built and signed by your hotkey → the tree is uploaded against a credential scoped to your prefix, skipping files already present with the right digest, so an interrupted upload resumes by running submit again → manifest.json is written last → the ready signal is committed on chain. That is the shot.

submitted 31 files (70,318,145,536 bytes uploaded, 0 already present)
  registration  3f9c…e2a1
  manifest      e43a72c5…df91        ← the digest the chain now names
  committed by  5Dm…N9Mt
THE SHOT IS NOW SPENT. Keep the OpenRouter account behind your registered key funded (§4, docs/MINER.md §6).

If no key is registered yet, the last line says so instead (step 4).

If the credential expires mid-upload (one day by default; 70 GB is hours on a normal link): ask the owner for a rotation and re-run with --generation 2. More time for the one submission, never a second one.

8. After you submit

You are queued in (commit block, hotkey) order, six judged per window; beyond a queue depth of twelve your commit is refused at the door with the shot intact. When your window runs, the validator re-hashes every shard against your manifest, admits and serves your tree, runs your arm on the same slice as the king's, and publishes the verdict — read it on the dashboard or at v3/reveal/<window>.json: duelled + won → crowned; duelled → refused with all four conditions; deferred → not judged yet, shot intact; refused → failed admission or the load, shot spent. You cannot resubmit with this hotkey; a second attempt is a new hotkey and registration. If crowned, keep your key funded.

When something goes wrong

the tool says what it means what to do
is not an ed25519 hotkey the hotkey is sr25519 btcli wallet new-hotkey … --crypto-type ed25519, register that one
keyfile … is encrypted the hotkey file has a password decrypt it in place (the one-liner in step 1); btcli 11 never encrypts hotkeys, so the file came from another tool
MEV-shielded submission needs free TAO for the outer carrier fee the coldkey's free balance is empty fund it (step 2): burn + fee + the shield's carrier fee, and burned_register cannot run unshielded
No such option: --wallet-name you are running bittensor-cli 9.x, a different program use the btcli inside the bittensor 11 package (uv run btcli --version); its flags are -w, -H, -n
no hotkey keyfile at … the hotkey is not on this machine create it (step 1), or copy the registered hotkey's keyfile from the machine that holds it
holds no uid not registered on this netuid, or not finalised check --netuid/--network; wait a block
has already committed manifest … this hotkey's shot is spent a new hotkey
mailbox envelope is not signed by the owner the envelope was not signed by the owner key pinned in the tools you are pointed at a different store or a different subnet — check --netuid and --mailbox-url; never take an owner key from someone
mailbox credential has already expired the envelope outlived its TTL ask for a rotation; --generation 2
would be REFUSED at admission: … the tree fails the gate fix what the line names — usually config.json/tokenizer drift
a 404 fetching the envelope the owner has not published your generation yet wait; check the key against identity
budget_exhausted rows in your reveal your key or your cap ran out mid-slice raise the cap, or fund the key above your estimate

3. What your model has to emit

See Build a Conductor. In short: one action per step (REASON / DELEGATE / RETRY / STOP), MAX_STEPS = 12, three consecutive parse failures force STOP. Any model on the catalog is allowed; there is no owner-curated shortlist.

4. What you cannot do

Nothing your model writes ever reaches the worker: the only things that reach an answering model are the benchmark's own task text and a validated model ID. Your reasoning is kept verbatim in the published trace and refused at the seam. So the answer path is not where the competition is; the competition is which model you pick, and what you spend. What Cannot Be Gamed has the full list.

5. How you are scored

Per benchmark, final_b = quality_b − λ_b · spend_b / C_b, equal weight per benchmark. λ is fitted so that spending more is neither rewarded nor punished in itself, only relative to what it buys. To take the crown, all of these must hold:

  1. the paired delta in final exceeds ε = 0.05, and its bootstrap lower bound clears zero;
  2. leave-one-out — drop your single best benchmark and you still win;
  3. median — the typical benchmark moved your way.

A near-copy of the king never wins: it ties, and a tie fails a strict beat. At ~250 tasks per arm ε behaves like a 7–8 point margin; a challenger genuinely at +0.05 is crowned about half the time.

6. Budget

You pay on your own OpenRouter key, with your own cap. Register it once, sealed to the owner:

thirtyspokes-miner register-key --wallet <name> --hotkey <hotkey> --cap-usd 25

The key is encrypted to the owner's mailbox key, signed by your hotkey and kept beside your submission. Only the owner's validator can open it, and it uses it for one thing: running your arm through the owner's gateway, sending the same pinned request every other arm sends, and metering each call's real cost against your cap and against what the key itself reports it can still spend. Every debit is against a signed receipt — which is what makes a model ID mean the same thing on both sides of a duel.

A key the provider refuses, or an account with nothing left, defers your entry with your shot intact rather than scoring you zero. An arm that exhausts mid-slice has its remaining tasks published as budget_exhausted, distinct in the reveal from a model that stalled and from a validator that ran out of clock. If you become king, keep the key funded: your arm is re-run every window.

You are charged for every delegate, not for every call. A window shares one outcome table across all its arms (see the protocol), so a delegate another arm already bought is handed to you at the same price without the provider being asked again. Your score is computed on the full charged amount; your key pays only for the rows you were the first to ask for. The reveal publishes both numbers for your arm.

How much is enough? Whatever your routing costs. For an order of magnitude — measured over 30 episodes per fixed policy on a frontier-free pool, not a budget anyone is asking you for — per-episode spend was $0.001 always calling the cheapest model, $0.067 always calling the strongest, $0.062 for a cheap-then-escalate cascade; against ~250 tasks per arm that is roughly $0.25 to $17 per window, and a floor rather than a bracket, since the catalog carries rungs a hundred times dearer. Fund above your own estimate.

What else entering costs you: the registration burn at whatever the network charges; your training compute, which this project has never measured and does not estimate; and the upload of ~70 GB. Storage is the owner's. The subnet charges nothing beyond what your own arm spends on your own key.

7. The queue and the immunity trap

Challengers are judged in (commit block, hotkey) order, three per window. Commits beyond MAX_QUEUE_DEPTH = 6 are refused, not accepted and deferred — taking a registration burn for a slot that would expire before it is judged is the outcome the cap exists to prevent. Queued challengers roll over with their shot unspent; a queue is never dropped, only delayed. The owner's chain-side obligation is that the subnet's immunity_period covers three windows, and the validator refuses to start otherwise.

8. What is published about you

Every window publishes the verdict and its diagnostics: your final and per-benchmark quality and spend, the four conditions with their numbers, the leave-one-out benchmark that was dropped, the sign test (a diagnostic that does not gate), your score split into tasks seen in an earlier window and tasks nobody has seen, how much of your arm was replayed from the window's outcome table against how much your key bought, which endpoints served your fills, and where your budget ran out if it did. Your own decision traces are yours; the owner's arms are published in full.

Your weights are not published unless you win. The upload goes to a private bucket that only your own scoped credential and the validator can read — not before your duel and not after it. A submission reaches the public models bucket, at models/sha256/<manifest digest>/, only when it takes the crown, and a published king is never deleted. A losing submission's weights are deleted 14 days after it is judged; its manifest.json, and therefore the record that it was submitted at all, is kept forever.