Transactions

unstake-all

Unstake everything from a hotkey across all subnets.

View as Markdown

Sweeps the signing coldkey's entire stake held on this hotkey across every subnet (root included) back to TAO in the coldkey's free balance. Subnets where subtoken trading is disabled or where the position fails validation are silently skipped, so the call can succeed while leaving some positions untouched. Alpha positions are sold at each pool's current price with no limit protection, so large positions can incur significant slippage. Use remove_stake to exit a single subnet, or unstake_all_alpha to consolidate onto root while staying staked.

Pass claim=True to redeem this validator's whole root basket entitlement first, then unstake everything. That is not a proportional payout: the claim pays 100% of the basket.

SignerOriginPalletWraps
coldkeysigned account (pallet role may apply)SubtensorModuleSubtensorModule.unstake_all, SubtensorModule.claim_root_with_hotkey, Utility.batch_all

Parameters

ParameterTypeRequiredDescription
hotkey_ss58stringyesHotkey whose entire stake is removed.
claimbooleannoAlso redeem this validator's whole root basket entitlement before unstaking, in one atomic batch. This is not a proportional payout: the claim pays 100% of the basket. Claimed yield is restaked on root, then unstake-all takes principal and that yield out together. Unavailable while RootStakeUnlockInterval is nonzero; claim first, wait for the hold, then unstake in that mode.

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 unstake-all \
  --hotkey <ss58|name> --dry-run
btcli tx unstake-all \
  --hotkey <ss58|name> -w my_coldkey

Python

import bittensor as bt
from bittensor.wallet import Wallet

wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
intent = bt.UnstakeAll(hotkey_ss58="5F...")

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("unstake_all", {...}, wallet)

On-chain implementation

SubtensorModule.unstake_allpallets/subtensor/src/macros/dispatches.rs#L1236:

#[pallet::call_index(83)]
#[pallet::weight(<T as crate::pallet::Config>::WeightInfo::unstake_all())]
pub fn unstake_all(origin: OriginFor<T>, hotkey: T::AccountId) -> DispatchResult {
    Self::do_unstake_all(origin, hotkey)
}

Delegates to do_unstake_all.

SubtensorModule.claim_root_with_hotkeypallets/subtensor/src/macros/dispatches.rs#L1993:

#[pallet::call_index(148)]
#[pallet::weight(
    <T as crate::pallet::Config>::WeightInfo::claim_root(crate::MAX_ROOT_CLAIM_WORK)
)]
pub fn claim_root_with_hotkey(
    origin: OriginFor<T>,
    hotkey: T::AccountId,
) -> DispatchResultWithPostInfo {
    let coldkey: T::AccountId = ensure_signed(origin)?;
    ensure!(
        Self::root_claim_fits_declared_budget(core::slice::from_ref(&hotkey)),
        Error::<T>::RootClaimTooHeavy
    );

    let outcome = Self::do_root_claim(coldkey.clone(), vec![hotkey])?;
    Self::maybe_add_coldkey_index(&coldkey);

    let weight = Self::root_claim_actual_weight(1, &outcome);
    Ok((Some(weight), Pays::Yes).into())
}

Delegates to root_claim_fits_declared_budget, do_root_claim, maybe_add_coldkey_index.

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).