Migrating from v9/v10
Complete mapping from the legacy bittensor SDK (v10) and bittensor-cli (v9) to the unified v11 package — written so an LLM can upgrade your scripts from this page alone.
Bittensor 11 replaces two packages — the bittensor SDK (v10.x) and the
separate bittensor-cli (v9.x, the old btcli) — with one package that
contains both. The API is not a rename of the old one: the Subtensor class
and its ~165 methods are gone, replaced by a small Client with typed reads
and a catalog of intents executed through one plan/execute pipeline.
This page maps every commonly scripted old call and CLI command to its v11
equivalent.
If you are an LLM migrating a user's scripts, follow this procedure:
- Read this entire page — the tables are exact, not illustrative.
- Apply the SDK mappings and CLI mappings mechanically; where a row says "no direct equivalent", use the listed escape hatch.
- Watch the semantic traps in Money, Weights and take, and Results and errors — these change behavior, not just names.
- Verify against the live catalogs rather than guessing:
sub.intents.list_tools()andsub.reads.list_reads()in Python,btcli toolsandbtcli query --helpon the CLI, or the generated references under Transactions and Queries. - Preview every rewritten mutation with
client.plan(...)or--dry-runbefore submitting anything.
Packages and install
| Old | New | |
|---|---|---|
| SDK | bittensor 10.x | bittensor 11.x |
| CLI | bittensor-cli 9.x (separate package) | included in bittensor |
| Wallet | bittensor_wallet (separate package) | included: bittensor.wallet |
| Python | 3.10+ | 3.10–3.13 |
uv pip install bittensor # library + btcli, one packageThe [cli] and [evm] extras still parse (bittensor[cli] installs fine)
but are empty — everything is in the base install now.
Remove bittensor-cli and bittensor_wallet from your requirements; both
are superseded. Wallets on disk are keyfile-compatible — nothing to
convert (see Wallets).
Upgrading an existing environment
Order matters in an environment that already has the old stack, because
bittensor-cli 9.x and bittensor 11.x both own the btcli command:
pip uninstall -y bittensor-cli bittensor-wallet
pip install -U bittensorIf you upgraded first and uninstalled bittensor-cli after, pip deletes the
btcli script (it is still listed in the old package's file manifest).
Nothing is lost — reinstate it with:
pip install --force-reinstall --no-deps bittensorWhile a stale bittensor-cli is still installed, the v11 btcli prints a
warning with this fix on every run.
Two version pitfalls that fail silently:
- Python 3.14: v10 allowed it, v11 supports 3.10–3.13. On a 3.14
interpreter
pip install -U bittensorquietly keeps 10.x (pip picks the newest release whoserequires-pythonmatches). Ifbtcli --versionstill says 9.x/10.x after upgrading, checkpython --versionfirst. - Pinned requirements: anything pinning
bittensor<11orbittensor~=10keeps working against v10 untouched — the new package changes nothing until you move the pin.
Platform support: wheels ship for Linux (x86_64, aarch64) and macOS (Apple Silicon and Intel), Python 3.10–3.13 — the same coverage as the old stack. Windows is unsupported natively (as before); use WSL. On any other platform pip falls back to a source build, which requires a Rust toolchain.
The model change in one example
Old (v10):
import bittensor as bt
subtensor = bt.Subtensor(network="finney")
wallet = bt.Wallet(name="my_coldkey", hotkey="my_hotkey")
balance = subtensor.get_balance("5F...")
response = subtensor.add_stake(
wallet, netuid=1, hotkey_ss58="5F...validator", amount=bt.tao(10),
)
if not response.success:
print(response.message)New (v11):
import asyncio
import bittensor as sub
from bittensor.wallet import Wallet
async def main():
wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
async with sub.Client("finney") as client:
balance = await client.balances.get("5F...")
result = await client.execute(
sub.AddStake(hotkey_ss58="5F...validator", netuid=1, amount_tao=10),
wallet,
)
if not result.success:
print(result.error.code, result.error.remediation)
asyncio.run(main())The three structural changes:
- One client, no method zoo.
Subtensor/AsyncSubtensorbecomesub.Client(async) orsub.SyncClient(blocking, same surface). Every read is a typed method on a category namespace (client.subnets,client.staking,client.delegation,client.prices, ... — with autocomplete and signature help), is also dispatchable by name (client.read("uid", hotkey_ss58=..., netuid=...)), and anything else is reachable through generic accessors (client.query(sub.storage...)). See The client. - Transactions are intents. Every mutation is a small dataclass
(
sub.Transfer,sub.AddStake,sub.SetWeights, ...) executed throughclient.plan(intent, wallet)/client.execute(intent, wallet). See The transaction model. - Results are data.
ExtrinsicResponsebecomesExtrinsicResultwith a classifiederror.codeand aremediationhint instead of prose.
Connecting
| v10 | v11 |
|---|---|
bt.Subtensor(network="finney") | sub.SyncClient("finney") (blocking) |
bt.AsyncSubtensor(...) + await sub.initialize() | async with sub.Client("finney") as client: |
get_async_subtensor(network=...) | await sub.Client(...).connect() |
Subtensor(config=config) | gone — pass arguments directly |
Subtensor(..., mock=True) / MockSubtensor | gone — test against a local node |
fallback_endpoints=, archive_endpoints=, retry_forever= | same names on Client / SyncClient |
subtensor.close() | client.close() / context manager |
Network names finney, test, archive, local resolve to the same
endpoints as before, and any ws:// / wss:// URL is accepted directly. The
extra v9-CLI aliases (dev, rao, latent-lite) are gone — pass the URL.
Every v10 read took block=. In v11, namespace methods still accept
block=, and for multi-read consistency pin a snapshot:
snap = await client.at(block) # Snapshot: same read surface, one block
bal = await snap.balances.get("5F...")Environment variables and configuration
| v10 SDK / v9 CLI | v11 |
|---|---|
BT_SUBTENSOR_NETWORK | BT_NETWORK |
BT_SUBTENSOR_CHAIN_ENDPOINT | pass a ws:// URL as the network; BT_CHAIN_ENDPOINT overrides local |
BT_WALLET_NAME | BT_WALLET |
BT_WALLET_HOTKEY | BT_WALLET_HOTKEY (unchanged) |
BT_WALLET_PATH | BT_WALLET_PATH (unchanged) |
| — | BT_WALLET_PASSWORD, BT_WALLET_PASSWORD_FILE (non-interactive coldkey unlock) |
BT_NO_PARSE_CLI_ARGS | gone — the SDK never parses CLI args |
BT_LOGGING_* | gone — standard logging (see below) |
BT_AXON_*, BT_PRIORITY_*, BT_MEV_PROTECTION | gone |
BTCLI_CONFIG_PATH (~/.bittensor/config.yml) | BTCLI_CONFIG (~/.bittensor/btcli.json) |
The old argparse machinery — bt.Config, Subtensor.add_args,
Wallet.add_args, --subtensor.network, --wallet.name flags parsed inside
the SDK — is gone entirely. Scripts own their own argument parsing; the SDK
reads nothing from sys.argv.
bt.logging is gone. The SDK logs under the bittensor.* namespace via the
standard library and configures no handlers:
import logging
logging.getLogger("bittensor").setLevel(logging.DEBUG)Reading chain state
General rules: every named read below is a typed method on its namespace
(client.staking.stake_for_coldkey(...)) and equally dispatchable by name
(client.read("stake_for_coldkey", ...)); both take the same ..._ss58
parameter names the old methods used. Results that used to be raw u16/u64
integers come back normalized (weights and bonds as 0..1 floats, balances as
Balance).
Anything without a named read is reachable through the generic accessors —
client.query(sub.storage.Pallet.Item, [params]),
client.query_map(...), client.constant(sub.constants...),
client.runtime(sub.runtime_api...) — which cover the entire runtime
surface, replacing query_subtensor, query_module, query_map,
query_map_subtensor, query_constant, query_runtime_api, and
state_call.
Balances and stake
v10 Subtensor method | v11 |
|---|---|
get_balance(address) | await client.balances.get(address) |
get_balances(*addresses) | await client.balances.get_many([...]) |
get_existential_deposit() | await client.balances.existential_deposit() |
get_stake(coldkey_ss58, hotkey_ss58, netuid) | await client.staking.get(coldkey_ss58, hotkey_ss58, netuid) |
get_stake_info_for_coldkey(coldkey_ss58) | await client.staking.stake_for_coldkey(coldkey_ss58=...) → list[StakePosition] |
get_stake_info_for_coldkeys([...]) | client.staking.stake_for_coldkeys(coldkey_ss58s=[...]) |
get_stake_for_coldkey_and_hotkey(...) | client.staking.stake(...) per netuid, or filter stake_for_coldkey |
get_staking_hotkeys(coldkey_ss58) | client.staking.staking_hotkeys(coldkey_ss58=...) |
get_auto_stakes(coldkey_ss58) | client.staking.auto_stake_all(coldkey_ss58=...) |
| valuing stake in TAO (manual sum) | client.staking.stake_value_for_coldkey(coldkey_ss58=...) — never sum alpha yourself (Money) |
sim_swap(...), get_stake_add_fee(...) | client.prices.quote_stake(netuid=..., amount_tao=...) |
get_unstake_fee(...), get_stake_movement_fee(...) | client.prices.quote_unstake(netuid=..., amount_alpha=...) |
get_transfer_fee(...), get_extrinsic_fee(...) | (await client.plan(intent, wallet)).fee |
get_stake_lock / get_coldkey_lock | client.locks.coldkey_lock(coldkey_ss58=..., netuid=...) |
get_stake_locks(...) | client.locks.locks_for_coldkey(coldkey_ss58=...) |
get_hotkey_conviction(hotkey_ss58, netuid) | client.locks.hotkey_conviction(...) |
get_most_convicted_hotkey_on_subnet(netuid) | client.locks.most_convicted_hotkey(netuid=...) |
Subnets and hyperparameters
| v10 | v11 |
|---|---|
all_subnets() / get_all_subnets_info() | await client.subnets.all() → list[SubnetInfo] |
subnet(netuid) / get_subnet_info(netuid) | await client.subnets.info(netuid) |
get_all_subnets_netuid() / get_total_subnets() | derive from client.subnets.all() |
subnet_exists(netuid) | client.subnets.subnet(netuid=...) and check |
get_subnet_burn_cost() | client.subnets.subnet_registration_cost() |
recycle(netuid) (registration price) | await client.subnets.burn(netuid) |
get_subnet_price(netuid) / get_subnet_prices() | client.prices.alpha_price(netuid=...) / client.prices.alpha_prices() |
get_subnet_hyperparameters(netuid) | client.subnets.subnet_hyperparameters(netuid=...) |
difficulty / immunity_period / min_allowed_weights / max_weight_limit / weights_rate_limit | same names on client.hyperparameters: client.hyperparameters.immunity_period(netuid=...) etc. |
commit_reveal_enabled(netuid) | client.subnets.commit_reveal_enabled(netuid) |
get_subnet_reveal_period_epochs(netuid) | client.hyperparameters.reveal_period(netuid=...) |
tempo(netuid) / subnetwork_n(netuid) | fields on client.subnets.info(netuid) / the metagraph |
blocks_since_last_step / blocks_until_next_epoch / get_next_epoch_start_block | same names on client.epochs (client.epochs.blocks_until_next_epoch(netuid=...)) |
blocks_since_last_update(netuid, uid) | client.epochs.blocks_since_last_update(netuid=..., uid=...) |
is_subnet_active(netuid) | client.epochs.epoch_status(netuid=...) / client.subnets.subnet_start_schedule(netuid=...) |
get_mechanism_count / get_mechanism_emission_split | client.subnets.mechanism_count(...) / client.subnets.mechanism_emission_split(...) |
weights(netuid, mechid) | client.weights.weights(netuid=..., mechid=0) — now dict[uid, dict[uid, float]] normalized 0..1, not u16 tuples |
bonds(netuid, mechid) | client.weights.bonds(netuid=..., mechid=0) — same shape change |
get_timelocked_weight_commits(...) | client.weights.timelocked_weight_commits(netuid=..., mechid=0) |
Neurons, registration, delegates
| v10 | v11 |
|---|---|
neurons(netuid) / neurons_lite(netuid) | await client.neurons.all(netuid, lite=...) |
neuron_for_uid(uid, netuid) | (await client.subnets.metagraph(netuid)).neuron(uid) |
get_uid_for_hotkey_on_subnet(hotkey_ss58, netuid) | client.neurons.uid(hotkey_ss58=..., netuid=...) |
is_hotkey_registered / ..._on_subnet / ..._any | client.neurons.uid(...) / client.neurons.netuids_for_hotkey(...) |
get_netuids_for_hotkey(hotkey_ss58) | client.neurons.netuids_for_hotkey(hotkey_ss58=...) |
get_hotkey_owner(hotkey_ss58) / does_hotkey_exist | client.neurons.hotkey_owner(hotkey_ss58=...) |
get_owned_hotkeys(coldkey_ss58) | client.neurons.owned_hotkeys(coldkey_ss58=...) |
get_delegates() / get_delegate_by_hotkey / is_hotkey_delegate / get_delegate_take / get_delegated | same names on client.delegation: delegates, delegate, is_delegate, delegate_take, delegated |
get_delegate_identities() | client.identity.hotkey_identities(hotkey_ss58s=[...]) |
get_children / get_children_pending / get_parents | client.delegation.children / pending_children / parents |
query_identity(coldkey_ss58) | client.identity.identity(coldkey_ss58=...) |
get_commitment / get_commitment_metadata | client.identity.commitment(netuid=..., hotkey_ss58=...) |
get_all_commitments(netuid) | await client.subnets.commitments(netuid) |
get_revealed_commitment* | client.identity.revealed_commitment(netuid=..., hotkey_ss58=...) |
get_neuron_certificate(...) | no named read — generic storage query |
Chain, blocks, and the rest
| v10 | v11 |
|---|---|
subtensor.block / get_current_block() | await client.block() |
get_block_info(block) / get_block_hash | await client.block_info(block) |
get_timestamp(block) | await client.timestamp(block) |
wait_for_block(block) | await client.wait_for_block(block) (plus wait_for_timestamp, wait_for_epoch) |
tx_rate_limit() / is_fast_blocks() | client.chain.tx_rate_limit() / client.is_fast_blocks() |
last_drand_round() | sub.timelock.current_round() |
get_proxies_for_real_account(ss58) | client.balances.proxies(coldkey_ss58=...) |
get_coldkey_swap_announcement(ss58) | client.balances.coldkey_swap_announcement(coldkey_ss58=...) |
get_crowdloans() / get_crowdloan_by_id / get_crowdloan_contributions | client.leasing.crowdloans() / crowdloan(...) / crowdloan_contributors(...) |
get_mev_shield_next_key() | client.chain.mev_shield_next_key() |
get_root_claim_type(ss58) | client.staking.root_claim_type(coldkey_ss58=...) |
other get_root_claim* reads | no named read — generic storage query |
get_liquidity_list(...) | removed (feature retired on chain) |
The metagraph
subtensor.metagraph(netuid) returned an object of parallel NumPy arrays
(S, W, B, I, uids, hotkeys, axons, ...) with sync(),
save(), load(). The v11 metagraph is a plain typed object with per-neuron
records and no matrices:
mg = await client.subnets.metagraph(netuid=1)
for n in mg: # MetagraphNeuron, ordered by uid
n.uid, n.hotkey, n.coldkey
n.incentive, n.dividends, n.rank, n.trust, n.consensus # 0..1 floats
n.emission, n.alpha_stake, n.tao_stake, n.total_stake # Balance
n.axon # "ip:port" or None
mg.hotkeys, mg.coldkeys, mg.validators
mg.neuron(5), mg.by_hotkey("5F...")| Old metagraph usage | v11 |
|---|---|
mg.S (stake array) | [n.total_stake for n in mg] |
mg.I, mg.D, mg.C, ... arrays | per-neuron fields (n.incentive, n.dividends, ...) |
mg.W (weight matrix) | await client.weights.weights(netuid=...) |
mg.B (bond matrix) | await client.weights.bonds(netuid=...) |
mg.axons (AxonInfo list) | n.axon ("ip:port" string or None) |
mg.sync(block=...) | refetch: client.subnets.metagraph(netuid, block=...) |
mg.save() / mg.load() | gone — persist mg.raw yourself if needed |
bt.Metagraph(netuid, network=...) | always fetched through a client |
Transactions
Every old transaction method maps to an intent class executed with
client.execute(intent, wallet). Trailing kwargs move as follows:
wait_for_inclusion / wait_for_finalization / period are kwargs of
execute; raise_error=True becomes result.raise_for_failure();
mev_protection becomes mev_shield (on by default for stake-trading
intents). The safe_staking / rate_tolerance / allow_partial_stake
flags become the explicit *Limit intent variants with a limit_price_rao
(see slippage for converting a tolerance to
a limit price).
| v10 method | v11 intent |
|---|---|
transfer(wallet, destination_ss58, amount) | sub.Transfer(dest_ss58=..., amount_tao=...) |
transfer(..., transfer_all=True) | sub.TransferAll(dest_ss58=...) |
add_stake(wallet, netuid, hotkey_ss58, amount) | sub.AddStake(hotkey_ss58=..., netuid=..., amount_tao=...) |
add_stake(..., safe_staking=True, rate_tolerance=...) | sub.AddStakeLimit(..., limit_price_rao=..., allow_partial=...) |
unstake(wallet, netuid, hotkey_ss58, amount) | sub.RemoveStake(hotkey_ss58=..., netuid=..., amount_alpha=...) (accepts "all") |
unstake(..., safe_unstaking=True) | sub.RemoveStakeLimit(...) |
unstake_all(wallet, netuid, hotkey_ss58) | sub.UnstakeAll(hotkey_ss58=...) / sub.UnstakeAllAlpha(...) |
add_stake_multiple / unstake_multiple | sub.Batch(intents=[...]) |
move_stake(...) | sub.MoveStake(origin_hotkey_ss58, origin_netuid, dest_hotkey_ss58, dest_netuid, amount_alpha) |
swap_stake(...) | sub.SwapStake(hotkey_ss58, origin_netuid, dest_netuid, amount_alpha) |
transfer_stake(...) | sub.TransferStake(dest_coldkey_ss58, hotkey_ss58, origin_netuid, dest_netuid, amount_alpha) |
set_auto_stake(...) | sub.SetAutoStake(netuid=..., hotkey_ss58=...) |
lock_stake / move_lock / set_perpetual_lock | sub.LockStake / sub.MoveLock / sub.SetPerpetualLock |
burned_register(wallet, netuid) / register(...) | sub.BurnedRegister(netuid=..., hotkey_ss58=None) |
register_subnet(wallet) | sub.RegisterSubnet() |
root_register(wallet) | sub.RootRegister() |
start_call(wallet, netuid) | sub.StartCall(netuid=...) |
serve_axon(netuid, axon: Axon) | sub.ServeAxon(netuid=..., ip=..., port=...) — no Axon object; TLS via sub.ServeAxonTls |
set_weights(wallet, netuid, uids, weights, mechid) | sub.SetWeights(netuid=..., weights={uid: w} or uids=[...], weights=[...], mechid=0) |
commit_weights / reveal_weights | sub.CommitWeights / sub.RevealWeights (reveal only for legacy salt commits) |
set_delegate_take(wallet, hotkey_ss58, take) | sub.SetTake(take=...) — unit change, see below |
set_children(wallet, netuid, hotkey_ss58, children) | sub.SetChildren(netuid=..., children=[...]) — unit change, see below |
set_subnet_identity(...) | sub.SetSubnetIdentity(netuid=..., subnet_name=..., ...) (flattened fields) |
| identity via extrinsics | sub.SetIdentity(name=..., url=..., ...) |
claim_root(wallet, netuids) | sub.ClaimRoot(subnets=[...]) |
set_root_claim_type(wallet, type) | sub.SetRootClaimType(claim_type="Swap"/"Keep"/"KeepSubnets") |
add_proxy / remove_proxy / remove_proxies / create_pure_proxy / kill_pure_proxy | same-name intents (sub.AddProxy(delegate_ss58=..., proxy_type=..., delay=...), ...) |
proxy(wallet, real_ss58, type, call) | client.execute(intent, wallet, proxy_for=real_ss58) |
announce_coldkey_swap / swap_coldkey_announced / dispute_coldkey_swap / clear_coldkey_swap_announcement | same-name intents |
| crowdloan methods | same-name intents (sub.CreateCrowdloan, sub.ContributeCrowdloan, ...) |
set_commitment(wallet, netuid, data) | no intent — raw call: client.submit_call(sub.calls.Commitments.set_commitment(...), wallet) |
hyperparameters (was CLI-only sudo set) | sub.SetHyperparameter(netuid=..., name=..., value=...) |
add_liquidity / modify_liquidity / remove_liquidity | removed (feature retired on chain) |
mev_submit_encrypted(wallet, call) | client.submit_shielded(intent, wallet), or the default shielding on stake intents |
compose_call(...) + sign_and_send_extrinsic(...) | client.submit_call(sub.calls.Pallet.function(...), wallet, signer="coldkey"/"hotkey") |
bittensor.core.extrinsics.* functions | gone — intents or submit_call |
Dynamic dispatch (the old pattern of calling methods by name) becomes
sub.intents.build(op, args_dict) or
client.execute_tool(op, args_dict, wallet) — see For agents.
New in v11, worth adopting during migration: client.plan(intent, wallet)
previews fee, effects, and warnings without submitting, and a
sub.Policy(max_spend_tao=..., allowed_netuids=[...]) on the client
hard-bounds what any execution may do.
Weights, children, and take
Three call families changed units, not just names:
- Weights are easier: pass floats (any scale — only proportions matter)
or a
{uid: weight}dict tosub.SetWeights; clipping, normalization, and u16 quantization happen internally (bittensor.intents.normalizeis the canonical implementation).bittensor.utils.weight_utils(process_weights_for_netuid,convert_weights_and_uids_for_emit, ...) is gone and unnecessary.set_weightsalso auto-selects plain vs timelocked commit-reveal submission; drop any manual commit/reveal scheduling unless you used salt-based commits.version_keynow defaults to 0 instead of the SDK version number — pass the subnet's required key explicitly if it enforces one. - Take still accepts the float 0–1 form (
take=0.18); a plain integer is the raw u16 proportion (take=11796), following the hyperparameter value rules (a decimal point marks the human form).set_delegate_take's auto-direction behavior is preserved bysub.SetTake;sub.IncreaseTake/sub.DecreaseTakepin the direction. - Children proportions likewise accept floats 0–1
(
children=[[0.5, "5F...child"]]); a plain integer is the raw u64 share ofu64::MAX.
Money and Balance
Balance survives but is stricter — the biggest silent-behavior change in
v11 (Money):
- Every
Balancecarries anetuid(0 = TAO). Arithmetic or comparison across units raisessub.UnitMismatchError(wasBalanceUnitMismatchError); comparing with a barefloatraisesTypeError. .taoon an alpha balance raises instead of returning a number. Use.amountfor the value in its own currency, or thestake-value-for-coldkeyread to value alpha in TAO.Balance.from_tao(amount)no longer takes a netuid;Balance.from_alpha(amount, netuid=...)is the alpha constructor. Shorthands:sub.tao(1.5),sub.alpha(2.5, 42),sub.rao(1_500_000_000).- Intent amount fields are unit-named (
amount_tao,amount_alpha,limit_price_rao) and acceptint | float | str | Decimal | Balance— the strict "must be a Balance object" rule from v10 is relaxed because the field name now carries the unit. Fields that support it accept"all".
Wallets
bittensor_wallet is replaced by bittensor.wallet with the same on-disk
format — same paths (~/.bittensor/wallets/<name>/), same keyfile
encryption (NaCl / legacy), same SS58 format. Existing wallets just work.
from bittensor.wallet import Wallet
wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") # same constructor
wallet.coldkey, wallet.hotkey, wallet.coldkeypub # same properties
wallet.create_new_coldkey(...), wallet.regenerate_coldkey(...)Differences: bt.Wallet(config=...) and Wallet.add_args are gone (pass
name/hotkey/path directly); bt.Keypair / bt.Keyfile are no longer
top-level exports (bittensor.keyfiles.Keyfile exists; keypairs come from
the wallet). Convenience helpers live in sub.wallets
(create, regen_coldkey, list_wallets, sign_message,
verify_message, ...). Non-interactive unlock: BT_WALLET_PASSWORD or
BT_WALLET_PASSWORD_FILE (wallets).
Axon, Dendrite, and Synapse are gone
v11 contains no miner/validator networking stack: bt.Axon,
bt.Dendrite, bt.Synapse, bt.StreamingSynapse, bt.SubnetsAPI, and
bt.Tensor do not exist. What the old stack actually provided splits into
three layers, and each has a v11 answer:
| Old stack | v11 |
|---|---|
hotkey signing + verification (dendrite.preprocess_synapse_for_request, axon.default_verify) | sub.http_auth.sign / sub.http_auth.verify — Signed requests |
Axon (FastAPI server, lifecycle, middleware) | your own server; FastAPI recipe in the guide |
Dendrite (aiohttp client, fan-out) | your own client; httpx recipe in the guide |
Synapse subclasses (shared request schema) | your own request/response models — signing covers raw bytes, so any schema works |
blacklist_fn | metagraph caller policy (registered / validator permit / min stake) — snippet in the guide |
priority_fn, threadpool | your framework's rate limiting, keyed by the verified caller hotkey |
StreamingSynapse | SSE / websockets in your framework |
Tensor | serialize with your ML library's own tools |
The chain-side operations remain — publish your endpoint with sub.ServeAxon
(see Mining), set weights with sub.SetWeights (see
Validating). The identity layer —
hotkey-signed HTTP requests, replacing the old bt_header_* scheme with a
cleaner cross-language wire format (btauth/1) — ships in the SDK as pure
sign/verify functions with no server or client attached. A miner that
must keep accepting old-SDK validators during a transition can also verify
the legacy v10 header format (recipe in the guide).
Results and errors
| v10 | v11 |
|---|---|
ExtrinsicResponse.success / .message | ExtrinsicResult.success / .message |
.extrinsic_fee | .fee |
.extrinsic_receipt | .block_hash, .extrinsic_id, .events, .explorer_url |
.data (uid, reveal_round, ...) | .data (same idea) |
.error (a Python exception) | .error — ChainError with .name, .code (sub.ErrorCode), .remediation |
raise_error=True kwarg | result.raise_for_failure() |
typed exceptions (StakeError, NotRegisteredError, TxRateLimitExceeded, ...) | branch on result.error.code: ErrorCode.INSUFFICIENT_BALANCE, RATE_LIMITED, NOT_REGISTERED, SUBNET_NOT_EXISTS, ... (Errors) |
BalanceTypeError / BalanceUnitMismatchError | TypeError / sub.UnitMismatchError |
result = await client.execute(intent, wallet)
if not result.success:
match result.error.code:
case sub.ErrorCode.RATE_LIMITED: ... # wait and retry
case sub.ErrorCode.INSUFFICIENT_BALANCE: ...
case _: log(result.error.remediation)CLI migration
One binary, still called btcli, now installed with bittensor. The
structure: every read is btcli query <name>, every transaction is
btcli tx <name> (intent op names with dashes), and familiar groups
(wallet, stake, subnets, sudo, weights, axon, proxy, ...) wrap
the same machinery. The v9 group shorthands (w, st, s, su, c, wt,
cr, d) and snake_case command spellings (wallet new_hotkey) still work
as hidden aliases. Full details: The CLI.
Global flags
| v9 | v11 |
|---|---|
--wallet.name / --wallet-name / --name | --wallet / -w |
--wallet.hotkey / --hotkey / -H | --wallet-hotkey / -H |
--wallet.path / -p | --wallet-path |
--network / --subtensor.network / --chain / --subtensor.chain_endpoint | --network / -n (name or ws:// URL) |
--no-prompt / -y / --yes | --yes / -y |
--json-output / --json-out | --json |
--quiet | --quiet / -q |
--verbose | -v / -vv / -vvv |
--mev-protection / --no-mev-protection | --mev-shield / --no-mev-shield |
--proxy NAME | --proxy-for NAME |
| — | --dry-run (preview any tx command) |
Config file moves from ~/.bittensor/config.yml to ~/.bittensor/btcli.json
and keys are renamed: btcli config set network finney,
btcli config set wallet my_coldkey (was wallet_name). The old
use_cache, rate_tolerance, safe_staking, allow_partial_stake, and
dashboard_path keys have no equivalent. Non-interactive behavior changed
for the better: without a TTY and without --yes, a mutation is declined
with exit code 1 instead of hanging; missing required options exit 2.
Command mapping
| v9 command | v11 command |
|---|---|
wallet list / create / new-coldkey / new-hotkey / regen-* | same names under wallet |
wallet balance [--all] | wallet balance [--all] or btcli query balance --coldkey NAME --json |
wallet transfer --dest --amount | wallet transfer --dest --amount or btcli tx transfer --dest --amount-tao |
wallet overview / inspect / sign / verify / encrypt / decrypt | same names |
wallet swap-hotkey DEST | wallet swap-hotkey --new-hotkey ... / tx swap-hotkey |
wallet swap-coldkey announce/execute/... | wallet announce-coldkey-swap, wallet swap-coldkey, wallet swap-check, tx dispute-coldkey-swap, tx clear-coldkey-swap-announcement |
wallet set-identity / get-identity / associate-hotkey | same names |
wallet history | wallet history (works again, mainnet indexer) |
stake add --amount | stake add / tx add-stake --amount-tao |
stake remove --amount | stake remove / tx remove-stake --amount-alpha (accepts all) |
stake remove --all | tx unstake-all / tx unstake-all-alpha |
stake list / move / transfer / swap | same names (single hotkey per call; --include-hotkeys/--exclude-hotkeys/--all-hotkeys batching is gone — loop in your script) |
stake auto / set-auto / set-claim / process-claim | same names |
stake child get/set/revoke/take | same names (child set takes --children as JSON pairs) |
sudo set --param NAME --value V | sudo set --name NAME --value V / tx set-hyperparameter |
sudo get / subnets hyperparameters | sudo get / subnets hyperparameters NETUID / query subnet-hyperparameters |
sudo set-take / get-take | same names (--take still a float on the CLI) |
sudo trim / stake-burn | same names |
sudo senate / proposals / senate-vote | removed — the senate is gone from the runtime (Governance) |
subnets list / show / create / register / burn-cost / price | same names (show/burn-cost take netuid positionally) |
subnets metagraph | subnets metagraph NETUID / query metagraph --netuid N --json |
subnets register --netuid 0 | tx root-register |
subnets start / check-start | sudo start / sudo check-start |
subnets set-identity / get-identity / set-symbol | sudo set-identity / sudo get-identity / sudo set-symbol |
subnets mechanisms ... | sudo mechanisms ... |
weights commit / reveal | weights commit / reveal — and weights set now exists (auto commit-reveal); prefer it |
axon set / reset | same names / tx serve-axon, tx reset-axon |
proxy create/add/remove/kill/execute | same names |
config add-proxy / proxies / remove-proxy | proxy book add / list / remove |
crowd create/contribute/withdraw/finalize/update/refund/dissolve | tx create-crowdloan, tx contribute-crowdloan, ... (crowd keeps the reads: list, info, contributors) |
liquidity add/list/modify/remove | removed (chain feature retired) |
view dashboard | removed |
utils convert / latency | same names |
deriv quote/positions/market/open/topup/close | same names |
| any other chain read | btcli query <name> — btcli query --help lists all of them |
| any other mutation | btcli tx <name> — btcli tx --help, or btcli call Pallet.function --args '{...}' for raw extrinsics |
Two new commands help automated migration directly: btcli tools dumps the
full transaction catalog with JSON schemas, and btcli explain <code>
explains any error code.
Migration checklist
- Replace
bittensor+bittensor-cli+bittensor_walletwith a single pinnedbittensor>=11(the CLI is included). In live environments, uninstall the old packages before upgrading (why). - Swap
bt.Subtensor/bt.AsyncSubtensorforsub.SyncClient/sub.Client; moveblock=-pinned reads ontoclient.at(block). - Rewrite reads using the tables above; anything unmapped goes through
client.query(sub.storage...). - Rewrite each transaction as an intent +
client.execute; take values and child proportions keep working as 0–1 floats (plain integers mean the raw u16/u64 wire values); replace safe-staking flags with*Limitintents. - Update result handling:
.successstill works, but branch onresult.error.codeinstead of exception types or message strings. - Fix Balance usage: no
.taoon alpha, no cross-unit arithmetic,from_alphafor subnet currency. - Delete
bt.config/argparse plumbing,bt.loggingcalls (use standardlogging), andweight_utilspreprocessing. - Update env vars (
BT_SUBTENSOR_NETWORK→BT_NETWORK,BT_WALLET_NAME→BT_WALLET) and shell scripts per the CLI tables (--wallet.name→-w,--json-output→--json,--no-prompt→--yes). - If the script served an axon or queried miners with a dendrite, rebuild
that layer on your own HTTP stack with
sub.http_authfor the hotkey signing (Signed requests); only its chain calls move to this SDK's client. - Dry-run everything:
client.plan(...)in Python,--dry-runon the CLI, ideally againsttestor a local node first.