Transaction Fees in Bittensor
This page describes the blockchain transaction fees charged by Bittensor and shows how to estimate them before running transactions.
Many extrinsic transactions that change the state of the blockchain are subject to a fee based on a combination of weight (computational load) and length (storage load).
Staking and unstaking operations incur this 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:
- The alpha fallback mechanism, whereby transaction fees are paid in alpha when a wallet's TAO balance is insufficient
- Fee-free extrinsics.
- Fees for proxy calls
- Fees for batch transactions
- Estimating fees
- Example: Fees in the lifecycle of a transaction
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_timecomponent, in picoseconds). Calculated as rao. -
Length fee — 1 rao per byte of the encoded extrinsic.
Extrinsics that are fee-free (e.g. set_weights, commit_weights, reveal_weights, sudo) pay neither component.
See affected extrinsics
Staking Operations
add_stakeremove_stakeadd_stake_limitremove_stake_limitremove_stake_full_limitmove_staketransfer_stakeswap_stakeswap_stake_limitunstake_allunstake_all_alpha
Wallet and Identity Management
set_identityset_subnet_identitytry_associate_hotkeyschedule_swap_coldkeyset_coldkey_auto_stake_hotkey
Registration
Subnet Management
Burn/Recycle Alpha
Child Hotkey Management
Governance
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 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.
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
When moving stake between hotkeys within the subnet, no staking fee is applied, only the weight-based transaction fee.
Source code references:
Alpha Fallback
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_stakeremove_stake_limitremove_stake_full_limitunstake_allunstake_all_alphamove_staketransfer_stakeswap_stakeswap_stake_limitrecycle_alphaburn_alpha
- For
remove_stake,remove_stake_limit,recycle_alpha, andburn_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, andburn_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).
Fee-Free Extrinsics
Reading the state of the chain is always free. Additionally, the following extrinsics are free of fees.
Weight Setting & Commit-Reveal
set_weights- Setting validator weightscommit_weights- Commit weight hashbatch_commit_weights- Batch commit weight hashesreveal_weights- Reveal committed weightscommit_crv3_weights- Commit CRv3 encrypted weightsbatch_reveal_weights- Batch reveal committed weights
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.
See: Batch Transactions
See how batch transaction fees are calculated
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.
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.
All three batch variants behave identically for fee purposes.
Source code: weight_and_dispatch_class pallets/utility/src/lib.rs:606–618.
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):
See Proxies: Overview for a full description of proxy types, delays, and use cases.
#[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.
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.
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:
| Constant | Rao | TAO |
|---|---|---|
ProxyDepositBase | 60,000,000 | τ 0.06 |
ProxyDepositFactor | 33,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):
| Constant | Rao | TAO |
|---|---|---|
AnnouncementDepositBase | 36,000,000 | τ 0.036 |
AnnouncementDepositFactor | 68,000,000 | τ 0.068 per announcement |
Up to 75 pending announcements are allowed per account (MaxPending). Source code: runtime/src/lib.rs:546–549.
Estimating fees
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.
For add_stake, remove_stake, move_stake, or swap_stake:
- Swap fee:
sim_swap(origin_netuid, destination_netuid, amount)→ usetao_feeoralpha_feeas appropriate (and same-subnet moves have no swap fee). - Transaction fee: Compose the extrinsic, then
get_payment_info(call, keypair)orget_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). ReturnsSimSwapResult:tao_amount,alpha_amount,tao_fee,alpha_fee.sim_swap_alpha_for_tao(netuid, alpha)— simulates alpha → TAO (e.g. remove_stake). ReturnsSimSwapResultwith fee and amounts.
Both official clients, BTCLI and the Python SDK, support sim swaps.
- BTCLI
- Bittensor SDK
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:
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.
Use the SDK's subtensor.sim_swap(origin_netuid, destination_netuid, amount).
This method hits the blockchain's SimSwap runtime API to compute the swap simulation, and for cross-subnet moves, simulates two swaps and combines the result. The result is a SimSwapResult with:
tao_fee,alpha_fee— the swap/liquidity fee (in TAO or alpha)tao_amount,alpha_amount— expected output amounts- Slippage fields
Btcli uses sim_swap to show “Fee (τ)” and “Est. Received” in stake add/remove/move. The SDK’s get_fee_for_stake_add, get_fee_for_stake_remove, and get_fee_for_move_stake use sim_swap for the liquidity-fee part.
This example estimates swap fee and output for add_stake (TAO → alpha for SN 14):
import bittensor as bt
sub = bt.Subtensor(network="finney")
netuid = 14
amount_stake = bt.Balance.from_tao(0.1)
result = sub.sim_swap(
origin_netuid=0,
destination_netuid=netuid,
amount=amount_stake,
)
print(result)
SimSwapResult(tao_amount=τ0.099949646, alpha_amount=2.635103285γ, tao_fee=τ0.000050354, alpha_fee=0.000000000γ)
This example estimates swap fee and output for remove_stake (alpha for SN 14 → TAO):
import bittensor as bt
sub = bt.Subtensor(network="finney")
netuid = 14
amount_stake = bt.Balance.from_tao(0.1)
result = sub.sim_swap(
origin_netuid=14,
destination_netuid=0,
amount=amount_stake,
)
print(result)
SimSwapResult(tao_amount=τ0.000964782, alpha_amount=0.099949646ξ, tao_fee=τ0.000000000, alpha_fee=0.000050354ξ)
This example estimates swap for a cross-subnet move (subnet 14 → subnet 5):
import bittensor as bt
sub = bt.Subtensor(network="finney")
amount_alpha = bt.Balance.from_tao(1).set_unit(5)
result = sub.sim_swap(
origin_netuid=14,
destination_netuid=5,
amount=amount_alpha,
)
print(result)
SimSwapResult(tao_amount=τ0.009642946, alpha_amount=0.625912713ξ, tao_fee=τ0.000004858, alpha_fee=0.000503280ξ)
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.
Extrinsic fee simulation
To estimate the weight + length fee for any extrinsic (including stake calls), use the chain's payment query APIs in the polkadot browser app, or use the Bittensor Python SDK
- Polkadot.js App
- BTCLI
- Bittensor SDK
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)orquery_fee_details(uxt, len)— full fee breakdown for a given extrinsic and its encoded lengthquery_weight_to_fee(weight)— weight component onlyquery_length_to_fee(length)— length component only
The stake add, stake remove, and stake move commands display Extrinsic Fee (τ) in the preview table alongside the swap fee, as described above:
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
│ │ │ β/Τ │ β │ │ │ β/Τ │
────────┼──────────────┼────────────┼──────────────┼──────────────┼──────────┼──────────────┼──────────────┼──────────────
│ │ │ │ │ │ │ │
Two methods in the SDK can be used to get fees for a transaction:
subtensor.get_payment_info(call, keypair)returns a dict includingpartial_fee(the total transaction fee in rao). This method is used by BTCLI under the hood to fetch “Extrinsic Fee”.subtensor.get_extrinsic_fee(call, keypair)callsget_payment_info()but returns only theBalanceamount for the fee.
You must compose the call (e.g. the exact add_stake, move_stake, or other extrinsic and params) before calling these; they simulate the fee for that call and keypair. See Working with Blockchain Calls
import bittensor as bt
sub = bt.Subtensor(network="test")
wallet = bt.Wallet(name="my_wallet", hotkey="my_hotkey")
wallet.unlock_coldkey()
netuid = 1
hotkey_ss58 = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
amount = bt.Balance.from_tao(10)
# Compose the add_stake call (same params you would use to send the tx)
call = sub.compose_call(
call_module="SubtensorModule",
call_function="add_stake",
call_params={
"hotkey": hotkey_ss58,
"netuid": netuid,
"amount_staked": amount.rao,
},
)
# Keypair is the account that pays the fee (coldkey)
fee = sub.get_extrinsic_fee(call=call, keypair=wallet.coldkeypub)
print(f"Estimated transaction fee: {fee}")
Estimated transaction fee: τ0.001337128
Batch fee simulation
Because a batch extrinsic is just a single composed call, pass the finished batch_call to get_extrinsic_fee before sending it as for any other 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)
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},
)
batch_call = sub.compose_call(
call_module="Utility",
call_function="batch_all",
call_params={"calls": [call_1, call_2]},
)
# Estimate the transaction fee before sending
fee = sub.get_extrinsic_fee(call=batch_call, keypair=wallet.coldkeypub)
print(f"Estimated transaction fee: {fee}")
Estimated transaction fee: τ0.001401232
Note this is only the weight + length transaction fee. For staking operations you also need the swap fee — use sub.sim_swap() per inner call for that. See Estimating fees for the full picture.
Example: Fees in the lifecycle of a transaction
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.
Scenario: Moving 1 TAO worth of alpha from subnet 5 to subnet 18 — same coldkey, different hotkeys.
What the chain does
-
The transaction fee is charged upfront. Before the extrinsic executes, the runtime deducts the weight + length fee from the coldkey’s TAO free balance (
withdraw_fee). If the balance is insufficient, the transaction is rejected immediately.- Weight fee: rao (
LinearWeightToFee:Perbill::from_parts(50_000)). - Length fee: 1 rao per byte of the encoded extrinsic (
LengthToFee = IdentityFee).
- Weight fee: rao (
-
The move executes.
do_move_stakecallstransition_stake_internal, which runs two swaps because origin and destination subnets differ:- Unstake on origin (subnet 5):
unstake_from_subnetconverts the alpha to TAO, taking a swap fee in alpha (drop_fee_origin). - Stake on destination (subnet 18):
stake_into_subnetconverts that TAO to alpha with no swap fee (drop_fee_destination = true). Only one liquidity fee is charged per move.
- Unstake on origin (subnet 5):
-
Which side pays the swap fee depends on the origin.
drop_fee_origin = origin_netuid == NetUid::ROOT,drop_fee_destination = !drop_fee_origin:- Non-root → subnet: swap fee on the origin unstake (alpha→TAO).
- Root → subnet: swap fee on the destination stake (TAO→alpha).
For this scenario (subnet 5 → subnet 18), the coldkey pays the transaction fee in TAO, and one swap fee is taken in alpha from the moved liquidity on subnet 5.
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: . 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 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 (calculate_fee_amount for mechanism 1), where 65,535 is the maximum possible fee rate value. With the default DefaultFeeRate of 33:
- Rate .
- 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).
Code references
| Claim | Source |
|---|---|
| move_stake weight (164_300_000 + reads(15) + writes(7)) | dispatches.rs L1557-L1559 |
| Weight-to-fee: 50_000/10^9 | transaction-fee/src/lib.rs L43-L56 |
| Length-to-fee: IdentityFee | runtime/src/lib.rs L564 |
| Fee withdrawal | transaction-fee/src/lib.rs L315 |
| do_move_stake, transition_stake_internal | move_stake.rs L30, move_stake.rs L298 |
| drop_fee_origin / drop_fee_destination | move_stake.rs L354-L355 |
| unstake_from_subnet / stake_into_subnet | move_stake.rs L358, move_stake.rs L376 |
| transfer_stake_within_subnet (no swap) | stake_utils.rs L883 |
| Swap fee: FeeRate/u16::MAX, DefaultFeeRate=33 | swap/impls.rs L367, swap/mod.rs L78 |
| get_stake_fee runtime API | runtime/src/lib.rs L2504 |