Concepts

The client

Connecting, the three levels of reading chain state, blocks and waiting, and snapshots.

View as Markdown

sub.Client is the async entry point to everything: reads, transactions, and chain metadata. sub.SyncClient is a synchronous facade over the same surface.

import bittensor as sub

async with sub.Client("finney") as client:      # "finney" | "test" | "local" | "ws://..."
    ...

client = sub.SyncClient("finney")                # same API, blocking
client.close()

Networks: finney is mainnet, test is the public testnet, local is a dev node at ws://127.0.0.1:9944, and any ws:// / wss:// endpoint works directly.

Three levels of reading

Typed conveniences — curated helpers with rich return types:

await client.balances.get("5F...")        # Balance
await client.subnets.all()                # list[SubnetInfo]
await client.neurons.all(netuid=1)        # list[Neuron]

Named reads — the full catalog of semantic reads, the same set the CLI's query group exposes. One page each under Queries:

mg = await client.read("metagraph", netuid=1)
take = await client.read("delegate_take", hotkey_ss58="5F...")
client.reads()                            # the machine-readable catalog

Generic accessors — anything in the chain's runtime metadata, via generated descriptors. This is the escape hatch when no read wraps what you need:

tempo = await client.query(sub.storage.SubtensorModule.Tempo, [1])
pairs = await client.query_map(sub.storage.SubtensorModule.Tempo)
ed = await client.constant(sub.constants.Balances.ExistentialDeposit)
info = await client.runtime(sub.runtime_api.NeuronInfoRuntimeApi.get_neurons_lite, [1])

The sub.storage, sub.constants, sub.runtime_api, and sub.calls modules are generated from chain metadata and cover the entire runtime surface.

Typed results: the metagraph

The richest typed result is the metagraph — a whole subnet as one object. client.subnets.metagraph(netuid) returns a Metagraph (the metagraph named read returns the underlying raw runtime record instead):

mg = await client.subnets.metagraph(netuid=1)

for n in mg:                        # iterates neurons, ordered by uid; len(mg) works
    print(n.uid, n.hotkey, n.incentive)

mg.validators                       # neurons holding a validator permit
mg.neuron(5)                        # by uid (KeyError if unknown)
mg.by_hotkey("5F...")               # by hotkey, or None
mg.hotkeys, mg.coldkeys             # parallel address lists

Each MetagraphNeuron carries the identity columns (uid, hotkey, coldkey), status (active, validator_permit, last_update, block_at_registration), the 0..1-normalized scores (rank, trust, consensus, incentive, dividends, pruning_score), balances (emission, alpha_stake, tao_stake, total_stake), and axon (the served ip:port or None), identity, and commitment. Subnet-level fields (tempo, price, owner_hotkey, ...) live on the Metagraph itself, and mg.raw keeps the untouched runtime record.

commitment is the neuron's entry in the Commitments pallet, timelock-aware:

c = mg.neuron(5).commitment          # or mg.commitments[5]; None if none
if c is not None:
    c.status                         # "plain" | "sealed" | "revealed"
    c.value                          # visible content, or None while sealed
    c.reveals_at                     # UTC datetime a sealed payload opens (else None)

client.subnets.commitments(netuid) fetches just the commitments (keyed by hotkey, much cheaper than the full metagraph); the named reads are commitment, commitments, and revealed-commitment. Writing one has no dedicated intent: the chain call is Commitments.set_commitment, reachable via the raw-call escape hatch (Advanced submission).

Blocks, time, and waiting

await client.block()                       # current block number
await client.block_info(123)               # header + extrinsics + timestamp
async for header in await client.blocks(): ...   # subscribe to new blocks

await client.wait_for_block(1_000_000)
await client.wait_for_timestamp("2026-08-01T00:00:00Z")
await client.wait_for_epoch(netuid=1)      # next epoch boundary on a subnet

Pinned snapshots

client.at(block) returns a Snapshot: the domain namespaces (balances, subnets, neurons, staking) pinned to one block, so a multi-read computation sees one consistent state instead of racing the chain. Writes through a snapshot are rejected — it is a view, not a signer.

snap = await client.at(await client.block())
bal = await snap.balances.get("5F...")
subnets = await snap.subnets.all()

Writing

plan, execute, execute_tool, submit_shielded, and submit_call are covered in The transaction model and Advanced submission.

Logging

The SDK logs under the bittensor.* namespace and never configures handlers — it is silent unless your application opts in:

import logging
logging.getLogger("bittensor").setLevel(logging.DEBUG)