Concepts
The client
Connecting, the three levels of reading chain state, blocks and waiting, and snapshots.
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 catalogGeneric 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 listsEach 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 subnetPinned 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)