Skip to main content
SUBMIT A PRSUBMIT AN ISSUElast edit: Mar 13, 2026

Transaction Fees in Bittensor

This page describes the blockchain transaction fees charged by Bittensor and shows how to estimate them prior to your transactions.

Many extrinsic transactions that change the state of the blockchain are subject to a transaction fee based on a combination of transaction weight (computational load) and transaction length (storage load).

Staking and unstaking operations incur this transaction fee as well as amount-based swap fees of 0.05% of the transacted liquidity.

Reading the state of the chain is always free.

This page also covers:

In-depth example (fees for a cross-subnet move_stake) and Estimating fees (how to estimate before sending).

General Transaction Fees

Each fee-bearing extrinsic is charged two components, both paid from the sender’s TAO free balance and recycled (deducted from TotalIssuance; see Recycling and Burning):

  • Weight fee — proportional to compute time (weight; specifically the ref_time component, in picoseconds). Calculated as weight×(50,000/109)\text{weight} \times (50{,}000 / 10^9) rao.

  • Length fee — 1 rao per byte of the encoded extrinsic.

Total transaction fee=weight fee+length fee\text{Total transaction fee} = \text{weight fee} + \text{length fee}

Extrinsics that are fee-free (e.g. set_weights, commit_weights, reveal_weights, sudo) pay neither component.

See affected extrinsics
See how fees are calculated

Weight fee — source: pallets/transaction-fee/src/lib.rs:44-56

pub struct LinearWeightToFee;

impl WeightToFeePolynomial for LinearWeightToFee {
type Balance = Balance;

fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
let coefficient = WeightToFeeCoefficient {
coeff_integer: 0,
coeff_frac: Perbill::from_parts(50_000), // 0.005%
negative: false,
degree: 1,
};
smallvec!(coefficient)
}
}

Length fee — runtime config sets LengthToFee = IdentityFee: 1 rao per byte of the encoded extrinsic. Source: runtime/src/lib.rs and the standard FRAME Transaction Payment pallet.

Swap Fees for Stake and Unstake Operations

In addition to the weight-based fee above, staking and unstaking operations are subject to fees based on a percentage of the quantity of transacted liquidity. When moving stake between subnets—whether through a transfer, swap, or move—a 0.05% fee is applied. If the move happens within the same subnet, no additional fee is incurred, only the weight-based fee.

Fee Details:

  • Rate: 0.05%
  • For staking: Fee paid in TAO from the staking amount
  • For unstaking: Fee paid in Alpha from the unstaking amount

See: Swap Simulator example

Source code references:

Alpha Fallback

note

This feature is not yet active. The alpha fallback logic is implemented but currently disabled in the chain. At present, if a coldkey cannot cover the transaction fee in TAO, the transaction is rejected.

For the unstaking and stake-movement extrinsics listed below, if the sender's TAO balance cannot cover the transaction fee, the chain will fall back to charging the fee in Alpha instead. If both TAO and Alpha balances are insufficient to cover the fee, the transaction is rejected before it is processed. When fees are paid in Alpha, the TAO fee amount is converted to Alpha at the current Alpha price with no slippage.

Affected extrinsics

  • remove_stake
  • remove_stake_limit
  • remove_stake_full_limit
  • unstake_all
  • unstake_all_alpha
  • move_stake
  • transfer_stake
  • swap_stake
  • swap_stake_limit
  • recycle_alpha
  • burn_alpha
balance edgecases
  • For remove_stake, remove_stake_limit, recycle_alpha, and burn_alpha: after withdrawing Alpha fees, if the remaining Alpha balance is too small to keep as a dust balance, the transaction will consume and process the entire remaining Alpha balance in the same call.
  • For remove_stake, remove_stake_limit, recycle_alpha, and burn_alpha: if the requested amount exceeds the available Alpha, the amount is capped at the available Alpha and the extrinsic succeeds (assuming no other errors).

Proxy Call Fees

When a call is dispatched through the proxy extrinsic, the total transaction fee covers the proxy's own overhead plus the inner call's weight. Crucially, the outer extrinsic inherits pays_fee and dispatch_class directly from the inner call (pallets/proxy/src/lib.rs:232–238):

#[pallet::weight({
let di = call.get_dispatch_info();
(T::WeightInfo::proxy(T::MaxProxies::get())
.saturating_add(T::DbWeight::get().reads_writes(1, 1))
.saturating_add(di.call_weight),
di.class, di.pays_fee)
})]

This means that if the inner call is fee-free (e.g. set_weights, commit_weights), the entire proxy call is also free — no transaction fee is charged.

Proxy registration deposits

Registering a proxy relationship locks a reserved balance from the real account's free TAO — this is not burned. It is returned in full when the proxy is removed. The total deposit for n proxies on a single real account is:

proxy deposit=60,000,000base+33,000,000×nper proxy rao\text{proxy deposit} = \underbrace{60{,}000{,}000}_{\text{base}} + \underbrace{33{,}000{,}000 \times n}_{\text{per proxy}} \text{ rao}

ConstantRaoTAO
ProxyDepositBase60,000,000τ 0.06
ProxyDepositFactor33,000,000τ 0.033 per proxy

So one proxy relationship costs τ 0.093 reserved; each additional proxy on the same real account adds τ 0.033.

Source code: deposit formula runtime/src/lib.rs:199–204, constants runtime/src/lib.rs:539–543, deposit calculation pallets/proxy/src/lib.rs:964–971.

Verify deposit amounts on-chain

These values are runtime constants (compiled into the WASM blob) and can only change with a runtime upgrade. To verify the current live values against what is documented here:

import bittensor as bt

sub = bt.Subtensor(network="finney")
s = sub.substrate

for name in [
"ProxyDepositBase",
"ProxyDepositFactor",
"AnnouncementDepositBase",
"AnnouncementDepositFactor",
"MaxProxies",
"MaxPending",
]:
print(f"{name}: {s.get_constant('Proxy', name)}")
ProxyDepositBase: 60000000
ProxyDepositFactor: 33000000
AnnouncementDepositBase: 36000000
AnnouncementDepositFactor: 68000000
MaxProxies: 20
MaxPending: 75

Announcement deposits

Proxies configured with a non-zero delay must announce calls before executing them. Each pending announcement locks an additional reserved balance (also returned on removal or execution):

announcement deposit=36,000,000base+68,000,000×nper announcement rao\text{announcement deposit} = \underbrace{36{,}000{,}000}_{\text{base}} + \underbrace{68{,}000{,}000 \times n}_{\text{per announcement}} \text{ rao}

ConstantRaoTAO
AnnouncementDepositBase36,000,000τ 0.036
AnnouncementDepositFactor68,000,000τ 0.068 per announcement

Up to 75 pending announcements are allowed per account (MaxPending). Source code: runtime/src/lib.rs:546–549.

Proxy management extrinsics

add_proxy, remove_proxy, remove_proxies, create_pure, kill_pure, announce, remove_announcement, and reject_announcement all pay the standard weight + length fee. add_proxy and remove_proxy additionally adjust the reserved proxy deposit.

See Proxies: Overview for a full description of proxy types, delays, and use cases.


Batch Transaction Fees

The utility pallet's batch, batch_all, and force_batch extrinsics aggregate the fees of their inner calls. The weight of the outer extrinsic is the sum of the inner call weights plus a small per-call overhead for the batch wrapper itself.

The pays_fee for the entire batch is determined by weight_and_dispatch_class:

let mut pays = Pays::No;

for di in calls.iter().map(|call| call.get_dispatch_info()) {
total_weight = total_weight.saturating_add(di.call_weight);
if di.pays_fee == Pays::Yes {
pays = Pays::Yes;
}
}

The batch pays a fee if any inner call is fee-bearing. The batch is free only if all inner calls are fee-free. For example, batching set_weights (free) with add_stake (fee-bearing) results in a fee being charged for the batch.

note

This applies only to the weight + length transaction fee. Swap fees for staking operations are assessed per-call inside the runtime and are not affected by the batch's pays_fee.

batch vs batch_all vs force_batch

All three behave identically for fee purposes. They differ only in error handling:

ExtrinsicOn error
batchStops at first failure; prior calls succeed. Emits BatchInterrupted.
batch_allReverts all calls atomically on any failure.
force_batchContinues past failures; failed calls are skipped.

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, weight_and_dispatch_class pallets/utility/src/lib.rs:606–618.

Using batch calls with the SDK

The Bittensor SDK does not have a high-level batch wrapper — add_stake_multiple and unstake_multiple send individual extrinsics sequentially, not a single batch extrinsic. To submit a true batch (one extrinsic on-chain), use the low-level compose_call + sign_and_send_extrinsic path directly.

Use batch_all (atomic) when all inner calls must succeed or none should. Use batch if partial success is acceptable, or force_batch to continue past failures. See the comparison table above.

Example: stake to two hotkeys in a single atomic batch extrinsic

import bittensor as bt

sub = bt.Subtensor(network="finney")
wallet = bt.Wallet(name="my_wallet", hotkey="my_hotkey")
wallet.unlock_coldkey()

hotkey_1 = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
hotkey_2 = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"
netuid = 1
amount = bt.Balance.from_tao(10)

# Compose each inner call individually
call_1 = sub.compose_call(
call_module="SubtensorModule",
call_function="add_stake",
call_params={
"hotkey": hotkey_1,
"netuid": netuid,
"amount_staked": amount.rao,
},
)
call_2 = sub.compose_call(
call_module="SubtensorModule",
call_function="add_stake",
call_params={
"hotkey": hotkey_2,
"netuid": netuid,
"amount_staked": amount.rao,
},
)

# Wrap in a 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 the batch as a single extrinsic
success, error_message = sub.sign_and_send_extrinsic(
call=batch_call,
wallet=wallet,
wait_for_inclusion=True,
wait_for_finalization=False,
)
print(f"Success: {success}" if success else f"Failed: {error_message}")
add_stake_multiple is not a batch extrinsic

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


Estimating fees (before you send a transaction)

The chain and the SDK expose two separate estimation paths: one for the swap/liquidity fee (stake, unstake, move, swap) and one for the transaction fee (weight + length). Use both when you want the full cost of a staking-related call.

Swap simulator

The SimSwap Runtime API simulates a swap and returns the liquidity fee and expected amounts. It does not include the transaction (extrinsic) fee.

Full cost for a stake operation

For add_stake, remove_stake, move_stake, or swap_stake:

  1. Swap fee: sim_swap(origin_netuid, destination_netuid, amount) → use tao_fee or alpha_fee as appropriate (and same-subnet moves have no swap fee).
  2. Transaction fee: Compose the extrinsic, then get_payment_info(call, keypair) or get_extrinsic_fee(call, keypair)partial_fee.

Total estimated cost = transaction fee (in TAO) + swap fee (in TAO or alpha, depending on direction).

On chain, the runtime implements SwapRuntimeApi with:

  • sim_swap_tao_for_alpha(netuid, tao) — simulates TAO → alpha (e.g. add_stake). Returns SimSwapResult: tao_amount, alpha_amount, tao_fee, alpha_fee.
  • sim_swap_alpha_for_tao(netuid, alpha) — simulates alpha → TAO (e.g. remove_stake). Returns SimSwapResult with fee and amounts.

Both official clients, BTCLI and the Python SDK, support sim swaps.

BCLI does not offer a separate sim swap command, but when you run stake add, stake remove, or stake move, BTCLI shows a preview table with the swap fee (Fee (τ)) and Extrinsic Fee (τ) (transaction fee) before you confirm:

danger

the only way to see these fees in BTCLI is to run the actual stake command; the table is printed before execution. Use the default prompt and answer "no" at "Would you like to continue?" to exit without sending the transaction.

btcli stake add
...

Amount to stake (TAO τ): 100

Staking to:
Wallet: 2MuchTau!, Coldkey ss58: 5Xj...
Network: test

┃ ┃ ┃ ┃ ┃ ┃ ┃ Rate with ┃ Partial
┃ ┃ ┃ ┃ Est. ┃ ┃ Extrinsic ┃ tolerance: ┃ stake
Netuid ┃ Hotkey ┃ Amount (τ) ┃ Rate (per τ) ┃ Received ┃ Fee (τ) ┃ Fee (τ) ┃ (0.5%) ┃ enabled
━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━
2 │ 5GrwvaEF5zX… │ 100.0000 τ │ 2416.813286… │ 241,556.4147 │ Τ 0.0504 │ 0.0013 τ │ 2404.7893 │ False
│ │ │ β/Τ │ β │ │ │ β/Τ │
────────┼──────────────┼────────────┼──────────────┼──────────────┼──────────┼──────────────┼──────────────┼──────────────
│ │ │ │ │ │ │ │

The table lists each hotkey/netuid with Fee (τ) (swap fee) and Extrinsic Fee (τ). To only view fees without executing, answer no at the confirmation prompt.

Code references: Runtime SwapRuntimeApi (sim_swap_tao_for_alpha, sim_swap_alpha_for_tao); swap pallet sim_swap; SDK sim_swap and btcli subtensor_interface.sim_swap; chain_data SimSwapResult.

Transaction (extrinsic) fee

To estimate the weight + length fee for any extrinsic (including stake calls), you can use the chain's payment query APIs in the polkadot browser app, or use the Bittensor Python SDK

You can query fee details directly from the chain using the Polkadot.js browser app connected to Finney. Under Developer → Runtime Calls, use TransactionPaymentApi:

  • query_info(uxt, len) or query_fee_details(uxt, len) — full fee breakdown for a given extrinsic and its encoded length
  • query_weight_to_fee(weight) — weight component only
  • query_length_to_fee(length) — length component only

Fee-Free Extrinsics

The following extrinsics are free.

Weight Setting & Commit-Reveal

In-depth example: fees for a cross-subnet move_stake

This section traces a single move_stake transaction from the moment it is submitted until the chain has applied every fee. It shows how the transaction fee (weight + length) and the swap/liquidity fee combine, and how the chain avoids double-charging on moves. All claims below are backed by source references; see Code references for this section at the end.

Scenario (from Managing stake with the SDK):
You move stake from subnet 5 (origin) to subnet 18 (destination). The amount is 1 TAO worth of alpha on subnet 5 (the coldkey signs; the amount is specified in origin-subnet alpha). Same coldkey, different hotkeys and subnets.

What the chain does

  1. Validate and charge the transaction fee (before dispatch)
    The runtime treats the call as a normal signed extrinsic: it computes weight and length (the size in bytes of the encoded extrinsic; see Length-Based Transaction Fee), then charges:

    • Weight fee: weight×(50,000/109)\text{weight} \times (50{,}000 / 10^9) rao (from LinearWeightToFee: Perbill::from_parts(50_000)).
    • Length fee: 1 rao per byte of the encoded extrinsic (from runtime config LengthToFee = IdentityFee).
      This is withdrawn from the coldkey’s TAO free balance. If the coldkey cannot pay, the extrinsic fails at validation and nothing else runs. Fee withdrawal: OnChargeTransaction::withdraw_fee.
  2. Execute the move (transition_stake_internal)
    do_move_stake calls transition_stake_internal. Because origin_netuid != destination_netuid, the chain:

    • Unstake on origin (subnet 5): Converts the requested alpha to TAO via the subnet's swap. A swap fee is applied here unless the origin is root (see below). Source: unstake_from_subnet with drop_fee_origin.
    • Stake on destination (subnet 18): Converts the resulting TAO to alpha on subnet 18 (stake_into_subnet with drop_fee_destination). For move_stake (and swap_stake), the chain does not charge a second swap fee on the destination when the origin is not root: it sets drop_fee_destination = true so that only one liquidity fee is taken (on the origin unstake). This avoids double-charging on a single move.
  3. Fee logic for moves
    In the code, drop_fee_origin = origin_netuid == NetUid::ROOT and drop_fee_destination = !drop_fee_origin. So:

    • Move from a non-root subnet (e.g. 5) to another subnet (e.g. 18): Fee is charged only on the origin (alpha→TAO on subnet 5). No fee on the destination stake.
    • Move from root to a subnet: Fee is charged only on the destination (TAO→alpha). No fee on the “unstake” from root.

So for subnet 5 → subnet 18, the user pays:

  • The transaction fee (weight + length) in TAO from the coldkey.
  • One swap fee (on subnet 5’s alpha→TAO conversion), taken from the moved liquidity (in alpha).

Calculating the fees

1. Transaction fee (weight component)
The move_stake extrinsic has a declared weight of (dispatches.rs):

Weight::from_parts(164_300_000, 0)
+ DbWeight::reads(15)
+ DbWeight::writes(7)

The chain converts this to rao using the fee formula: fee_rao=weight×50,000/109\text{fee\_rao} = \text{weight} \times 50{,}000 / 10^9. The exact weight contribution of each read and write is determined by the chain’s DbWeight constants. For illustration, if the total weight is about 165_000_000:

  • Weight fee 165,000,000×50,000/109\approx 165{,}000{,}000 \times 50{,}000 / 10^9 \approx 8,250 rao (about 0.00000825 TAO).

2. Transaction fee (length component)
The encoded extrinsic includes the call index, two account IDs (32 bytes each), two netuids, and an alpha amount. A typical size is on the order of 100–200 bytes. At 1 rao per byte (LengthToFee = IdentityFee):

  • Length fee ≈ 100–200 rao.

3. Swap fee (liquidity component)
For subnet 5 (mechanism 1), the fee is alpha_amount×(FeeRate(netuid)/65,535)\text{alpha\_amount} \times (\text{FeeRate}(\text{netuid}) / 65{,}535) (calculate_fee_amount for mechanism 1), where 65,535 is the maximum possible fee rate value. With the default DefaultFeeRate of 33:

  • Rate 33/65,5350.0504%\approx 33 / 65{,}535 \approx 0.0504\%.
  • For 1 TAO worth of alpha on subnet 5, the swap fee is about 0.0005 TAO (about 500_000 rao), taken in alpha from the amount moved.

So in this example, total cost to the user is roughly:

  • Transaction fee: ~8_250 + ~150 ≈ ~8_400 rao (0.0000084 TAO), paid from the coldkey’s TAO balance.
  • Swap fee: ~0.05% of the moved amount, paid in alpha (from the stake on subnet 5).

The swap fee is much larger than the transaction fee for typical move amounts; the transaction fee is still required and must be covered by the coldkey’s TAO.

Same-subnet move

If you move_stake (or transfer_stake) within the same subnet (same hotkey or different hotkey, same netuid), the chain uses transfer_stake_within_subnet: it only reattributes alpha between hotkeys/coldkeys. There is no swap, so no swap fee—only the weight and length transaction fee apply. (Comment in code: "Does not incur any swapping nor fees".)

How to get exact numbers

  • Transaction fee: Use the runtime’s TransactionPaymentApi.query_fee_details(uxt, len) or the SDK’s get_payment_info(call, keypair) to get the actual inclusive fee (e.g. partial_fee) for the constructed extrinsic.
  • Swap fee for a move: Use the runtime API get_stake_fee (origin, origin_coldkey, destination, destination_coldkey, amount) or the SimSwap API / SDK sim_swap for the path and amount you use. For move_stake, the chain charges only one side (origin or destination) as above, so the displayed “stake fee” from these APIs should be interpreted in that light.

Code references

ClaimSource
move_stake weight (164_300_000 + reads(15) + writes(7))dispatches.rs L1535-L1538
Weight-to-fee: 50_000/10^9transaction-fee/src/lib.rs L43-L56
Length-to-fee: IdentityFeeruntime/src/lib.rs L504
Fee withdrawaltransaction-fee/src/lib.rs L307-L335
do_move_stake, transition_stake_internalmove_stake.rs L30-L75, L298-L398
drop_fee_origin / drop_fee_destinationmove_stake.rs L354-L355
unstake_from_subnet / stake_into_subnetmove_stake.rs L356-L383
transfer_stake_within_subnet (no swap)stake_utils.rs L850-L857
Swap fee: FeeRate/u16::MAX, DefaultFeeRate=33swap/impls.rs L362-L381, swap/mod.rs L75-L83
get_stake_fee runtime APIruntime/src/lib.rs L2476-L2477