Batch Transactions
The Bittensor runtime's utility pallet exposes three extrinsics — batch, batch_all, and force_batch — that let you submit multiple calls as a single on-chain transaction. This is useful when you want to stake to multiple hotkeys, perform multiple operations atomically, or reduce the number of round-trips to the chain.
For how fees are calculated across a batch. See Batch Transaction Fees.
batch vs batch_all vs force_batch
The three variants differ only in how they handle errors. Choose based on whether partial success is acceptable:
| Extrinsic | On error |
|---|---|
batch | Stops at first failure; prior calls succeed. Emits BatchInterrupted. |
batch_all | Reverts all calls atomically on any failure. |
force_batch | Continues past failures; failed calls are skipped. |
Use batch_all when all inner calls must succeed or none should. Use batch if partial success is acceptable, or force_batch to continue past failures.
Source code: batch pallets/utility/src/lib.rs:197–201, batch_all pallets/utility/src/lib.rs:309–313, force_batch pallets/utility/src/lib.rs:408–412.
Using batch calls with the SDK
The operations on this page require a coldkey. Your primary coldkey should remain in cold storage (hardware wallet) and never be loaded onto a machine running btcli or the Bittensor SDK. Use a scoped, delayed proxy coldkey to perform these operations via btcli or the SDK. See Coldkey and Hotkey Workstation Security and Proxies.
The SDK's add_stake_multiple and unstake_multiple send individual extrinsics sequentially, not a single batch extrinsic.
To submit multiple stake actions as an atomic batch (one extrinsic on-chain), use the low-level pallet builder + proxy path. The batch call is wrapped in a proxy extrinsic signed by your proxy wallet.
The Staking proxy type allows specific staking extrinsics (add_stake, remove_stake, etc.) but does not allow Utility::batch_all as the outer call. For batch staking operations, use a NonCritical proxy, which prohibits only destructive operations (dissolve_network, root_register, burned_register, Sudo) and allows everything else, including batch wrappers.
import os
import bittensor as bt
from bittensor.core.chain_data.proxy import ProxyType
from bittensor.core.extrinsics.pallets import SubtensorModule
sub = bt.Subtensor(network="finney")
proxy_wallet = bt.Wallet(name=os.environ['BT_PROXY_WALLET_NAME'])
real_account_ss58 = os.environ['BT_REAL_ACCOUNT_SS58']
hotkey_1 = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
hotkey_2 = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
netuid = 1
amount = bt.Balance.from_tao(10)
pallet = SubtensorModule(sub)
call_1 = pallet.add_stake(netuid=netuid, hotkey=hotkey_1, amount_staked=amount.rao)
call_2 = pallet.add_stake(netuid=netuid, hotkey=hotkey_2, amount_staked=amount.rao)
# Wrap in Utility.batch_all — reverts all calls atomically on any failure
batch_call = sub.compose_call(
call_module="Utility",
call_function="batch_all",
call_params={"calls": [call_1, call_2]},
)
# Submit via proxy — NonCritical proxy type is required for batch wrappers
response = sub.proxy(
wallet=proxy_wallet,
real_account_ss58=real_account_ss58,
force_proxy_type=ProxyType.NonCritical,
call=batch_call,
)
print(response)
add_stake_multiple is not a batch extrinsicsubtensor.add_stake_multiple() and subtensor.unstake_multiple() loop over their inputs and submit one extrinsic per hotkey. Each transaction is settled independently — they are not atomic. Use the Utility.batch_all pattern above when you need all-or-nothing semantics or want to pay a single transaction fee for the group.