Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
f472452
describe every chain error with its trigger and where to check
unarbos Jul 13, 2026
3e4f4bc
restore PEP 740 provenance attestations on PyPI releases
unarbos Jul 13, 2026
d316a45
expose every owner-settable hyperparameter and describe each in the l…
unarbos Jul 13, 2026
7b548b6
give each hyperparameter an explainer page with a playable demo
unarbos Jul 13, 2026
a66ae53
add a parameter-specific chart to every hyperparameter explainer page
unarbos Jul 13, 2026
8ab26eb
add activity_cutoff_factor hyperparameter, expose derived hyperparams…
unarbos Jul 14, 2026
66944fe
describe the new TAO and Alpha insufficient-balance error variants
unarbos Jul 14, 2026
088eae1
preserve dissolution stake settlement cursor
unarbos Jul 14, 2026
d359d53
split InsufficientBalance into TAO and Alpha variants
unarbos Jul 14, 2026
a7743d8
refund unswapped tao in stake_into_subnet at price limit
unarbos Jul 14, 2026
201a2e8
remove alpha transfer cap bypass in move_stake
unarbos Jul 14, 2026
06e22e9
charge alpha-based fees for hotkey swaps
unarbos Jul 14, 2026
92ebe74
account for check_nonce weight in extension
unarbos Jul 14, 2026
7949e89
wire cleanup_swap_v3 and orphan subnet-identity migrations
unarbos Jul 14, 2026
d06c697
add optional jemalloc allocator feature for the node
unarbos Jul 14, 2026
6529178
document neuron precompile methods
unarbos Jul 14, 2026
b5cffc5
add signer-side srtool verification script
unarbos Jul 14, 2026
0826b89
defer wallet-security details to multisig and proxy guides
unarbos Jul 14, 2026
21adad3
alias staging docs via the deployments api
unarbos Jul 14, 2026
a4f3d8d
fix wrong and outdated doc comments
unarbos Jul 14, 2026
51c89df
bump brace-expansion, lodash, picomatch, and flatted in website
unarbos Jul 14, 2026
70f2a73
regen sdk generated-file headers to spec 430
unarbos Jul 14, 2026
796fae5
bump spec_version to 431
unarbos Jul 14, 2026
621edcd
surface keyfile errors as clean cli failures in wallet commands
unarbos Jul 14, 2026
13f6d60
document v431 chain and runtime changes in migration guide and releas…
unarbos Jul 14, 2026
9ded802
restore devnet and testnet docker image publishing
unarbos Jul 14, 2026
c6b7442
beautify the extension signing bridge ui
unarbos Jul 14, 2026
18a6b4d
Charge identity migration weight for every scanned entry
unarbos Jul 14, 2026
f3f90ce
fix clippy duplicate attribute and ruff formatting
unarbos Jul 14, 2026
9ad3920
surface actionable bridge errors in the signing page UI
unarbos Jul 14, 2026
ee87586
Finish dissolution hotkey cleanup when on_idle weight runs out
unarbos Jul 14, 2026
6a15266
ci: lock cargo-audit installation
UnArbosFive Jul 14, 2026
127e9cb
Fix btcli/docs audit issues ahead of v431 release.
unarbos Jul 14, 2026
cb2ce82
Fix CI: dissolve cursor borrow and e2e manifest count.
unarbos Jul 14, 2026
e13c7bb
Mark mechanism emission split as raw-only for coverage.
unarbos Jul 14, 2026
b27ab99
fix(runtime): preserve deprecated tempo call indices
UnArbosFour Jul 14, 2026
4fb248b
Fix clippy needless borrow in destroy_alpha_tests.
unarbos Jul 14, 2026
66022e8
fix(runtime): make deprecated tempo calls no-op
UnArbosFour Jul 14, 2026
ebff258
Retry flaky GHCR docker pushes in e2e image job.
unarbos Jul 14, 2026
44af61a
Merge pull request #2906 from RaoFoundation/fix/preserve-tempo-call-i…
unarbos Jul 14, 2026
9596e35
Validate hyperparameter values against codec and semantic bounds.
unarbos Jul 14, 2026
1b5a108
Fix CI: ruff-format hyperparams and clippy indexing in tempo tests.
unarbos Jul 14, 2026
5406f31
Mark deprecated tempo call stubs raw-only and refresh docs catalogs.
unarbos Jul 14, 2026
dc96d2b
Regenerate SDK metadata layer for runtime spec 431.
unarbos Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .github/workflows/publish-sdk-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ jobs:
working-directory: sdk/python
run: uv build --out-dir ../../dist

# PEP 740 provenance, same as the rc/stable publish jobs. Running it on
# the dev channel means a broken attestation step fails here on every
# push to main instead of for the first time during a release.
- name: Generate PEP 740 attestations
uses: astral-sh/attest-action@f589a42a7efb6fe400b4f400de60b4bc90390027 # v0.0.6

- name: Publish to TestPyPI
run: |
uv publish --trusted-publishing always \
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/release-train.yml
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ jobs:
- name: Build SDK wheel and sdist
working-directory: sdk/python
run: uv build --out-dir ../../dist
# PEP 740 provenance: sign every dist with this job's OIDC identity
# (must match the trusted publisher, so it runs here rather than in
# build-core-wheels.yml). uv publish uploads the resulting
# *.publish.attestation files alongside each dist.
- name: Generate PEP 740 attestations
uses: astral-sh/attest-action@f589a42a7efb6fe400b4f400de60b4bc90390027 # v0.0.6
# --check-url makes a re-run after a partial upload idempotent: files
# already on the index are skipped instead of failing with a 400
# duplicate (e.g. the SDK wheel landed but bittensor-core didn't).
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/watch-mainnet-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ jobs:
working-directory: sdk/python
run: uv build --out-dir ../../dist

# PEP 740 provenance: sign every dist with this job's OIDC identity.
# Must happen here, not in build-core-wheels.yml — PyPI only accepts
# attestations whose Sigstore identity matches the trusted publisher
# doing the upload. uv publish picks up the *.publish.attestation
# files from dist/ automatically.
- name: Generate PEP 740 attestations
uses: astral-sh/attest-action@f589a42a7efb6fe400b4f400de60b4bc90390027 # v0.0.6

# --check-url makes a re-run after a partial upload idempotent: files
# already on the index are skipped instead of failing with a 400
# duplicate (e.g. the SDK wheel landed but bittensor-core didn't).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contracts alongside native Substrate extrinsics.
| Shared Rust | [`common/`](./common/), [`primitives/`](./primitives/), [`support/`](./support/) | Shared types, math primitives, lints, macros, weight tooling. |
| Client core | [`sdk/bittensor-core/`](./sdk/bittensor-core/) | The Rust core for Bittensor clients (keypairs, keyfiles, SCALE codec, extrinsic assembly, RFC-0078 digest, drand timelock, ML-KEM) — built against the same crate revisions as the chain; Python bindings in [`sdk/bittensor-core-py/`](./sdk/bittensor-core-py/). |
| Python SDK | [`sdk/python/`](./sdk/python/) | The `bittensor` package and `btcli` CLI. Generated from chain metadata so reads, writes, and docs cannot drift. |
| Documentation | [`docs/`](./docs/) | Source for bittensor.com/docs. `docs/tx/`, `docs/query/`, and `docs/errors.mdx` are generated — do not hand-edit. |
| Documentation | [`docs/`](./docs/) | Source for bittensor.com/docs. `docs/tx/`, `docs/query/`, and `docs/errors/` are generated — do not hand-edit. |
| Website | [`website/`](./website/) | Yarn + Turbo monorepo; `apps/bittensor-website` renders marketing pages and these docs. |
| Tests | [`ts-tests/`](./ts-tests/), [`clones/`](./clones/), [`eco-tests/`](./eco-tests/) | Moonwall/zombienet integration tests, mainnet-clone regression harness, indexer contract tests. |
| Operations | [`chainspecs/`](./chainspecs/), [`scripts/`](./scripts/), [`Dockerfile`](./Dockerfile) | Chain specs, CI/dev scripts, production and localnet Docker images. |
Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/meta.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"title": "Concepts",
"pages": ["network", "emissions", "wallets", "money", "client", "transactions", "advanced"]
"pages": ["network", "emissions", "wallets", "money", "staking-pools", "client", "transactions", "advanced"]
}
4 changes: 3 additions & 1 deletion docs/concepts/money.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ slippage.

## Slippage

Staking and unstaking are swaps against a pool, so the trade itself moves the
Staking and unstaking are swaps against a pool
([staking and pools](/docs/concepts/staking-pools) explains the pool
mechanics with interactive charts), so the trade itself moves the
price: your order consumes reserves as it fills, and the gap between what the
spot price promised and what you actually receive grows with the trade's size
relative to the pool's liquidity. Strictly, that self-inflicted gap is
Expand Down
170 changes: 170 additions & 0 deletions docs/concepts/staking-pools.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
title: Staking and pools
description: 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](/docs/guides/staking) covers the commands, and
[how money moves](/docs/concepts/money) 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 $w_1 + w_2 = 1$. The spot price is

$$
p = \frac{w_1 \cdot \text{TAO reserve}}{w_2 \cdot \text{alpha reserve}}
$$

With the default equal weights $w_1 = w_2 = 0.5$ 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
$L = x^{w_1} \cdot y^{w_2}$. Selling TAO into the pool (staking), the alpha
you receive for `Δy` TAO is

$$
\Delta x = x \cdot \left(1 - \left(\tfrac{y}{y+\Delta y}\right)^{w_2/w_1}\right)
$$

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.

<StakingPoolSwapDemo />

The math lives in `pallets/swap/src/pallet/balancer.rs`; quotes over RPC use
exactly the same path ([`quote-stake`](/docs/query/quote-stake),
[`quote-unstake`](/docs/query/quote-unstake)), so a quote is a faithful
simulation, not an estimate.

## What a stake operation does

[`add-stake`](/docs/tx/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:

<StakingFlowDiagram />

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`](/docs/tx/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`](/docs/tx/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`](/docs/tx/add-stake-limit),
[`remove-stake-limit`](/docs/tx/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`).

<StakingSlippageLimit />

To turn a tolerance into a `limit_price`: staking, ceiling = spot × (1 +
tolerance); unstaking, floor = spot × (1 − tolerance). Read spot with
[`alpha-price`](/docs/query/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](/docs/concepts/advanced#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.

<MevShieldTimeline />

```python
await client.submit_shielded(
sub.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](/docs/guides/staking#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](/docs/concepts/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

```bash
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 exit
```

Spot valuation of a position (`alpha × price`,
[`stake-value-for-coldkey`](/docs/query/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.
Loading
Loading