Transactions

swap-basket

Rebalance a root validator's basket: sell one holding to buy another.

View as Markdown

Sells amount of the fund's origin_netuid holding for TAO and buys dest_netuid with it. Either side may be netuid 0, the fund's TAO cash slot. Fund shares and staker entitlements do not change; only the fund's composition moves. Signed by the coldkey that owns hotkey_ss58 or by a BasketTrading proxy of it (the intended setup for a trader multisig).

Guardrails enforced on chain: each AMM leg must fill fully within 2% of the strictest of the subnet's slow moving price, its fast moving price (a two-hour EMA of spot written each block from the previous block's close, so a price someone just moved cannot be traded at) and its spot price (SlippageTooHigh otherwise); the TAO through the middle is taken from the fund's turnover bucket, which holds BasketDailyTurnoverCap of the fund's guarded NAV (each holding at the lower of its realizable value and its slow-moving-price value) and refills over 7200 blocks (BasketTurnoverBudgetExceeded); the destination holding may not end above the BasketLiquidityCap share of the destination pool's alpha reserve (BasketLiquidityCapExceeded) nor above the BasketConcentrationCap share of the guarded NAV (BasketConcentrationCapExceeded). Trading must be enabled network-wide and not frozen for the hotkey. Query basket_trading_status for the remaining budget and validator_basket for current holdings before trading. Pass all to sell the fund's whole origin holding (e.g. to clear a small balance).

min_amount_out is your own floor on top of the protocol band: the buy leg must credit at least this much destination alpha (TAO when the destination is netuid 0), after fees, or the whole trade rolls back with BasketMinOutNotMet. The default 0 sets no floor. btcli root swap derives it from a quote and --max-slippage.

SignerOriginPalletWraps
coldkeysigned account (pallet role may apply)SubtensorModuleSubtensorModule.swap_basket

Parameters

ParameterTypeRequiredDescription
hotkey_ss58stringyesRoot-registered validator whose basket to rebalance.
origin_netuidintegeryesSubnet to sell out of (0 = the fund's TAO cash slot).
dest_netuidintegeryesSubnet to buy into (0 = the fund's TAO cash slot).
amountnumber | "all"yesHow much of the origin holding to sell, in the origin subnet's alpha (TAO when the origin is netuid 0), or all for the whole holding.
min_amount_outnumber | "all"noLeast amount the buy leg must credit, in the destination subnet's alpha (TAO when the destination is netuid 0), after fees; the trade rolls back with BasketMinOutNotMet below it. 0 (default) sets no floor. The 2% protocol band applies regardless.

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 swap-basket \
  --hotkey <ss58|name> \
  --origin-netuid <int> \
  --dest-netuid <int> \
  --amount <amount|all> --dry-run
btcli tx swap-basket \
  --hotkey <ss58|name> \
  --origin-netuid <int> \
  --dest-netuid <int> \
  --amount <amount|all> -w my_coldkey

Python

import bittensor as bt
from bittensor.wallet import Wallet

wallet = Wallet(name="my_coldkey", hotkey="my_hotkey")
intent = bt.SwapBasket(hotkey_ss58="5F...", origin_netuid=1, dest_netuid=2, amount=1.0)

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

On-chain implementation

SubtensorModule.swap_basketpallets/subtensor/src/macros/dispatches.rs#L2105:

#[pallet::call_index(150)]
// Declared weight is a cap sized for 256 holdings (one NAV sim-swap sweep, two
// post-trade re-quotes, and two AMM legs) plus the flat pending-deposit flush
// allowance every flushing extrinsic declares; the actual weight is computed in
// `do_swap_basket` from the real holding count and flush work and refunded
// post-dispatch, mirroring `stake_into_basket` / `claim_root`.
#[pallet::weight((Pallet::<T>::swap_basket_declared_weight(), DispatchClass::Normal, Pays::Yes))]
pub fn swap_basket(
    origin: OriginFor<T>,
    hotkey: T::AccountId,
    origin_netuid: NetUid,
    destination_netuid: NetUid,
    amount: AlphaBalance,
    min_amount_out: u64,
) -> DispatchResultWithPostInfo {
    let coldkey: T::AccountId = ensure_signed(origin)?;
    let weight = Self::do_swap_basket(
        coldkey,
        hotkey,
        origin_netuid,
        destination_netuid,
        amount.to_u64(),
        min_amount_out,
    )?;
    Ok((Some(weight), Pays::Yes).into())
}

Delegates to do_swap_basket.

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