Conviction and locked stake
The locked stake feature lets coldkey holders lock alpha stake to a specific hotkey on a subnet. Locked stake builds conviction, a score that grows over time toward the locked amount. Conviction provides a public, on-chain signal of long-term commitment that cannot be silently reversed.
Conviction provides information about subnet owners and other large investors in a subnet. A subnet owner whose alpha is locked has made a cryptographic commitment: unwinding a large position requires switching the lock to decaying mode and then waiting through an exponential decay period before the stake is gone. This gives other stakers advance warning before any large exit completes.
Locking stake binds a specific amount of a coldkey's staked alpha on a subnet to a specific delegate (stake recipient) hotkey.
The lock code ensures that total alpha staked by the coldkey on that subnet cannot decrease below the locked amount. Everything above the locked amount is freely unstakable.
The coldkey can also continue to stake additional alpha at any time: the lock only blocks the staked balance from dropping below the locked amount.
Conviction increases over time toward the amount of locked stake, following an exponential curve so it slows as it approaches the limit value of the locked amount.
Decaying and perpetual modes
By default, the locked amount decreases or 'decays' over time along an exponential curve, freeing up more of the originally locked amount to potentially be unstaked.
Because conviction will rise toward the locked amount, while the locked amount itself falls, over time, conviction will peak somewhere in the middle and then start to fall again.
The locked amount reaches zero (freeing all stake) with no explicit action needed.
A locked amount can also be set to perpetual so that it will never decreas.
The mode, decaying or perpetual, is per-coldkey per-subnet and can be changed at any time. Switching from perpetual to decaying initiates the decay process immediately from the current locked mass.
One lock per coldkey per subnet is enforced. If a lock already exists for a coldkey on a subnet, additional lock_stake calls top up the locked amount (provided the hotkey matches the existing lock).
The conviction score
The conviction score grows over time, from zero toward the locked amount. In perpetual mode it follows an exponential curve:
where:
- : conviction at last update
- : conviction now
- : locked mass (alpha units)
- : blocks elapsed since last update
- : maturity time constant (
MaturityRate, a governance-settable on-chain value; query the chain for the current value)
In decaying mode, both the locked mass and conviction decay toward zero, but they follow different curves. Starting from a fresh lock (), conviction first rises as the lock accumulates maturation time, then falls as the mass erodes. The formula (when UnlockRate = MaturityRate = τ, the default) is:
Switching to perpetual mode stops the mass decay and allows conviction to grow toward the full locked amount.
90% conviction (perpetual mode) is reached at approximately blocks. At one time constant , conviction is at 63.2% of locked mass.
MaturityRate and UnlockRate are governance-settable on-chain storage values. The specific block counts and day estimates depend on the current on-chain values. Query api.query.subtensorModule.maturityRate() and api.query.subtensorModule.unlockRate() on the live chain before relying on any specific number.
Perpetual mode (fresh lock of 100 alpha, ):
| Elapsed | Locked mass | Conviction |
|---|---|---|
| 0 | 100 | 0 |
| 0.5τ | 100 | 39.3 |
| 1τ | 100 | 63.2 |
| 2τ | 100 | 86.5 |
| 2.3τ | 100 | ~90 |
| 3τ | 100 | 95.0 |
Conviction closes in on the locked mass; maximum conviction equals the locked mass.
See how it's calculated
Closing a gap between current conviction and the target (locked mass):
gap = m - c0
c1 = m - gap × exp(-dt/τ)
exp(-dt/τ) is the fraction of the gap that remains after dt blocks.
dt = 0→exp(0) = 1→ gap unchanged → c1 = c0 ✓dt = τ→exp(-1) ≈ 0.368→ 36.8% of gap remains → 63.2% closeddt → ∞→exp(-∞) = 0→ gap gone → c1 = m ✓
Starting from c0 = 0 (fresh lock of 100 alpha, perpetual mode):
gap = 100
at τ: c1 = 100 - 100 × 0.368 = 63.2
at 2τ: c1 = 100 - 100 × 0.135 = 86.5
at 3τ: c1 = 100 - 100 × 0.050 = 95.0
Conviction is always closing in on m, getting closer every block, never quite arriving.
Decaying mode (fresh lock of 100 alpha, , UnlockRate = MaturityRate = τ):
| Elapsed | Locked mass | Conviction |
|---|---|---|
| 0 | 100 | 0 |
| 0.5τ | 60.7 | 30.3 |
| 1τ | 36.8 | 36.8 (peak) |
| 2τ | 13.5 | 27.1 |
| 3τ | 5.0 | 14.9 |
Conviction peaks at ~36.8% of the original locked mass at elapsed time = τ. After that both values fall toward zero. Note that once elapsed time exceeds τ, conviction exceeds the remaining locked mass; it reflects accumulated commitment, not just current holdings. Topping up an existing lock adds to locked mass immediately, conviction continuing from its current value.
See how it's calculated
When UnlockRate = MaturityRate = τ, conviction is the accumulated area under the decaying lock curve:
c1 = exp(-dt/τ) × (c0 + m × dt/τ)
m1 = m × exp(-dt/τ)
Starting from c0 = 0 (fresh lock of 100 alpha, decaying mode):
at 0.5τ: m1 = 60.7, c1 = 100 × 0.5 × exp(-0.5) = 30.3
at τ: m1 = 36.8, c1 = 100 × 1.0 × exp(-1) = 36.8 ← peak
at 2τ: m1 = 13.5, c1 = 100 × 2.0 × exp(-2) = 27.1
at 3τ: m1 = 5.0, c1 = 100 × 3.0 × exp(-3) = 14.9
The term (dt/τ) × exp(-dt/τ) is maximized at dt = τ (value = 1/e ≈ 0.368). Conviction represents accumulated commitment, not current holdings; after τ has elapsed, conviction exceeds the remaining locked mass.
Subnet owner auto-locking
When a subnet owner receives their distribution cut each epoch, it is automatically locked to the subnet owner's hotkey by default. If the owner already has a lock, the auto-lock tops it up using the existing lock's hotkey. If no lock exists, the auto-lock targets the subnet owner's hotkey.
Auto-locking is enabled per-subnet by default and can be disabled by the subnet owner or root via sudo_set_owner_cut_auto_lock_enabled (admin-utils pallet).
Any lock targeting the subnet owner's hotkey instantly matures conviction to the locked amount. This applies to any coldkey locking to the subnet owner's hotkey, not just the owner locking to themselves. The trigger is the target hotkey, not the locking coldkey.
Key swap behavior
Hotkey swap (system-level): When a hotkey is swapped via btcli wallet swap-hotkey, all locks targeting the old hotkey are transferred to the new hotkey. Conviction is not reset, because the same coldkey owns both hotkeys.
Coldkey swap: A coldkey swap fails if the destination coldkey already has active locked mass on any subnet. The swap succeeds if the destination coldkey only has expired or zero-mass locks.
Transferring locked stake
When stake is moved to another coldkey within the same subnet, lock obligations follow the alpha proportionally. The runtime resolves how much of the transfer carries lock state:
- Freely available alpha transfers first: alpha above the locked amount moves with no lock implications.
- Locked alpha is drawn next: if the transfer exceeds freely available alpha, the remainder comes from locked mass. Conviction transfers proportionally with the locked amount. This step fails with
LockHotkeyMismatchif the destination coldkey already has a lock pointing at a different hotkey.
Cross-subnet moves are different: moving stake between subnets goes through unstake → TAO transfer → restake, which must satisfy the lock constraint. You cannot move locked alpha across subnets directly.
Querying conviction
In Polkadot.js, go to Developer → RPC calls and select the stakeInfo module to call getColdkeyLock. For the other two methods, go to Developer → Runtime calls and select the stakeInfoRuntimeApi module.
| Polkadot.js module | Method | Returns |
|---|---|---|
RPC calls → stakeInfo | getColdkeyLock(coldkey, netuid) | The current LockState for this coldkey on netuid, rolled forward to the current block, or None if no lock exists |
Runtime calls → stakeInfoRuntimeApi | getHotkeyConviction(hotkey, netuid) | Current total conviction for hotkey on netuid, summed over all coldkeys that have locked to it |
Runtime calls → stakeInfoRuntimeApi | getMostConvictedHotkeyOnSubnet(netuid) | The hotkey with the highest conviction on netuid, or None if no locks exist |
Conviction is a rolling value: querying at different blocks yields different results as time passes and the exponential evolves.