Concepts
Staking and pools
The per-subnet balancer pools, what a stake operation actually does, price impact, swap fees, and MEV-shielded submission.
Staking on a subnet is not a deposit — it is a swap. Every subnet has a liquidity pool holding TAO and the subnet's alpha; staking sells TAO into that pool for alpha, unstaking sells alpha back for TAO. Everything that follows — price impact, swap fees, limit orders, MEV — falls out of that one fact. This page explains the machinery; the staking guide covers the commands, and how money moves covers units and valuation.
The pool
Each subnet's pool is a weighted balancer pool (a generalization of the
constant-product AMM): two reserves, TAO (SubnetTAO) and alpha
(SubnetAlphaIn), plus a pair of weights . The spot price is
With the default equal weights this reduces to the
familiar reserve ratio TAO / alpha. Unlike Uniswap-style pools, the weights
let the protocol add liquidity disproportionally to price without moving
the price — the weights absorb the imbalance instead (they are re-solved on
every injection and bounded so they stay near 0.5 in practice). Only the
protocol adds liquidity this way; user staking always trades against the
pool.
A swap moves along the weighted invariant
. Selling TAO into the pool (staking), the alpha
you receive for Δy TAO is
which is why the execution price curves away from spot as the trade grows — each slice of your order fills at a slightly worse price than the last.
Each subnet's stake operations swap through a weighted pool of TAO and alpha (pallets/swap/src/pallet/balancer.rs): buying alpha pays ∆x = x·(1 − (y/(y+∆y))^(w2/w1)), so the effective price drifts away from spot p = (w1·y)/(w2·x) as trade size grows. A ≈0.05% fee (FeeRate 33/65535) is taken from the input side before the swap and paid to the block author. Alpha reserve is derived so spot is 0.05 τ/α.
Spot price
0.050000 τ/α
p = (w1·y)/(w2·x), w2 = 0.50
Alpha received
38,442.92 α
after 1.01 τ fee (≈0.05% of input)
Effective price
0.052025 τ/α
TAO paid per alpha received
Price impact
4.050%
paying above spot
The math lives in pallets/swap/src/pallet/balancer.rs; quotes over RPC use
exactly the same path (quote-stake,
quote-unstake), so a quote is a faithful
simulation, not an estimate.
What a stake operation does
add-stake is a coldkey-signed extrinsic that moves
TAO out of your free balance, swaps it through the subnet's pool, and
credits the resulting alpha to the (hotkey, coldkey, netuid) stake
position. Step through it:
Step through the on-chain path from add_stake.rs → stake_into_subnet (stake_utils.rs) → the balancer swap in swap_step.rs. Illustrative pool: 10,000 τ / 200,000 α at 0.05 τ/α; the fee is 0.05% of the input side and 100% of it goes to the block author.
coldkey free balance
- balance
- 900.00 τ
- signs
- add_stake(100 τ)
swap fee → block author
- fee (0.05%)
- —
- into pool
- —
balancer pool
- τ reserve
- 10,000.00 τ
- α reserve
- 200,000 α
- price
- 0.0500 τ/α
(hotkey, coldkey, netuid) stake
- alpha
- 0 α
- value
- —
step 1 / 5 — Coldkey signs add_stake(hotkey, netuid, 100 τ) — 100 τ leaves the coldkey's free balance into the subnet account.
alpha received
1,979.2 α
1,999 α at spot — slippage eats the rest
effective price
0.05050 τ/α
spot was 0.0500 τ/α
fee to block author
0.05 τ
input × FeeRate/65535, default ≈ 0.05%
Root staking (netuid 0) skips all of this: there is no pool, so TAO is credited 1:1 as root stake — no swap fee, no slippage, no price movement.
Points worth pinning down:
- The fee comes off the input side first. Amount ×
FeeRate/65535 (default 33 ≈ 0.05%, per-subnet) is deducted before the swap and paid to the block author — TAO when staking, alpha when unstaking. - The swap updates both reserves. Your TAO enters
SubnetTAO, alpha leaves the pool's side (SubnetAlphaIn) and joins the staked total (SubnetAlphaOut). The price after your trade is the new reserve ratio — your own trade moved it. - The position is alpha, not TAO. From the moment the swap settles, the position's TAO value floats with the pool price, and it earns the validator's staking emissions (minus its take) every epoch.
- Unstaking is the mirror image (
remove-stake): alpha leaves the position, the fee is taken in alpha, the swap pays out TAO. There is no unbonding period — the TAO is spendable immediately.
Moving a position between hotkeys on the same subnet
(move-stake) is not a swap — no fee, no price
impact. Cross-subnet moves run two swaps (alpha → TAO → alpha) and pay the
fee once.
Price impact, slippage, and limit orders
Two different things eat your execution price, and they have different defenses:
- Price impact — self-inflicted: your own order consumes reserves as it fills. Deterministic, included in every quote.
- Slippage — external: other transactions land before yours and move the price between quote and execution. Not knowable in advance.
The limit variants (add-stake-limit,
remove-stake-limit) defend against both by
bounding the execution price itself: the chain computes exactly how much can
fill before the pool price reaches your limit, fills up to that point, and
either stops there (allow_partial=true) or rejects the whole order
(allow_partial=false).
add_stake_limit stops filling once the pool price reaches your limit (pallets/swap/src/pallet/balancer.rs): the maximum TAO that fits is ∆y = y·((p′/p)^w1 − 1).
The thin curve is the marginal pool price, which meets the dashed limit line exactly at the fill boundary; the shaded execution-price curve is what you actually pay on average. remove_stake_limit is symmetric with ∆x = x·((p/p′)^w2 − 1).
995 τ of 1,200 τ executes
Limit price
0.051000 τ/α
spot × (1 + 2.0%)
Max fill at limit
995 τ
∆y = y·((p′/p)^w1 − 1)
Requested trade
1,200 τ
crosses the limit price
Outcome
fills partially
995 τ of 1,200 τ executes, the rest is returned
To turn a tolerance into a limit_price: staking, ceiling = spot × (1 +
tolerance); unstaking, floor = spot × (1 − tolerance). Read spot with
alpha-price.
MEV and shielded submission
A large stake order sitting in the public mempool is legible to everyone — including a front-runner who can buy alpha before your order executes, let your price impact reprice the pool, and sell back after. The sandwich's profit is carved out of your execution price.
MEV-shielded submission closes the window: the SDK signs your call as a complete inner extrinsic, encrypts it to the chain's rotating ML-KEM-768 key, and submits only the ciphertext. The mempool sees an opaque blob; the block author decrypts it at block-build time and includes the inner extrinsic in the block it builds. There is no interval where the order is both public and pending.
The same 500 τ stake into a 50,000 τ pool, submitted plain and shielded (docs/concepts/advanced.mdx, pallets/shield). Plain, the order is readable in the mempool and gets sandwiched; shielded, the mempool holds only ciphertext that the block author decrypts at build time.
mempool
block N
block N+1
lane A — plain submission
lane B — shielded submission
step 1 / 5 — The victim submits a 500 τ stake. Plain: the call sits readable in the public mempool. Shielded: the inner call is signed, encrypted to the chain's rotating ML-KEM-768 key with XChaCha20-Poly1305, and wrapped in MevShield.submit_encrypted — the mempool sees only ciphertext.
plain — victim exec price
0.0546 τ/α
9,158 α received
shielded — victim exec price
0.0505 τ/α
9,901 α received
sandwich cost
−743 α
price 8.1% worse; attacker keeps ≈38 τ
Shielding matters for large pool swaps with loose price limits — the cases where your own price impact is worth stealing. Root staking (netuid 0) has no pool, and small swaps move the price too little to sandwich; neither is worth shielding. SDK: client.submit_shielded(intent, wallet).
await client.submit_shielded(
bt.AddStakeLimit(hotkey_ss58="5F...", netuid=1, amount_tao=5_000,
limit_price_rao=int(spot["price_rao"] * 1.01),
allow_partial=False),
wallet,
)Shield and bound the price — they protect against different things. Shielding hides the order from front-runners; the limit caps damage from whatever moves the price anyway. What is worth shielding follows the same economics as price impact: a large swap with a loose limit is the prime target, while small swaps (well under ~1 TAO) and root-network staking (no pool, nothing to front-run) gain nothing from it.
Root staking: the exception
Staking on the root network (netuid 0) is not a pool swap. TAO stays
TAO-denominated and converts 1:1 — no swap fee, no price impact, no MEV
exposure. The limit_price machinery degenerates accordingly: any limit ≥
1 TAO/TAO fills fully, anything lower fills nothing. See
root stake and dividends
for how root positions earn.
Where the liquidity comes from
Nobody LPs these pools. Every block, the coinbase injects each subnet's
share of TAO emission into its pool's TAO reserve, alongside newly issued
alpha into the alpha reserve (alpha_in ≈ tao_in / price, capped by the
subnet's root proportion) — see
emissions. Because the protocol may inject the
two sides disproportionally to price, the balancer weights absorb the
imbalance; this is the reason the pools are weighted at all. Reserves only
grow through injection and trading — stake swaps in TAO, unstake swaps it
back out — and the pool refuses trades that would drain a reserve below a
minimum, and rejects single swaps larger than 1,000× the TAO reserve
(InsufficientLiquidity).
Reading the pool
btcli query alpha-price --netuid 1 --json # spot price now
btcli query quote-stake --netuid 1 --amount-tao 100 --json # simulate entry
btcli query quote-unstake --netuid 1 --amount-alpha 50 --json # simulate exitSpot valuation of a position (alpha × price,
stake-value-for-coldkey) marks to
the current reserve ratio and ignores what your own exit would do to it. For
what you would actually receive, quote the exit — the quote runs the real
swap math against the real reserves.