Transactions

batch

Execute several intents atomically in one extrinsic (all-or-nothing).

View as Markdown

Wraps the child calls in Utility.batch_all: they run in order and if any one fails the whole extrinsic reverts, so there is never a partially-applied result. Use it for multi-step operations that must land together (e.g. move funds then act on them) instead of submitting the steps separately and risking a half-done state. All children must share one signer — an extrinsic has a single signature — and batches cannot contain other batches. Spend limits and policy checks aggregate across every child, and the transaction plan lists each child's effects.

SignerOriginPalletWraps
coldkeysigned account (pallet role may apply)UtilityUtility.batch_all

Parameters

ParameterTypeRequiredDescription
intentsarrayyesThe calls to execute, in order, as a JSON list of objects {"op": <intent name>, ...args}. At least one; all must share a signer; batches cannot nest.

Address parameters (--hotkey, --coldkey, --dest, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name.

CLI

Preview with --dry-run (shows fee, effects, and policy result without submitting), then submit:

btcli tx batch \
  --intents <a,b,c> --dry-run
btcli tx batch \
  --intents <a,b,c> -w my_coldkey

Python

import bittensor as bt
from bittensor.wallet import Wallet

wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
intent = bt.Batch(intents=[...])

sub = bt.Subtensor()
plan = sub.plan(intent, wallet)   # fee, effects, policy — no submission
result = sub.execute(intent, wallet)
if not result.success:
    print(result.error.code, result.error.remediation)

(bt.Subtensor is also the async client — async with bt.Subtensor() as client: — see The client.) Or build the intent by op name, as an agent would:

result = sub.execute_tool("batch", {...}, wallet)

On-chain implementation

Utility.batch_allpallets/utility/src/lib.rs#L314:

        #[pallet::call_index(2)]
        #[pallet::weight({
			let (dispatch_weight, pays) = Pallet::<T>::weight_and_dispatch_class(calls);
			let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32));
			(dispatch_weight, DispatchClass::Normal, pays)
		})]
        pub fn batch_all(
            origin: OriginFor<T>,
            calls: Vec<<T as Config>::RuntimeCall>,
        ) -> DispatchResultWithPostInfo {
            // Do not allow the `None` origin.
            if ensure_none(origin.clone()).is_ok() {
                return Err(BadOrigin.into());
            }

            let is_root = ensure_root(origin.clone()).is_ok();
            let calls_len = calls.len();
            ensure!(
                calls_len <= Self::batched_calls_limit() as usize,
                Error::<T>::TooManyCalls
            );

            // Track the actual weight of each of the batch calls.
            let mut weight = Weight::zero();
            for (index, call) in calls.into_iter().enumerate() {
                let info = call.get_dispatch_info();
                // If origin is root, bypass any dispatch filter; root can call anything.
                let result = if is_root {
                    call.dispatch_bypass_filter(origin.clone())
                } else {
                    let mut filtered_origin = origin.clone();
                    // Don't allow users to nest `batch_all` calls.
                    filtered_origin.add_filter(
                        move |c: &<T as frame_system::Config>::RuntimeCall| {
                            let c = <T as Config>::RuntimeCall::from_ref(c);
                            !matches!(c.is_sub_type(), Some(Call::batch_all { .. }))
                        },
                    );
                    call.dispatch(filtered_origin)
                };
                // Add the weight of this call.
                weight = weight.saturating_add(extract_actual_weight(&result, &info));
                result.map_err(|mut err| {
                    // Take the weight of this function itself into account.
                    let base_weight = T::WeightInfo::batch_all(index.saturating_add(1) as u32);
                    // Return the actual used weight + base_weight of this call.
                    err.post_info = Some(base_weight.saturating_add(weight)).into();
                    err
                })?;
                Self::deposit_event(Event::ItemCompleted);
            }
            Self::deposit_event(Event::BatchCompleted);
            let base_weight = T::WeightInfo::batch_all(calls_len as u32);
            Ok(Some(base_weight.saturating_add(weight)).into())
        }

Every file is browsable under /code exactly as built into the runtime, or as plain text under /code/raw/<path> (index: /code/index.json).