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, Results and errors, and Chain and runtime changes — these change behavior, not just names.
- Verify against the live catalogs rather than guessing:
bt.intents.list_tools()andbt.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 bittensor as bt
from bittensor.wallet import Wallet
wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
sub = bt.Subtensor() # defaults to finney; no close() needed
balance = sub.balances.get("5F...")
result = sub.execute(
bt.AddStake(hotkey_ss58="5F...validator", netuid=1, amount_tao=10),
wallet,
)
if not result.success:
print(result.error.code, result.error.remediation)The three structural changes:
- One client, no method zoo.
bt.Subtensorkeeps its name but is a new class: one entry point that is blocking when used directly and async when awaited (async with bt.Subtensor() as client:) —AsyncSubtensoris gone. The ~165 v10 methods are replaced by typed reads on category namespaces (sub.subnets,sub.staking,sub.delegation,sub.prices, ... — with autocomplete and signature help), dispatchable by name too (sub.read("uid", hotkey_ss58=..., netuid=...)), and anything else is reachable through generic accessors (sub.query(bt.storage...)). See The client. - Transactions are intents. Every mutation is a small dataclass
(
bt.Transfer,bt.AddStake,bt.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") | bt.Subtensor() (blocking, finney is the default) |
bt.AsyncSubtensor(...) + await bt.initialize() | async with bt.Subtensor() as client: |
get_async_subtensor(network=...) | await bt.Subtensor(...) |
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 Subtensor |
subtensor.close() | optional — automatic on garbage collection / process exit |
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(bt.storage.Pallet.Item, [params]),
client.query_map(...), client.constant(bt.constants...),
client.runtime(bt.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() | sub.block (blocking) / 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() | bt.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 |
The metagraph record returned by the metagraph read (client.read("metagraph", netuid=...),
btcli query metagraph --netuid N --json) carries name and symbol decoded to text; on
the wire they are compact-u16 vectors of utf-8 bytes, unlike every other text
field on the record. If you need the undecoded wire record, call the runtime
API directly:
await client.runtime(bt.runtime_api.SubnetInfoRuntimeApi.get_metagraph, [netuid])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) | bt.Transfer(dest_ss58=..., amount_tao=...) |
transfer(..., transfer_all=True) | bt.TransferAll(dest_ss58=...) |
add_stake(wallet, netuid, hotkey_ss58, amount) | bt.AddStake(hotkey_ss58=..., netuid=..., amount_tao=...) |
add_stake(..., safe_staking=True, rate_tolerance=...) | bt.AddStakeLimit(..., limit_price_rao=..., allow_partial=...) |
unstake(wallet, netuid, hotkey_ss58, amount) | bt.RemoveStake(hotkey_ss58=..., netuid=..., amount_alpha=...) (accepts "all") |
unstake(..., safe_unstaking=True) | bt.RemoveStakeLimit(...) |
unstake_all(wallet, netuid, hotkey_ss58) | bt.UnstakeAll(hotkey_ss58=...) / bt.UnstakeAllAlpha(...) |
add_stake_multiple / unstake_multiple | bt.Batch(intents=[...]) |
move_stake(...) | bt.MoveStake(origin_hotkey_ss58, origin_netuid, dest_hotkey_ss58, dest_netuid, amount_alpha) |
swap_stake(...) | bt.SwapStake(hotkey_ss58, origin_netuid, dest_netuid, amount_alpha) |
transfer_stake(...) | bt.TransferStake(dest_coldkey_ss58, hotkey_ss58, origin_netuid, dest_netuid, amount_alpha) |
set_auto_stake(...) | bt.SetAutoStake(netuid=..., hotkey_ss58=...) |
lock_stake / move_lock / set_perpetual_lock | bt.LockStake / bt.MoveLock / bt.SetPerpetualLock |
burned_register(wallet, netuid) / register(...) | bt.BurnedRegister(netuid=..., hotkey_ss58=None) |
register_subnet(wallet) | bt.RegisterSubnet() |
root_register(wallet) | bt.RootRegister() |
start_call(wallet, netuid) | bt.StartCall(netuid=...) |
serve_axon(netuid, axon: Axon) | bt.ServeAxon(netuid=..., ip=..., port=...) — no Axon object; TLS via bt.ServeAxonTls |
set_weights(wallet, netuid, uids, weights, mechid) | bt.SetWeights(netuid=..., weights={uid: w} or uids=[...], weights=[...], mechid=0) |
commit_weights / reveal_weights | bt.CommitWeights / bt.RevealWeights (reveal only for legacy salt commits) |
set_delegate_take(wallet, hotkey_ss58, take) | bt.SetTake(take=...) — unit change, see below |
set_children(wallet, netuid, hotkey_ss58, children) | bt.SetChildren(netuid=..., children=[...]) — unit change, see below |
set_subnet_identity(...) | bt.SetSubnetIdentity(netuid=..., subnet_name=..., ...) (flattened fields) |
| identity via extrinsics | bt.SetIdentity(name=..., url=..., ...) |
claim_root(wallet, netuids) | bt.ClaimRoot(subnets=[...]) |
set_root_claim_type(wallet, type) | bt.SetRootClaimType(claim_type="Swap"/"Keep"/"KeepSubnets") |
add_proxy / remove_proxy / remove_proxies / create_pure_proxy / kill_pure_proxy | same-name intents (bt.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 (bt.CreateCrowdloan, bt.ContributeCrowdloan, ...) |
set_commitment(wallet, netuid, data) | no intent — raw call: client.submit_call(bt.calls.Commitments.set_commitment(...), wallet) |
hyperparameters (was CLI-only sudo set) | bt.SetHyperparameter(netuid=..., name=..., value=...) (includes collateral_lock_share / collateral_drain_ratio) |
| (new) miner collateral top-up / floor | bt.AddCollateral(netuid=..., amount_alpha=...) / bt.SetMinCollateral(netuid=..., min_alpha=...); reads via client.collateral.* |
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(bt.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
bt.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
bt.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 tobt.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 bybt.SetTake;bt.IncreaseTake/bt.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 raisesbt.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:bt.tao(1.5),bt.alpha(2.5, 42),bt.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 bt.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) | bt.http_auth.sign / bt.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 bt.ServeAxon
(see Mining), set weights with bt.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 (bt.ErrorCode), .remediation, plus .description / .docs_url when the name is classified |
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 / bt.UnitMismatchError |
Prefer .code over matching .name or message strings. SubtensorModule renamed
InsufficientBalance → InsufficientTaoBalance (same error index) and added
InsufficientAlphaBalance; both still classify as
ErrorCode.INSUFFICIENT_BALANCE. Other pallets (Balances, Crowdloan, Swap)
may still emit InsufficientBalance. btcli explain <Name> and
result.error.description give the per-name trigger text.
result = await client.execute(intent, wallet)
if not result.success:
match result.error.code:
case bt.ErrorCode.RATE_LIMITED: ... # wait and retry
case bt.ErrorCode.INSUFFICIENT_BALANCE: ...
case _: log(result.error.remediation)Chain and runtime changes
The v11 package ships against a runtime that also changed behavior. These are not rename mappings — scripts that still "work" can fail or mis-account after the upgrade. Full narrative: The V431 Upgrade.
Ownership and emissions
- Subnet ownership. For subnets at least one year old, ownership transfers
to the highest-conviction hotkey when total conviction reaches 10% of
SubnetAlphaOut. Existing conviction counts toward this threshold. See Conviction. - Cross-subnet emissions. Allocation uses EMA price adjusted by miner burn;
root_proportionis no longer included in the inter-subnet split (it still applies inside each subnet for injection caps and root dividends). Update emission calculations and forecasts. See Emissions.
Proxies and coldkeys
- Deny-by-default allowlists. Proxy types only permit listed call groups.
Audit every existing grant. Triumvirate / Senate / Governance / RootWeights
proxies deny all calls. A Staking proxy cannot wrap
Utility.batch_all— batched staking needs NonTransfer. See Proxies. - Coldkey-swap and dissolve scope. NonTransfer, NonFungible, and
NonCritical proxies can no longer call
reset_coldkey_swap,swap_coldkey, orschedule_swap_coldkey. NonCritical can no longer callroot_dissolve_network. Owner proxies are limited to owner-settable subnet configuration and identity calls (not owner-key rotation). - Reject locked alpha. Coldkeys that should accept locked-alpha transfers
must opt in with
set_reject_locked_alpha(raw call /submit_call— no first-class intent yet). Default is to reject.
Staking and fees
- Default slippage protection.
AddStake,RemoveStake, andSwapStake(and the matching CLI commands) apply a 5% price bound by default. Old any-price scripts fail withSlippageTooHighunless you passslippage_protection=False/--no-slippage-protection, or set an explicitrate_tolerance/ limit price. - Strict stake moves.
move_stake/transfer_stake/swap_stakeno longer silently cap the alpha amount to what remains after an alpha-paid fee. Submitting the full stake balance can fail withNotEnoughStakeToWithdraw— leave dust or query the post-fee balance first. - Limit-stake TAO refund. When a price limit stops a stake-in swap before
the full TAO is consumed, the unswapped remainder is refunded to the coldkey
(it used to sit on the subnet pallet account). Prefer post-state balances
over summing
StakeAddedamounts for partial fills — the event can still report the pre-refund TAO, andSubnetVolumemay overstate the same way. - Take changes pay fees.
increase_takeanddecrease_takearePays::Yes. Preview withclient.plan(...)/--dry-run. - Hotkey-swap alpha fees.
swap_hotkey/swap_hotkey_v2can take the transaction fee in alpha across the hotkey's stake subnets — same "don't submit the entire balance" footgun as stake moves when paying in alpha. - Stake move/transfer/swap extrinsics also pay the ordinary transaction
fee (
Pays::Yes). Same-subnetmove_stakeis not a pool swap, but it is not fee-free. - Miner collateral.
add_collateraltakes alpha (amount_alpha/AlphaBalance), not TAO — free stake covers the lock first; only a shortfall is bought with TAO underlimit_price. Bondedswap_hotkey/swap_hotkey_v2withkeep_stake=truefails withKeepStakeBlockedByCollateral; usekeep_stake=falseso the bond migrates (lineage maps track the rename). Coldkey swap migratesMinerCollateralwith the stake. - Key lineage (runtime 437). Hotkey swaps write per-subnet
HotkeySuccessor/HotkeyRoot; coldkey swaps write globalColdkeySuccessor/ColdkeyRoot. Preferhotkey_root/coldkey_root(andsame_*_lineage) for bans and owner attribution;*_lineage_tipis best-effort. See The V437 Upgrade.
Registration, hyperparameters, and randomness
- Queued subnet registration. When capacity is full or cleanup is pending,
register_networkmay emitNetworkRegistrationQueuedwithout creating the subnet.client.execute(RegisterSubnet(...))andbtcli subnets createwait for the matchingNetworkAddedby default and return the assignednetuid; raw-call clients must do the same before treating the registration as live. - Tempo and activity cutoff. Owner (and root) set these through
AdminUtils::sudo_set_tempoandAdminUtils::sudo_set_activity_cutoff_factor.SubtensorModulecall indices 139 (set_tempo) and 140 (set_activity_cutoff_factor) remain as deprecated no-op compatibility entry points: they charge a fee and return success without changing state. Use the AdminUtils calls to update these values. The live inactivity window isactivity_cutoff_factor(per-mille of tempo); absoluteactivity_cutoffis derived / legacy. Set viabt.SetHyperparameter/btcli sudo set --name activity_cutoff_factor|tempo. - Hyperparams read API. Prefer
get_subnet_hyperparams_v3(name→value); v1/v2 are deprecated. The SDK read already uses v3. - Drand submissions. After the initial pulse, only
last_stored_round + 1is accepted. Missing rounds must be backfilled sequentially or commit-reveal / timelock consumers stall.
Runtime API, metadata, and catalogs
get_proxy_filter→get_proxy_filters. Regenerate any client that called the singular API.- Typed metadata. TAO/Alpha balances,
NetUid, and related fields use distinct TypeInfo paths (SCALE bytes unchanged). Regenerate static bindings. - Error catalog shape.
/catalog/errors.jsonchain_errorsvalues are now{code, description, docs_url}objects, not bare code strings. Update parsers that assumed a string. - Contract / EVM ABI. Chain-extension output still collapses both
Subtensor insufficient-balance variants to the ABI
InsufficientBalancecode — contracts cannot distinguish TAO vs Alpha from that code alone.
Dissolution (operators / indexers)
- Settlement cursor. Multi-block dissolution stake settlement keeps the cursor on the last completed hotkey and retries an unpaid one on the next pass instead of skipping it. Cleanup progress that assumed "cursor always advances" needs a re-check.
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 | removed — chain feature not available on current runtime |
| 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). - Keep
bt.Subtensor(...)— the name survives, blocking as before (async when awaited;bt.AsyncSubtensoris gone) — but every method on it changed; moveblock=-pinned reads ontosub.at(block). - Rewrite reads using the tables above; anything unmapped goes through
client.query(bt.storage...). Prefer hyperparams v3 for custom RPC clients. - 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 or the default 5% slippage protection (--no-slippage-protectionto opt out). - Update result handling:
.successstill works, but branch onresult.error.codeinstead of exception types or message strings. Treat SubtensorModuleInsufficientTaoBalance/InsufficientAlphaBalanceas the oldInsufficientBalance(other pallets may still emit that name). - 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
bt.http_authfor the hotkey signing (Signed requests); only its chain calls move to this SDK's client. - Apply the chain and runtime changes: audit
proxies; retarget owner tempo /
activity_cutoff_factorcalls to AdminUtils; never submit a full alpha balance when fees are alpha-paid; let the SDK wait forNetworkAddedon queued registrations (or do so in raw-call clients); regenerate metadata bindings and anyerrors.json/get_proxy_filterconsumers. - Dry-run everything:
client.plan(...)in Python,--dry-runon the CLI, ideally againsttestor a local node first.