# For agents (/docs/agents) The SDK and CLI are built to be driven by agents. Every operation is discoverable at runtime with a JSON schema, every mutation can be previewed before it spends anything, every failure returns a machine-readable code with a remediation hint, and a `Policy` can hard-bound what a session is allowed to do. Nothing here requires parsing human prose. ## Discover the operations [#discover-the-operations] ```bash btcli tools # JSON: every transaction op + summary, description, signer, input schema ``` ```python import bittensor as sub sub.intents.list_tools() # same catalog, as Python dicts sub.reads.list_reads() # every read: name, params, docs, category ``` The same catalogs are published statically by these docs: [`/catalog/intents.json`](/catalog/intents.json), [`/catalog/reads.json`](/catalog/reads.json), [`/catalog/errors.json`](/catalog/errors.json). Each entry includes a `docs_url` pointing at its reference page, and every docs page is fetchable as raw markdown (see the copy-markdown link on any page, or [`/llms-full.txt`](/llms-full.txt) for everything at once). ## Execute by name [#execute-by-name] An agent never needs to import intent classes — build and execute by op name with a plain dict, validated against the schema: ```python async with sub.Client("finney") as client: result = await client.execute_tool( "transfer", {"dest_ss58": "5F...", "amount_tao": 1.0}, wallet ) ``` On the CLI, every op in the catalog is `btcli tx ` (underscores become dashes) and every read is `btcli query `. ## Preview before you spend [#preview-before-you-spend] `plan` (SDK) and `--dry-run` (CLI) run the full pipeline — fee estimation, predicted effects, warnings, policy check — without submitting: ```python plan = await client.plan(intent, wallet) plan.fee # estimated fee plan.effects # list[str]: what this will do plan.warnings # non-fatal cautions (e.g. dust amounts) plan.ok # would policy allow it? ``` ## Bound the blast radius with Policy [#bound-the-blast-radius-with-policy] Attach a `Policy` to the client and every execution — by class, by name, or raw — passes through one choke point: ```python policy = sub.Policy(max_spend_tao=5.0, allowed_netuids=[1, 2]) async with sub.Client("finney", policy=policy) as client: ... # anything exceeding the caps raises PolicyError at execute time ``` Operations whose cost cannot be bounded ahead of time (e.g. subnet registration, whose price floats) are blocked by a spend cap until it is raised — the safe default. Raw calls are refused unless the policy sets `allow_raw_calls=True`. ## Branch on failures, don't parse them [#branch-on-failures-dont-parse-them] Every write returns an `ExtrinsicResult`; failures carry a semantic [`ErrorCode`](/docs/errors) and a remediation hint: ```python result = await client.execute(intent, wallet) if not result.success: match result.error.code: case sub.ErrorCode.RATE_LIMITED: ... # wait and retry case sub.ErrorCode.INSUFFICIENT_BALANCE: ... # reduce amount case _: log(result.error.remediation) # actionable next step, always present ``` ## CLI rules for non-interactive use [#cli-rules-for-non-interactive-use] * `--json` on any command produces machine-readable output. * `--yes` skips confirmation prompts. Without it, a non-interactive session is **declined, not blocked** — the CLI never hangs waiting for input it can't get. * `--dry-run` previews any `tx` command. * Configuration comes from flags, `BT_*` environment variables, or `btcli config` — highest to lowest precedence. ## The escape hatch [#the-escape-hatch] Anything on chain that no intent wraps (deprecated, root/admin-only, or off-chain-signed calls) is still reachable: ```python call = sub.calls.Commitments.set_commitment(netuid=1, info={...}) await client.submit_call(call, wallet, signer="hotkey") ``` And generic accessors cover any storage item, constant, or runtime API in the chain metadata: ```python tempo = await client.query(sub.storage.SubtensorModule.Tempo, [1]) ed = await client.constant(sub.constants.Balances.ExistentialDeposit) ``` # The CLI (/docs/cli) The `btcli` command is the SDK with a shell in front: every read in the catalog is `btcli query `, every transaction is `btcli tx `, and both are generated from the same registries as the Python API, so they cannot diverge from it. ## Configuration [#configuration] Set connection and identity once, override per-command as needed: ```bash btcli config set network finney btcli config set wallet my_coldkey btcli config get ``` Precedence, highest first: **CLI flag > environment variable > config file > built-in default**. | Option | Env | Meaning | | ----------------------- | ------------------ | -------------------------------------------------- | | `--network`, `-n` | `BT_NETWORK` | `finney` / `test` / `local`, or a `ws://` endpoint | | `--wallet`, `-w` | `BT_WALLET` | coldkey wallet name | | `--wallet-hotkey`, `-H` | `BT_WALLET_HOTKEY` | hotkey name within the wallet | | `--wallet-path` | `BT_WALLET_PATH` | wallet directory | | `--json` | | machine-readable JSON output | | `--yes`, `-y` | | skip confirmation prompts | | `--dry-run` | | preview a mutation without submitting | | `--quiet`, `-q` | | suppress informational output | ## Command groups [#command-groups] * `btcli query ` — every [read](/docs/query); `query --help` lists them grouped by topic. * `btcli tx ` — every [transaction](/docs/tx); `tx --help` lists them grouped by pallet. All support `--dry-run`, `--yes`, and `--proxy-for`. * Familiar workflows also have hand-written groups that wrap the same machinery: `wallet`, `stake`, `subnets`, `weights`, `axon`, `proxy`, `multisig`, `crowd`, `lock`, `timelock`, `config`. * `btcli tools` — the JSON catalog of every transaction op with schemas, for agents. ## Interactive by default, safe when it can't be [#interactive-by-default-safe-when-it-cant-be] Run a `tx` command with required options missing and the CLI prompts for them, then shows the plan and asks for confirmation. In a non-interactive session (no TTY) without `--yes`, the command is **declined rather than left hanging** — automation never blocks on a prompt it can't answer. ## Address arguments [#address-arguments] Any address option (`--dest`, `--hotkey`, `--coldkey`, ...) accepts a raw ss58 address, an address-book or proxy-book name, or a local key name (`HOTKEY`, `WALLET/HOTKEY`, or a wallet name for coldkey options). `--hotkey` / `--coldkey` fall back to the configured wallet when omitted; destination options never default. ## Shell completion [#shell-completion] Completion scripts for bash, zsh, and fish ship in `completions/` and install into the standard system locations. If your shell doesn't auto-load them (e.g. a venv install), source the script from your rc file, or run `btcli --install-completion`. # Errors (/docs/errors) A failed `execute` returns an `ExtrinsicResult` whose `error` has a semantic `code` (branch on it) and a `remediation` hint (what to try next). Every chain error name is deliberately classified — a CI gate keeps the mapping complete in both directions. The machine-readable version is at [`/catalog/errors.json`](/catalog/errors.json). ## Error codes [#error-codes] ### `insufficient_balance` [#insufficient_balance] Fund the signing account or reduce the amount; check with `btcli wallet balance` ### `insufficient_liquidity` [#insufficient_liquidity] The pool cannot absorb this trade; reduce the amount or split it into smaller steps ### `rate_limited` [#rate_limited] Wait for the rate-limit window to pass, then retry ### `not_registered` [#not_registered] Register the hotkey on this subnet first with `btcli subnets register` ### `not_authorized` [#not_authorized] Sign with the key or origin that owns the target object, then retry ### `already_exists` [#already_exists] The object or state already exists; treat the goal as met or pick a different target ### `not_found` [#not_found] The referenced object is not on-chain; check the identifier ### `subnet_not_exists` [#subnet_not_exists] Use an existing netuid; `btcli subnets list` shows valid ones ### `subtoken_disabled` [#subtoken_disabled] The subnet is not active yet; wait for start\_call ### `disabled` [#disabled] This call or feature is switched off on this network ### `too_early` [#too_early] The required window has not opened yet; wait some blocks and retry ### `expired` [#expired] The window has closed; restart the flow with fresh state ### `limit_exceeded` [#limit_exceeded] A chain-side capacity limit was hit; reduce the size or count of the request ### `unit_mismatch` [#unit_mismatch] Match the Balance netuid to the operation's currency ### `invalid_argument` [#invalid_argument] Check the argument values against the operation schema ### `policy_violation` [#policy_violation] The action exceeds a configured safety policy ### `internal` [#internal] A chain-side invariant failed; nothing to fix client-side — report it if it persists ### `unknown` [#unknown] Inspect the message for details ## Chain error classification [#chain-error-classification] The exact chain error name (from the extrinsic receipt) maps to a code: | Chain error | Code | | ------------------------------------------------ | ------------------------ | | `AccountNotAllowedCommit` | `not_authorized` | | `AccountRejectsLockedAlpha` | `invalid_argument` | | `ActiveLockExists` | `already_exists` | | `ActivityCutoffFactorMilliOutOfBounds` | `invalid_argument` | | `ActivityCutoffTooLow` | `invalid_argument` | | `AddStakeBurnRateLimitExceeded` | `rate_limited` | | `AdminActionProhibitedDuringWeightsWindow` | `too_early` | | `AllNetworksInImmunity` | `too_early` | | `AlphaHighTooLow` | `invalid_argument` | | `AlphaLowOutOfRange` | `invalid_argument` | | `AlreadyApproved` | `already_exists` | | `AlreadyDeposited` | `already_exists` | | `AlreadyFinalized` | `already_exists` | | `AlreadyNoted` | `already_exists` | | `AlreadyStored` | `already_exists` | | `AmountTooLow` | `invalid_argument` | | `AnnouncedColdkeyHashDoesNotMatch` | `invalid_argument` | | `AnnouncementDepositInvariantViolated` | `internal` | | `ArithmeticOverflow` | `internal` | | `AutoEpochAlreadyImminent` | `already_exists` | | `BadEncKeyLen` | `invalid_argument` | | `BalanceLow` | `insufficient_balance` | | `BalanceWithdrawalError` | `insufficient_balance` | | `BeneficiaryDoesNotOwnHotkey` | `not_authorized` | | `BlockDurationTooLong` | `invalid_argument` | | `BlockDurationTooShort` | `invalid_argument` | | `BondsMovingAverageMaxReached` | `limit_exceeded` | | `CallDisabled` | `disabled` | | `CallFiltered` | `not_authorized` | | `CallUnavailable` | `not_found` | | `CanNotSetRootNetworkWeights` | `invalid_argument` | | `CannotAddSelfAsDelegateDependency` | `invalid_argument` | | `CannotAffordLockCost` | `insufficient_balance` | | `CannotBurnOrRecycleOnRootSubnet` | `invalid_argument` | | `CannotEndInPast` | `invalid_argument` | | `CannotReleaseYet` | `too_early` | | `CannotUseSystemAccount` | `not_authorized` | | `CapNotRaised` | `too_early` | | `CapRaised` | `limit_exceeded` | | `CapTooLow` | `invalid_argument` | | `ChainIdMismatch` | `invalid_argument` | | `ChangePending` | `already_exists` | | `ChildParentInconsistency` | `invalid_argument` | | `CodeInUse` | `invalid_argument` | | `CodeInfoNotFound` | `not_found` | | `CodeNotFound` | `not_found` | | `CodeRejected` | `invalid_argument` | | `CodeTooLarge` | `limit_exceeded` | | `ColdKeyAlreadyAssociated` | `already_exists` | | `ColdkeySwapAlreadyDisputed` | `already_exists` | | `ColdkeySwapAnnounced` | `already_exists` | | `ColdkeySwapAnnouncementNotFound` | `not_found` | | `ColdkeySwapClearTooEarly` | `too_early` | | `ColdkeySwapDisputed` | `not_authorized` | | `ColdkeySwapReannouncedTooEarly` | `too_early` | | `ColdkeySwapTooEarly` | `too_early` | | `CommitRevealDisabled` | `disabled` | | `CommitRevealEnabled` | `disabled` | | `CommittingWeightsTooFast` | `rate_limited` | | `ContractNotFound` | `not_found` | | `ContractReverted` | `internal` | | `ContractTrapped` | `internal` | | `ContributionPeriodEnded` | `expired` | | `ContributionPeriodNotEnded` | `too_early` | | `ContributionTooLow` | `invalid_argument` | | `CreateOriginNotAllowed` | `not_authorized` | | `CurrencyError` | `internal` | | `DeadAccount` | `not_found` | | `DecodingFailed` | `invalid_argument` | | `DelegateDependencyAlreadyExists` | `already_exists` | | `DelegateDependencyNotFound` | `not_found` | | `DelegateTakeTooHigh` | `invalid_argument` | | `DelegateTakeTooLow` | `invalid_argument` | | `DelegateTxRateLimitExceeded` | `rate_limited` | | `DeltaZero` | `invalid_argument` | | `DepositCannotBeWithdrawn` | `invalid_argument` | | `DepositTooLow` | `invalid_argument` | | `Deprecated` | `disabled` | | `DisabledTemporarily` | `disabled` | | `DrandConnectionFailure` | `internal` | | `Duplicate` | `already_exists` | | `DuplicateChild` | `invalid_argument` | | `DuplicateContract` | `already_exists` | | `DuplicateOffenceReport` | `already_exists` | | `DuplicateOrderInBatch` | `invalid_argument` | | `DuplicateUids` | `invalid_argument` | | `DynamicTempoBlockedByCommitReveal` | `disabled` | | `Entered` | `already_exists` | | `EpochTriggerAlreadyPending` | `already_exists` | | `EvmKeyAssociateRateLimitExceeded` | `rate_limited` | | `ExistentialDeposit` | `insufficient_balance` | | `ExistingVestingSchedule` | `already_exists` | | `Exited` | `already_exists` | | `ExpectedBeneficiaryOrigin` | `not_authorized` | | `Expendability` | `insufficient_balance` | | `ExpiredWeightCommit` | `expired` | | `FailedToExtractRuntimeVersion` | `invalid_argument` | | `FailedToSchedule` | `internal` | | `FaucetDisabled` | `disabled` | | `FeeOverflow` | `internal` | | `FeeRateTooHigh` | `invalid_argument` | | `FirstEmissionBlockNumberAlreadySet` | `already_exists` | | `GasLimitTooHigh` | `invalid_argument` | | `GasLimitTooLow` | `invalid_argument` | | `GasPriceTooLow` | `invalid_argument` | | `HotKeyAccountNotExists` | `not_registered` | | `HotKeyAlreadyDelegate` | `already_exists` | | `HotKeyAlreadyRegisteredInSubNet` | `already_exists` | | `HotKeyNotRegisteredInNetwork` | `not_registered` | | `HotKeyNotRegisteredInSubNet` | `not_registered` | | `HotKeySetTxRateLimitExceeded` | `rate_limited` | | `HotKeySwapOnSubnetIntervalNotPassed` | `rate_limited` | | `IncorrectCommitRevealVersion` | `invalid_argument` | | `IncorrectPartialFillAmount` | `invalid_argument` | | `IncorrectWeightVersionKey` | `invalid_argument` | | `Indeterministic` | `invalid_argument` | | `InputForwarded` | `invalid_argument` | | `InputLengthsUnequal` | `invalid_argument` | | `InsufficientBalance` | `insufficient_balance` | | `InsufficientInputAmount` | `invalid_argument` | | `InsufficientLiquidity` | `insufficient_liquidity` | | `InsufficientStakeForLock` | `insufficient_balance` | | `InvalidCallFlags` | `invalid_argument` | | `InvalidChainId` | `invalid_argument` | | `InvalidChild` | `invalid_argument` | | `InvalidChildkeyTake` | `invalid_argument` | | `InvalidCrowdloanId` | `not_found` | | `InvalidDerivedAccount` | `invalid_argument` | | `InvalidDerivedAccountId` | `invalid_argument` | | `InvalidDifficulty` | `invalid_argument` | | `InvalidEquivocationProof` | `invalid_argument` | | `InvalidFinalizationConfig` | `invalid_argument` | | `InvalidIdentity` | `invalid_argument` | | `InvalidIpAddress` | `invalid_argument` | | `InvalidIpType` | `invalid_argument` | | `InvalidKeyOwnershipProof` | `invalid_argument` | | `InvalidLeaseBeneficiary` | `invalid_argument` | | `InvalidLiquidityValue` | `invalid_argument` | | `InvalidNonce` | `invalid_argument` | | `InvalidNumRootClaim` | `invalid_argument` | | `InvalidOrigin` | `not_authorized` | | `InvalidPort` | `invalid_argument` | | `InvalidRecoveredPublicKey` | `invalid_argument` | | `InvalidRevealCommitHashNotMatch` | `invalid_argument` | | `InvalidRevealRound` | `expired` | | `InvalidRootClaimThreshold` | `invalid_argument` | | `InvalidRoundNumber` | `invalid_argument` | | `InvalidSchedule` | `invalid_argument` | | `InvalidSeal` | `invalid_argument` | | `InvalidSignature` | `invalid_argument` | | `InvalidSpecName` | `invalid_argument` | | `InvalidSubnetNumber` | `invalid_argument` | | `InvalidTickRange` | `invalid_argument` | | `InvalidValue` | `invalid_argument` | | `InvalidVotingPowerEmaAlpha` | `invalid_argument` | | `InvalidWorkBlock` | `invalid_argument` | | `IssuanceDeactivated` | `disabled` | | `LeaseCannotEndInThePast` | `invalid_argument` | | `LeaseDoesNotExist` | `not_found` | | `LeaseHasNoEndBlock` | `invalid_argument` | | `LeaseHasNotEnded` | `too_early` | | `LeaseNetuidNotFound` | `not_found` | | `LimitOrdersDisabled` | `disabled` | | `LiquidAlphaDisabled` | `disabled` | | `LiquidityRestrictions` | `insufficient_balance` | | `LockHotkeyMismatch` | `invalid_argument` | | `MaxAllowedUIdsLessThanCurrentUIds` | `invalid_argument` | | `MaxAllowedUidsGreaterThanDefaultMaxAllowedUids` | `invalid_argument` | | `MaxAllowedUidsLessThanMinAllowedUids` | `invalid_argument` | | `MaxCallDepthReached` | `limit_exceeded` | | `MaxContributionReached` | `limit_exceeded` | | `MaxContributorsReached` | `limit_exceeded` | | `MaxDelegateDependenciesReached` | `limit_exceeded` | | `MaxValidatorsLargerThanMaxUIds` | `invalid_argument` | | `MaxWeightExceeded` | `limit_exceeded` | | `MaxWeightTooLow` | `invalid_argument` | | `MaximumContributionTooLow` | `invalid_argument` | | `MechanismDoesNotExist` | `subnet_not_exists` | | `MigrationInProgress` | `too_early` | | `MinAllowedUidsGreaterThanCurrentUids` | `invalid_argument` | | `MinAllowedUidsGreaterThanMaxAllowedUids` | `invalid_argument` | | `MinimumContributionTooHigh` | `invalid_argument` | | `MinimumContributionTooLow` | `invalid_argument` | | `MinimumThreshold` | `invalid_argument` | | `MultiBlockMigrationsOngoing` | `too_early` | | `Named` | `invalid_argument` | | `NeedWaitingMoreBlocksToStarCall` | `too_early` | | `NegativeSigmoidSteepness` | `invalid_argument` | | `NetworkTxRateLimitExceeded` | `rate_limited` | | `NeuronNoValidatorPermit` | `not_authorized` | | `NewColdKeyIsHotkey` | `invalid_argument` | | `NewHotKeyIsSameWithOld` | `invalid_argument` | | `NewHotKeyNotCleanForRootSwap` | `invalid_argument` | | `NoApprovalsNeeded` | `invalid_argument` | | `NoChainExtension` | `disabled` | | `NoContribution` | `not_found` | | `NoDeposit` | `not_found` | | `NoExistingLock` | `not_found` | | `NoMigrationPerformed` | `invalid_argument` | | `NoNeuronIdAvailable` | `limit_exceeded` | | `NoPermission` | `not_authorized` | | `NoSelfProxy` | `invalid_argument` | | `NoTimepoint` | `invalid_argument` | | `NoWeightsCommitFound` | `not_found` | | `NonAssociatedColdKey` | `not_authorized` | | `NonDefaultComposite` | `invalid_argument` | | `NonZeroRefCount` | `invalid_argument` | | `NoneValue` | `not_found` | | `NotAllowed` | `not_authorized` | | `NotAuthorized` | `not_authorized` | | `NotConfigured` | `disabled` | | `NotEnoughAlphaOutToRecycle` | `insufficient_liquidity` | | `NotEnoughBalanceToPaySwapColdKey` | `insufficient_balance` | | `NotEnoughBalanceToPaySwapHotKey` | `insufficient_balance` | | `NotEnoughBalanceToStake` | `insufficient_balance` | | `NotEnoughStake` | `insufficient_balance` | | `NotEnoughStakeToSetChildkeys` | `insufficient_balance` | | `NotEnoughStakeToSetWeights` | `insufficient_balance` | | `NotEnoughStakeToWithdraw` | `insufficient_balance` | | `NotFound` | `not_found` | | `NotNoted` | `not_found` | | `NotOwner` | `not_authorized` | | `NotPermittedOnRootSubnet` | `invalid_argument` | | `NotProxy` | `not_authorized` | | `NotReadyToDissolve` | `too_early` | | `NotRequested` | `not_found` | | `NotRootSubnet` | `invalid_argument` | | `NotSubnetOwner` | `not_authorized` | | `NothingAuthorized` | `not_found` | | `OrderAlreadyProcessed` | `already_exists` | | `OrderCancelled` | `expired` | | `OrderExpired` | `expired` | | `OrderNetUidMismatch` | `invalid_argument` | | `OutOfBounds` | `invalid_argument` | | `OutOfGas` | `limit_exceeded` | | `OutOfTransientStorage` | `limit_exceeded` | | `OutputBufferTooSmall` | `invalid_argument` | | `Overflow` | `internal` | | `POWRegistrationDisabled` | `disabled` | | `PalletHotkeyNotRegistered` | `not_registered` | | `PartialFillsNotEnabled` | `disabled` | | `PauseFailed` | `invalid_argument` | | `PaymentOverflow` | `internal` | | `PreLogExists` | `invalid_argument` | | `PriceConditionNotMet` | `too_early` | | `PriceLimitExceeded` | `insufficient_liquidity` | | `ProportionOverflow` | `invalid_argument` | | `PulseVerificationError` | `invalid_argument` | | `RandomSubjectTooLong` | `limit_exceeded` | | `ReentranceDenied` | `invalid_argument` | | `Reentrancy` | `internal` | | `RegistrationNotPermittedOnRootSubnet` | `invalid_argument` | | `RegistrationPriceLimitExceeded` | `limit_exceeded` | | `RelayerMissMatch` | `invalid_argument` | | `RelayerRequiredForPartialFill` | `invalid_argument` | | `Requested` | `invalid_argument` | | `RequireSudo` | `not_authorized` | | `RescheduleNoChange` | `invalid_argument` | | `ReservesOutOfBalance` | `insufficient_liquidity` | | `ReservesTooLow` | `insufficient_liquidity` | | `ResumeFailed` | `invalid_argument` | | `RevealPeriodTooLarge` | `invalid_argument` | | `RevealPeriodTooSmall` | `invalid_argument` | | `RevealTooEarly` | `too_early` | | `RootNetUidNotAllowed` | `invalid_argument` | | `RootNetworkDoesNotExist` | `subnet_not_exists` | | `SameAutoStakeHotkeyAlreadySet` | `already_exists` | | `SameNetuid` | `invalid_argument` | | `SenderInSignatories` | `invalid_argument` | | `ServingRateLimitExceeded` | `rate_limited` | | `SettingWeightsTooFast` | `rate_limited` | | `SignatoriesOutOfOrder` | `invalid_argument` | | `SlippageTooHigh` | `insufficient_liquidity` | | `SpaceLimitExceeded` | `rate_limited` | | `SpecVersionNeedsToIncrease` | `invalid_argument` | | `StakeTooLowForRoot` | `insufficient_balance` | | `StakeUnavailable` | `insufficient_balance` | | `StakingRateLimitExceeded` | `rate_limited` | | `StateChangeDenied` | `not_authorized` | | `StorageDepositLimitExhausted` | `limit_exceeded` | | `StorageDepositNotEnoughFunds` | `insufficient_balance` | | `StorageOverflow` | `internal` | | `SubNetRegistrationDisabled` | `disabled` | | `SubnetDoesNotExist` | `subnet_not_exists` | | `SubnetLimitReached` | `limit_exceeded` | | `SubnetNotExists` | `subnet_not_exists` | | `SubtokenDisabled` | `subtoken_disabled` | | `SwapInputTooLarge` | `insufficient_liquidity` | | `SwapReturnedZero` | `insufficient_liquidity` | | `SymbolAlreadyInUse` | `already_exists` | | `SymbolDoesNotExist` | `not_found` | | `TargetBlockNumberInPast` | `invalid_argument` | | `TempoOutOfBounds` | `invalid_argument` | | `TerminatedInConstructor` | `internal` | | `TerminatedWhileReentrant` | `invalid_argument` | | `TooBig` | `limit_exceeded` | | `TooFew` | `invalid_argument` | | `TooFewSignatories` | `invalid_argument` | | `TooMany` | `limit_exceeded` | | `TooManyCalls` | `limit_exceeded` | | `TooManyChildren` | `limit_exceeded` | | `TooManyFieldsInCommitmentInfo` | `limit_exceeded` | | `TooManyFreezes` | `limit_exceeded` | | `TooManyHolds` | `limit_exceeded` | | `TooManyPendingExtrinsics` | `limit_exceeded` | | `TooManyRegistrationsThisBlock` | `rate_limited` | | `TooManyRegistrationsThisInterval` | `rate_limited` | | `TooManyReserves` | `limit_exceeded` | | `TooManySignatories` | `limit_exceeded` | | `TooManyTopics` | `limit_exceeded` | | `TooManyUIDsPerMechanism` | `limit_exceeded` | | `TooManyUnrevealedCommits` | `limit_exceeded` | | `TooSoon` | `rate_limited` | | `TransactionMustComeFromEOA` | `not_authorized` | | `TransactorAccountShouldBeHotKey` | `not_authorized` | | `TransferDisallowed` | `disabled` | | `TransferFailed` | `insufficient_balance` | | `TrimmingWouldExceedMaxImmunePercentage` | `limit_exceeded` | | `TxChildkeyTakeRateLimitExceeded` | `rate_limited` | | `TxRateLimitExceeded` | `rate_limited` | | `UidMapCouldNotBeCleared` | `internal` | | `UidVecContainInvalidOne` | `invalid_argument` | | `UidsLengthExceedUidsInSubNet` | `limit_exceeded` | | `UnableToRecoverPublicKey` | `invalid_argument` | | `Unannounced` | `too_early` | | `Unauthorized` | `not_authorized` | | `Undefined` | `internal` | | `Underflow` | `internal` | | `UnexpectedTimepoint` | `invalid_argument` | | `UnexpectedUnreserveLeftover` | `internal` | | `UnlockAmountTooHigh` | `insufficient_balance` | | `Unproxyable` | `not_authorized` | | `Unreachable` | `internal` | | `UnverifiedPulse` | `invalid_argument` | | `ValueNotInBounds` | `invalid_argument` | | `ValueTooLarge` | `limit_exceeded` | | `VestingBalance` | `insufficient_balance` | | `VotingPowerTrackingNotEnabled` | `disabled` | | `WeightExceedsAbsoluteMax` | `limit_exceeded` | | `WeightVecLengthIsLow` | `invalid_argument` | | `WeightVecNotEqualSize` | `invalid_argument` | | `WithdrawFailed` | `insufficient_balance` | | `WrongTimepoint` | `invalid_argument` | | `XCMDecodeFailed` | `invalid_argument` | | `ZeroBalanceAfterWithdrawn` | `insufficient_balance` | | `ZeroShareInBatch` | `invalid_argument` | # Bittensor Documentation (/docs) Bittensor is an open network where independent **subnets** produce digital commodities — compute, inference, storage, prediction — and the chain pays participants in its token, **TAO**, in proportion to the value they contribute. **Miners** produce the commodity, **validators** score the miners, **subnet creators** define the incentive mechanism, and **stakers** back validators with TAO. These docs cover the `bittensor` Python SDK and CLI: one install gives you a library (`import bittensor`) and a command line (`btcli`) that together can perform every user-facing operation on the chain. Both are generated from the chain's own runtime metadata, so they cannot drift from what the chain actually does — and neither can these docs, because the reference section is generated from the same registries. ## Where to go [#where-to-go] ## Install [#install] Requires Python 3.10–3.13. Using [uv](https://docs.astral.sh/uv/): ```bash uv venv && source .venv/bin/activate uv pip install 'bittensor[cli]' ``` This installs both the `btcli` command and the `bittensor` Python package. For the library alone (no terminal-UI dependencies), install plain `bittensor`. ## Machine-readable everything [#machine-readable-everything] If you are an agent (or building one), you never need to scrape these pages: * [`/llms.txt`](/llms.txt) — index of every page; [`/llms-full.txt`](/llms-full.txt) — the entire docs in one file. * Every page is fetchable as raw markdown: append `content.md` under `/llms.mdx/docs/...` (linked from each page). * [`/catalog/intents.json`](/catalog/intents.json) — every transaction with its JSON schema, signer, and summary. * [`/catalog/reads.json`](/catalog/reads.json) — every query with parameters and docs. * [`/catalog/errors.json`](/catalog/errors.json) — every error code with remediation, and the full chain-error classification. * The same catalogs, live from the tool itself: `btcli tools` on the CLI, `sub.intents.list_tools()` in Python. # Quickstart (/docs/quickstart) ## 1. Install [#1-install] Requires Python 3.10–3.13: ```bash uv venv && source .venv/bin/activate uv pip install 'bittensor[cli]' ``` The `[cli]` extra pulls in the terminal UI for the `btcli` command; plain `uv pip install bittensor` installs just the Python library. In anything unattended, pin the exact version and upgrade only to announced releases — see [supply-chain risk](/docs/concepts/wallets#supply-chain-risk). ## 2. Configure once [#2-configure-once] ```bash btcli config set network finney # or `test` for testnet, `local` for a dev node btcli config set wallet my_coldkey btcli config get # show the whole config ``` Precedence, highest first: **CLI flag > environment variable > config file > built-in default**. So `btcli -n test query tx-rate-limit` overrides the configured network for that one call. The environment variables are `BT_NETWORK`, `BT_WALLET`, `BT_WALLET_HOTKEY`, `BT_WALLET_PATH`. ## 3. Create or import a wallet [#3-create-or-import-a-wallet] ```bash btcli wallet create -w my_coldkey # new coldkey + hotkey btcli wallet regen-coldkey -w my_coldkey # import from mnemonic (prompted securely) btcli wallet list ``` A wallet has two keys: the **coldkey** holds funds and signs financial operations; the **hotkey** identifies you on subnets and signs operational calls (weights, serving). See [Wallets and keys](/docs/concepts/wallets). ## 4. Read chain state [#4-read-chain-state] Reads are free and unsigned. On the CLI (add `--json` for machine output): ```bash btcli wallet balance my_coldkey btcli subnets list btcli query metagraph --netuid 1 btcli query --help # all 70+ reads, grouped by topic ``` In Python: ```python import asyncio import bittensor as sub async def main(): async with sub.Client("finney") as client: bal = await client.balances.get("5F...coldkey") subnets = await client.subnets.all() mg = await client.read("metagraph", netuid=1) asyncio.run(main()) ``` There is a synchronous facade too: ```python client = sub.SyncClient("finney") print(client.balances.get("5F...coldkey")) client.close() ``` ## 5. Submit a transaction [#5-submit-a-transaction] Every mutation supports `--dry-run`: it shows the fee, the predicted effects, and any policy verdict without submitting anything. ```bash btcli tx transfer --dest 5F...dest --amount-tao 1.5 --dry-run btcli tx transfer --dest 5F...dest --amount-tao 1.5 -w my_coldkey ``` In Python the same two-step shape is `plan` then `execute`: ```python from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") async with sub.Client("finney") as client: intent = sub.Transfer(dest_ss58="5F...", amount_tao=1.5) plan = await client.plan(intent, wallet) # fee, effects, warnings — nothing submitted result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` ## Next [#next] * [The transaction model](/docs/concepts/transactions) — intents, plan/execute, Policy. * [Money](/docs/concepts/money) — TAO, alpha, and why the SDK refuses to mix them. * [For agents](/docs/agents) — discover and drive every operation programmatically. * [Staking](/docs/guides/staking), [Mining](/docs/guides/mining), [Validating](/docs/guides/validating), [EVM](/docs/guides/evm). # Advanced submission (/docs/concepts/advanced) These modes compose with any intent — they change *how* a transaction is signed and submitted, not *what* it does. ## Proxy: keep the coldkey offline [#proxy-keep-the-coldkey-offline] A registered proxy signs on behalf of the real account, so the coldkey holding the funds never has to be on the machine doing the work: ```python await client.execute(intent, delegate_wallet, proxy_for="5F...real_coldkey") ``` On the CLI, `--proxy-for ` works on every `tx` command (and `--force-proxy-type` requires a specific proxy type). Manage delegations with [`add-proxy`](/docs/tx/add-proxy) / [`remove-proxy`](/docs/tx/remove-proxy), create standalone proxy accounts with [`create-pure-proxy`](/docs/tx/create-pure-proxy), and inspect with the [`proxies`](/docs/query/proxies) read. ### Proxy types [#proxy-types] Each grant is scoped by `proxy_type` — the runtime filters every proxied call against the type's allowlist. Prefer the narrowest type that covers the job: * **Any** — everything the account can do, including transfers. Avoid. * **NonTransfer** — everything *except* balance transfers, stake transfers, and coldkey swaps. Notably it can manage the account's other proxies. * **Transfer** — balance transfers and `transfer_stake` only. **SmallTransfer** is the same but caps each transfer below 0.5 TAO (0.5 alpha for stake transfers). * **Staking** — stake operations only: add / remove / move / swap / unstake-all and their limit variants, plus `set_root_claim_type`. The allowlist does **not** include `Utility.batch_all`, so batching staking calls through a Staking proxy fails with `CallFiltered` — use a NonTransfer proxy for batched staking. * **Registration** — neuron registration calls (`burned_register`, `register`, `register_limit`). * **ChildKeys** — `set_children` and `set_childkey_take`. * **RootClaim** — `claim_root` only. * **Owner** — subnet-owner admin calls; **NonCritical** — everything except registrations, coldkey swaps, and subnet dissolution; **SubnetLeaseBeneficiary** — the lease-scoped subset of owner calls. ### Deposits and limits [#deposits-and-limits] Proxy bookkeeping is paid with **reserved** deposits, not fees: adding a proxy reserves a base of 0.06 TAO plus 0.033 TAO per delegation from the granting account, returned on removal. An account can have at most 20 proxies. Announcements (below) reserve 0.036 TAO base plus 0.068 TAO each, at most 75 pending. ### Delayed proxies: announce, monitor, execute [#delayed-proxies-announce-monitor-execute] A delegation granted with `delay` > 0 cannot act directly. The delegate first announces the *hash* of the intended call (blake2-256 of the SCALE-encoded call), waits out the delay, then executes with [`execute-proxy-announced`](/docs/tx/execute-proxy-announced) — which must rebuild the call with byte-identical parameters, since anything else hashes differently and matches no announcement. During the delay the real account can veto the pending call. Two operational notes. A delay only protects you if you check pending announcements (the `Proxy.Announcements` storage map, via `client.query`) on a schedule *shorter* than the delay — otherwise it is only forensic. And a zero-delay proxy executes with no veto window at all: treat a leaked zero-delay key as immediately spent, and rotate it. The SDK wraps the execute side as an intent; announcing and vetoing have no intent and are reachable as raw calls: ```python # delegate announces a future call await client.submit_call( sub.calls.Proxy.announce(real="5F...real", call_hash="0x..."), delegate_wallet ) # real account vetoes it during the delay await client.submit_call( sub.calls.Proxy.reject_announcement(delegate="5F...delegate", call_hash="0x..."), real_wallet, ) ``` ## Batch: all-or-nothing [#batch-all-or-nothing] Several intents in one atomic extrinsic — either every one lands or none do: ```python await client.execute(sub.Batch(intents=[ {"op": "transfer", "dest_ss58": "5F...", "amount_tao": 1.0}, {"op": "add_stake", "hotkey_ss58": "5F...", "netuid": 1, "amount_tao": 2.0}, ]), wallet) ``` `Batch` wraps `Utility.batch_all`, the atomic variant. The chain also has best-effort `Utility.batch` (stops at the first failure, earlier calls stand) and `Utility.force_batch` (skips failures and continues) — no intent wraps them, but both are reachable as raw calls (below). A batch pays a transaction fee if any inner call is fee-bearing. ## MEV-shielded submission [#mev-shielded-submission] Encrypts the call so the mempool cannot read (and front-run) it; the chain decrypts and applies it validator-side. A mainnet feature: ```python await client.submit_shielded(sub.Transfer(dest_ss58="5F...", amount_tao=1.0), wallet) ``` Under the hood, the intent's call is signed as a complete inner extrinsic, encrypted to the chain's rotating ML-KEM-768 key ([`mev-shield-next-key`](/docs/query/mev-shield-next-key)) with XChaCha20-Poly1305 for the payload, and carried inside `MevShield.submit_encrypted`. The same account signs both, so the outer wrapper takes the account's next nonce and the inner call the one after — the SDK pins both automatically. Both are signed with a short 8-block era; the chain rejects shielded wrappers signed with anything longer, so a stuck submission evicts from the pool within a handful of blocks. The block author decrypts the wrapper and includes the inner extrinsic in the block it builds, where it executes — and reports success or failure — like any normally submitted extrinsic. The wrapper's acceptance is marked by the pallet's `EncryptedSubmitted` event, and a successful submission carries `inner_extrinsic_hash` in the result data so the inner extrinsic can be located on chain. Shielding is for coldkey-signed operations. The chain accepts a shielded wrapper from any signed origin, but the SDK and CLI refuse to shield hotkey-signed extrinsics as policy: the wrapper pays a fee, and funding a hotkey with TAO to cover it defeats the key-separation model. Hotkey calls (weights, serving) are fee-free and gain nothing from shielding anyway. ## Multisig [#multisig] `client.multisig(signatories, threshold)` manages k-of-n accounts; the [`multisig-*`](/docs/tx/multisig-execute) intents approve, execute, and cancel multisig calls, and the [`multisig`](/docs/query/multisig) read inspects pending ones. The CLI's `btcli multisig` group wraps the full flow, printing the co-signer commands after each approval. The first approver of a pending call reserves a deposit — 0.132 TAO base plus 0.032 TAO per unit of threshold — returned when the multisig executes or is cancelled. A multisig can have at most 100 signatories. ## Limit orders [#limit-orders] The chain has a `LimitOrders` pallet: resting orders (limit buy, take-profit, stop-loss) signed off-chain with a limit price and expiry, settled against the subnet's pool by an executor submitting them in batches — each order can restrict which relayer accounts may execute it, and a root-set global toggle enables the pallet. `execute_batched_orders` nets a batch's buys against its sells at the current price, so only the residual amount touches the pool in a single swap — offset-side orders fill internally with no price impact. No intent wraps the pallet; the calls are reachable as raw calls under `sub.calls.LimitOrders.*` (`execute_orders`, `execute_batched_orders`, `cancel_order`, `set_pallet_status`). ## Raw calls: the escape hatch [#raw-calls-the-escape-hatch] Every chain call in the runtime metadata is available under `sub.calls` — even those no intent wraps (deprecated, root/admin-only, or off-chain-signed calls, each recorded with a reason): ```python call = sub.calls.Commitments.set_commitment(netuid=1, info={...}) await client.submit_call(call, wallet, signer="hotkey") ``` Raw calls bypass intent preview, so an active `Policy` refuses them unless it sets `allow_raw_calls=True`. # The client (/docs/concepts/client) `sub.Client` is the async entry point to everything: reads, transactions, and chain metadata. `sub.SyncClient` is a synchronous facade over the same surface. ```python import bittensor as sub async with sub.Client("finney") as client: # "finney" | "test" | "local" | "ws://..." ... client = sub.SyncClient("finney") # same API, blocking client.close() ``` Networks: `finney` is mainnet, `test` is the public testnet, `local` is a dev node at `ws://127.0.0.1:9944`, and any `ws://` / `wss://` endpoint works directly. ## Three levels of reading [#three-levels-of-reading] **Typed conveniences** — curated helpers with rich return types: ```python await client.balances.get("5F...") # Balance await client.subnets.all() # list[SubnetInfo] await client.neurons.all(netuid=1) # list[Neuron] ``` **Named reads** — the full catalog of semantic reads, the same set the CLI's `query` group exposes. One page each under [Queries](/docs/query): ```python mg = await client.read("metagraph", netuid=1) take = await client.read("delegate_take", hotkey_ss58="5F...") client.reads() # the machine-readable catalog ``` **Generic accessors** — anything in the chain's runtime metadata, via generated descriptors. This is the escape hatch when no read wraps what you need: ```python tempo = await client.query(sub.storage.SubtensorModule.Tempo, [1]) pairs = await client.query_map(sub.storage.SubtensorModule.Tempo) ed = await client.constant(sub.constants.Balances.ExistentialDeposit) info = await client.runtime(sub.runtime_api.NeuronInfoRuntimeApi.get_neurons_lite, [1]) ``` The `sub.storage`, `sub.constants`, `sub.runtime_api`, and `sub.calls` modules are generated from chain metadata and cover the entire runtime surface. ## Typed results: the metagraph [#typed-results-the-metagraph] The richest typed result is the metagraph — a whole subnet as one object. `client.subnets.metagraph(netuid)` returns a `Metagraph` (the [`metagraph`](/docs/query/metagraph) named read returns the underlying raw runtime record instead): ```python mg = await client.subnets.metagraph(netuid=1) for n in mg: # iterates neurons, ordered by uid; len(mg) works print(n.uid, n.hotkey, n.incentive) mg.validators # neurons holding a validator permit mg.neuron(5) # by uid (KeyError if unknown) mg.by_hotkey("5F...") # by hotkey, or None mg.hotkeys, mg.coldkeys # parallel address lists ``` Each `MetagraphNeuron` carries the identity columns (`uid`, `hotkey`, `coldkey`), status (`active`, `validator_permit`, `last_update`, `block_at_registration`), the 0..1-normalized scores (`rank`, `trust`, `consensus`, `incentive`, `dividends`, `pruning_score`), balances (`emission`, `alpha_stake`, `tao_stake`, `total_stake`), and `axon` (the served `ip:port` or None), `identity`, and `commitment`. Subnet-level fields (`tempo`, `price`, `owner_hotkey`, ...) live on the `Metagraph` itself, and `mg.raw` keeps the untouched runtime record. `commitment` is the neuron's entry in the Commitments pallet, timelock-aware: ```python c = mg.neuron(5).commitment # or mg.commitments[5]; None if none if c is not None: c.status # "plain" | "sealed" | "revealed" c.value # visible content, or None while sealed c.reveals_at # UTC datetime a sealed payload opens (else None) ``` `client.subnets.commitments(netuid)` fetches just the commitments (keyed by hotkey, much cheaper than the full metagraph); the named reads are [`commitment`](/docs/query/commitment), [`commitments`](/docs/query/commitments), and [`revealed-commitment`](/docs/query/revealed-commitment). Writing one has no dedicated intent: the chain call is `Commitments.set_commitment`, reachable via the raw-call escape hatch ([Advanced submission](/docs/concepts/advanced)). ## Blocks, time, and waiting [#blocks-time-and-waiting] ```python await client.block() # current block number await client.block_info(123) # header + extrinsics + timestamp async for header in await client.blocks(): ... # subscribe to new blocks await client.wait_for_block(1_000_000) await client.wait_for_timestamp("2026-08-01T00:00:00Z") await client.wait_for_epoch(netuid=1) # next epoch boundary on a subnet ``` ## Pinned snapshots [#pinned-snapshots] `client.at(block)` returns a `Snapshot`: the domain namespaces (`balances`, `subnets`, `neurons`, `staking`) pinned to one block, so a multi-read computation sees one consistent state instead of racing the chain. Writes through a snapshot are rejected — it is a view, not a signer. ```python snap = await client.at(await client.block()) bal = await snap.balances.get("5F...") subnets = await snap.subnets.all() ``` ## Writing [#writing] `plan`, `execute`, `execute_tool`, `submit_shielded`, and `submit_call` are covered in [The transaction model](/docs/concepts/transactions) and [Advanced submission](/docs/concepts/advanced). ## Logging [#logging] The SDK logs under the `bittensor.*` namespace and never configures handlers — it is silent unless your application opts in: ```python import logging logging.getLogger("bittensor").setLevel(logging.DEBUG) ``` # Emissions (/docs/concepts/emissions) Emissions run in two stages. Every block, the chain mints TAO, splits it across subnets, and injects liquidity into their pools (the **coinbase**). Every tempo, each subnet distributes its accumulated alpha to owner, miners, validators, and stakers via **Yuma Consensus** (the **epoch**). This page covers the numbers; [The network](/docs/concepts/network) covers the roles. ## TAO emission and halvings [#tao-emission-and-halvings] The base emission is **1 TAO per block** (one block every 12 seconds), decaying toward a hard cap of 21 million TAO. Halvings are triggered by **total-issuance thresholds**, not block counts: emission halves each time issuance crosses the midpoint of the remaining supply (10.5M, 15.75M, ...). Because recycled TAO (registration burns) is subtracted from total issuance and can be re-emitted, recycling pushes halvings out. Transaction fees are not recycled — they are paid to the block author (see [fees](/docs/concepts/transactions#fees)). The first halving occurred in December 2025: current emission is 0.5 TAO per block, roughly 3,600 TAO per day. A TAO halving also halves every pool's injection: each subnet's `tao_in` is a share of the halved block emission, and `alpha_in` tracks it (`tao_in / price`), so alpha injection halves too — slowing every subnet's alpha issuance and stretching alpha-halving timelines. Supply accounting in brief: max supply is 21M for TAO and for each subnet's alpha; **total issuance** counts what has been emitted and not recycled (the halving yardstick), while circulating supply is smaller — issuance includes pool reserves and staked positions. Burned tokens stay counted in issuance forever; recycled amounts are re-emittable ([recycled vs burned](/docs/concepts/money#recycled-vs-burned)). ## Alpha emission [#alpha-emission] Subnet tokens date from the **dTAO** upgrade (February 2025, first dTAO block 4,920,351), which converted all existing stake to root TAO stake at the switch. Each subnet's alpha token has its own 21M cap and follows the same halving curve, applied to that subnet's alpha issuance and starting from the subnet's launch. Per block, a subnet mints alpha in two places: * **`alpha_out`** — up to 1 alpha (at the subnet's current halving rate) destined for participants, accumulated for distribution at the next epoch. * **`alpha_in`** — alpha injected into the pool alongside the subnet's TAO emission, normally `tao_in / price` so the injection is price-neutral. So a young subnet mints up to 2 alpha per block in total. The injection is capped at `root_proportion × alpha_emission`, where `root_proportion = (root_tao × tao_weight) / (root_tao × tao_weight + alpha_issuance)`. As a subnet ages its alpha issuance grows, the cap falls, and the TAO that can no longer be injected as liquidity is instead swapped for alpha on the subnet's own pool — buying pressure that transitions mature subnets from liquidity injection to chain buybacks. Alpha bought this way accumulates as protocol-owned alpha. ## Subnet emission shares [#subnet-emission-shares] Each block's TAO emission is divided across subnets in proportion to their **EMA price**, weighted by a miner-burn penalty (this price-based formula shipped in June 2026): ``` share_i = p_i × (1 − b_i) / Σ_j p_j × (1 − b_j) ``` where `p_i` is the subnet's moving price (`SubnetMovingPrice`) and `b_i` is the proportion of the last tempo's miner incentive that was withheld because it was directed to subnet-owner hotkeys (counted whether the withheld alpha was recycled or burned). If the combined weight is zero across all subnets, the chain falls back to unweighted price shares so emission is never stranded. Emission-disabled subnets get zero share, redistributed proportionally to enabled ones — and with `tao_in` zeroed, `alpha_in` (`tao_in / price`) is zero too, so pool injection stops entirely while `alpha_out` keeps accruing for participants. The EMA uses an age-dependent smoothing factor: ``` ema_alpha = base_alpha × blocks_since_start / (blocks_since_start + halving_blocks) ``` with `halving_blocks` defaulting to 201,600 (\~4 weeks). New subnets start near zero — their moving price adapts extremely slowly, which blunts launch pumps, coordinated buys, and flash attacks on emission shares. The spot price feeding the EMA is capped at 1.0. There is no zero-emission floor: a subnet with a low EMA price still receives a small non-zero share. ## Pools and price [#pools-and-price] Pools are Balancer-style weighted pools, and the spot alpha price is `(w_alpha / w_tao) × (TAO reserve / alpha reserve)` (read it with [`alpha-price`](/docs/query/alpha-price)). The two weights start at 0.5/0.5 — where the price reduces to the plain reserve ratio and the math to constant-product — and are bounded to \[0.01, 0.99]. Per-block liquidity injections shift the weights instead of the price, so emission does not move the market, but it does nudge the weights slightly off 0.5/0.5 — so the price is the weighted ratio, not exactly `TAO / alpha`. Pool liquidity is protocol-owned by default: the chain has a user-liquidity feature (per-subnet `user_liquidity_enabled` toggle, off by default) but with it off there are no user LP positions or LP tokens. ## The per-tempo split [#the-per-tempo-split] Per-block `alpha_out` is divided as it accrues: * **18%** to the subnet owner (`SubnetOwnerCut`, 11796/65535). * **41%** to miners — 50% of the remainder. * **41%** to validators and their stakers — the other 50%. A `root_proportion` share of the validator half (same formula as the injection cap) is reserved for **root TAO stakers** and accumulated as claimable root dividends — but only in blocks where the sum of all subnets' EMA prices exceeds 1.0; otherwise that alpha is recycled. If an epoch ends with zero total miner incentive, the miner half of that tempo's pending alpha is paid to validators instead of being withheld. Within a validator's dividends, each staker is paid according to the validator's stake mix: the TAO-staker portion is `τ × w / (α + τ × w)` and the alpha-staker portion is `α / (α + τ × w)`, where `w` is the global TAO weight (currently 0.18 on mainnet, governance-set). The validator's [take](/docs/tx/set-take) is deducted before delegators are paid. ## Epochs [#epochs] Distribution happens at epoch boundaries. Each subnet's epoch fires once `tempo` blocks have passed since its last epoch; the default tempo is 360 blocks (\~72 minutes), owner-settable between 360 and 50,400 (\~7 days), and some older subnets carry smaller legacy values. At most 2 subnet epochs run per block — extras are deferred one block — and a subnet owner can trigger an early epoch manually. An epoch that hits inconsistent chain state is skipped (the schedule still advances); its pending emission keeps accruing and is drained by the next successful epoch. Emission is settled per epoch, at epoch end: the accumulated alpha is paid to the hotkeys holding each UID when the epoch fires, not continuously per block. Deregistration follows the same rule — a neuron pruned mid-tempo gets nothing for the partial tempo, since the payout lands on whoever holds the UID at the epoch (its last payout was the last epoch that fired while it was registered). The [`metagraph`](/docs/query/metagraph)'s per-neuron emission field reflects this settlement: it is denominated in rao (of the subnet's alpha) and holds each UID's combined payout from the subnet's most recent epoch — a per-tempo amount, not a per-block rate. [`epoch-status`](/docs/query/epoch-status) and [`blocks-until-next-epoch`](/docs/query/blocks-until-next-epoch) expose the timing per subnet. ## Yuma Consensus [#yuma-consensus] At each epoch the chain resolves the validator-weight matrix (set via [`set-weights`](/docs/tx/set-weights)) into per-neuron emission shares. **Stake weight.** Each neuron's stake weight is `alpha_stake + tao_stake × tao_weight` — alpha staked on the subnet plus root-subnet TAO stake scaled by the global TAO weight (currently 0.18 on mainnet, governance-set). Validators below the chain's stake threshold (currently 1,000 tokens' worth) are zeroed, and neurons whose last weight update is older than the activity cutoff (default 5,000 blocks) are inactive and excluded from consensus. **Permits.** Validator permits are recalculated every epoch as the top-K neurons by stake weight, with K = `MaxAllowedValidators` (default 128). Only permitted neurons can set weights over others; bonds are kept while a permit is held and cleared when it is lost. **Weight filtering.** Self-weights are removed (except the subnet owner's), as are weights from non-permitted validators and weights set before the target neuron's latest registration. Each validator's surviving weights are row-normalized, so influence is independent of absolute weight values. **Consensus and clipping.** For each miner, consensus is the stake-weighted median: the highest weight level supported by at least `kappa` of active stake, with kappa defaulting to 32767/65535 ≈ 0.5. Weights above consensus are clipped down to it. **Rank and incentive.** A miner's rank is the stake-weighted sum of clipped weights, `r_j = Σ_i s_i × w̄_ij`; normalized ranks become **incentive**, each miner's share of the miner emission. **Validator trust** is the sum of a validator's clipped weights. The per-miner **trust** metric (the ratio of clipped to unclipped rank) is deprecated — the chain no longer computes it, and the metagraph returns empty trust and rank vectors. **Bonds and dividends.** Bond weights interpolate between raw and clipped weights by the bonds-penalty hyperparameter (default: fully clipped). Instant bonds `ΔB = W ∘ S` (weights times stake, column-normalized) are smoothed by an EMA: ``` B(t) = alpha × ΔB + (1 − alpha) × B(t−1) ``` where `alpha = 1 − bonds_moving_average / 1,000,000` (default 900,000, so alpha = 0.1). Validator dividends are bonds times miner incentive, `d_i = Σ_j B_ij × I_j` — validators who recognize good miners early build bonds and earn more when consensus catches up. **Yuma3** (a per-subnet toggle) switches to fixed-point bond computation with per-pair scaling, and computes dividends differently: the row-sum of bonds × incentive, scaled by each validator's active stake, then renormalized. With **liquid alpha** enabled, the EMA rate becomes dynamic per validator–miner pair, moving between `alpha_low` (default 0.7) and `alpha_high` (default 0.9) via a sigmoid on the distance from consensus (steepness default 1000). Liquid alpha only takes effect when `yuma3_enabled` is also on — the classic bond path ignores the toggle entirely. Both toggles and their parameters are owner-set [hyperparameters](/docs/query/subnet-hyperparameters). If no valid weights exist, emission falls back to stake proportions, so a subnet without consensus still pays its stakers. The epoch writes consensus, incentive, dividends, validator trust, bonds, and permits back to chain state — [`metagraph`](/docs/query/metagraph) returns all of it in one read, and combined emission drives pruning (deregistration) order. # Money (/docs/concepts/money) Amounts on Bittensor come in two kinds of currency: **TAO**, the chain token, and **alpha**, of which every subnet has its own. They trade against each other in per-subnet pools, so an alpha amount is meaningless without knowing which subnet it belongs to — and 1 alpha on subnet 1 is not worth 1 alpha on subnet 2. ## The Balance type [#the-balance-type] `Balance` is unit-tagged: every instance carries the netuid whose currency it denominates (netuid 0 is TAO). Arithmetic between different units raises `UnitMismatchError` instead of silently mixing currencies. ```python import bittensor as sub sub.Balance.from_tao("10000000.123456789") # exact TAO (str/Decimal for big amounts) sub.Balance.from_alpha(2.5, netuid=42) # subnet-42 alpha; prints with the subnet's # on-chain token symbol (α₄₂ before connect) sub.tao(1.5); sub.alpha(2.5, 42); sub.rao(1_500_000_000) # shorthands ``` Readback is unit-named too: `.tao` on a TAO balance, `.alpha` on an alpha balance — and `.tao` on an alpha balance raises rather than pretending the units are interchangeable. The base unit is **rao**: 1 TAO = 10⁹ rao. ## Amount parameters [#amount-parameters] Intent fields are named by unit so there is no ambiguity about what a number means: `amount_tao` is TAO, `amount_alpha` is the subnet's alpha (the origin subnet for cross-subnet moves), `*_rao` is rao. Money fields that support it accept the string `"all"` — the concrete amount is resolved from chain state at build time (e.g. [`transfer`](/docs/tx/transfer), [`remove-stake`](/docs/tx/remove-stake)). ## Valuing stake [#valuing-stake] Alpha is never summed across subnets or silently treated as TAO. To value a coldkey's stake in TAO, use the [`stake-value-for-coldkey`](/docs/query/stake-value-for-coldkey) read: a spot price mark (`alpha × price`, excluding slippage and fees), pinned to one block. For what you would actually receive by exiting a position, simulate the swap with [`quote-unstake`](/docs/query/quote-unstake), which includes fees and slippage. ## Slippage [#slippage] Staking and unstaking are swaps against a pool, 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 **price impact**; **slippage** is the further movement from other transactions landing before yours. The quotes account for the first, and the limit variants protect against both. The `*-limit` variants ([`add-stake-limit`](/docs/tx/add-stake-limit), [`remove-stake-limit`](/docs/tx/remove-stake-limit)) bound the execution price and fail instead of filling badly. To turn a tolerance into a `limit_price`: when staking, ceiling = spot × (1 + tolerance); when unstaking, floor = spot × (1 − tolerance). At a spot price of 2.0 TAO ([`alpha-price`](/docs/query/alpha-price)), a 0.5% tolerance means 2.01 staking and 1.99 unstaking. The same economics decide what is worth [MEV-shielding](/docs/concepts/advanced#mev-shielded-submission). Root-subnet (netuid 0) staking is not a pool swap — it converts 1:1 with no swap fee and no price impact, so there is nothing for a front-runner to extract and shielding it buys nothing. Small swaps (well under \~1 TAO) are equally unattractive targets; the danger case is a large swap with a loose or absent limit price. ## Swap fees [#swap-fees] Every swap against a pool pays a fee of amount × FeeRate / 65535, where `FeeRate` is per-subnet and governance-settable (default 33 ≈ 0.05%). It is taken from the input side: TAO when staking, alpha when unstaking — and paid to the block author (an alpha-side fee is converted to TAO first). Moving stake between hotkeys within the same subnet is not a swap and pays no swap fee; a cross-subnet move runs two swaps but is charged the fee only once. The [`quote-stake`](/docs/query/quote-stake) / [`quote-unstake`](/docs/query/quote-unstake) reads include it. This is distinct from the per-extrinsic transaction fee — see [the transaction model](/docs/concepts/transactions). ## Minimums [#minimums] Two floors to know about: * **Existential deposit** — 500 rao. An account whose balance drops below it is reaped: deactivated, with the remaining dust destroyed. Live value: [`existential-deposit`](/docs/query/existential-deposit). * **Minimum stake** — staking operations below a chain-configured minimum (default 2,000,000 rao = 0.002 TAO, plus the swap fee on top) fail with `AmountTooLow`; unstakes are held to the same floor, both on the amount withdrawn and on any dust position left behind. ## Supply [#supply] Every token is hard-capped at **21 million** — TAO and each subnet's alpha alike. **Total issuance** is the chain's counter of what has been emitted and not recycled; it is what halving thresholds are measured against ([emissions](/docs/concepts/emissions)). Circulating supply is smaller: issuance includes tokens locked in pool reserves and staked positions. ## Recycled vs burned [#recycled-vs-burned] Both remove tokens from circulation, but not equivalently. **Recycled** tokens are subtracted from the chain's issuance counter, so the emission schedule can re-issue them later — neuron registration costs are recycled this way. **Burned** tokens stay counted in total issuance and are simply gone forever; nothing re-emits them. The distinction matters when reasoning about supply: recycling slows emission's approach to the cap, burning permanently retires supply. Transaction fees are neither — they are paid to the block author, not destroyed ([fees](/docs/concepts/transactions#fees)). # The network (/docs/concepts/network) Bittensor is a blockchain (the **subtensor** chain) that coordinates and pays for useful work. The work itself happens off-chain, inside **subnets**; the chain records who participates, measures agreement about who is doing good work, and emits the network's token, **TAO**, accordingly. The chain produces one block every 12 seconds. Subtensor is a Substrate-based chain. Each block contains **extrinsics** — the submitted calls — and **events**; some events are emitted by the chain's own per-block hooks (emissions, epoch boundaries) rather than by any extrinsic. ## Networks and endpoints [#networks-and-endpoints] The SDK's network names resolve to public endpoints: * `finney` — mainnet, `wss://entrypoint-finney.opentensor.ai:443` * `test` — public testnet, `wss://test.finney.opentensor.ai:443` * `local` — a dev node at `ws://127.0.0.1:9944` Mainnet also has a public **lite** node at `wss://lite.chain.opentensor.ai:443` and an **archive** node at `wss://archive.chain.opentensor.ai:443`. A lite node keeps only recent state; to query state older than roughly 300 blocks (block-pinned reads, historical snapshots) you need the archive endpoint or your own archive node. Any `ws://` / `wss://` endpoint can be passed to [`Client`](/docs/concepts/client) directly. ## Subnets [#subnets] A subnet is an independent incentive market, identified by an integer **netuid**. Each subnet has: * an **owner** — the coldkey that registered it, who defines the incentive mechanism and sets its [hyperparameters](/docs/tx/set-hyperparameter); * **miners** — hotkeys registered on the subnet that produce whatever the subnet's mechanism demands (inference, storage, predictions, ...); * **validators** — hotkeys with enough stake behind them to hold a validator permit; they score miners by [setting weights](/docs/tx/set-weights); * a liquidity pool pairing TAO with the subnet's own token, **alpha**. Netuid 0 is the **root network**: it has no miners, no alpha token, and no validation work — it is the pool where TAO is staked directly. Root TAO stake counts toward validator stake weight on every subnet (see below) and earns a share of each subnet's dividends. Registration there is stake-based rather than burn-based; the root network's governance role is covered in the [governance guide](/docs/guides/governance). By convention, root (TAO) is priced at 1.0 wherever subnet prices are summed. Subnet slots are limited: a subnet holds at most `max_allowed_uids` neurons (default 256, and no lower than 64). Registering on a full subnet ([`burned-register`](/docs/tx/burned-register)) evicts the lowest-ranked non-immune neuron; a freshly registered neuron is protected for an immunity period, then competes like everyone else. A neuron's **stake weight** on a subnet is `alpha_stake + tao_stake × tao_weight` — its alpha staked there, plus its root TAO stake scaled by the global TAO weight (currently 0.18 on mainnet, governance-set). Validator permits go to the top neurons by stake weight, up to the subnet's `max_allowed_validators` (default 128), recalculated every epoch. ## TAO and alpha [#tao-and-alpha] TAO is the chain's native token (1 TAO = 10⁹ rao). Each subnet also has its own token, alpha, traded against TAO in the subnet's pool. Staking TAO into a subnet swaps it to that subnet's alpha at the pool price; unstaking swaps back. Alpha on one subnet is not worth alpha on another — the SDK's [`Balance` type](/docs/concepts/money) enforces this at the type level. Spot prices come from the [`alpha-price`](/docs/query/alpha-price) read, and [`quote-stake`](/docs/query/quote-stake) / [`quote-unstake`](/docs/query/quote-unstake) simulate a swap including fees and slippage before you commit. ## Emissions and epochs [#emissions-and-epochs] The chain continuously emits TAO to subnets, and within each subnet to its participants, in proportion to measured value. Each subnet runs on a cycle (its **tempo**): validators submit weights over miners during the epoch, and at the epoch boundary Yuma Consensus combines those weights — weighted by validator stake — into ranks, trust, and incentive, which set each participant's share of the emission. Delegated stake earns a share of its validator's dividends, minus the validator's [take](/docs/tx/set-take). The numbers behind all of this — halving schedules, the emission-share formula, the 18/41/41 per-tempo split, and the consensus math — are on the [Emissions](/docs/concepts/emissions) page. The [`epoch-status`](/docs/query/epoch-status) and [`blocks-until-next-epoch`](/docs/query/blocks-until-next-epoch) reads expose the timing; [`metagraph`](/docs/query/metagraph) returns the full per-neuron state of a subnet in one call. ## Who signs what [#who-signs-what] Every account is an **ss58 address** backed by a keypair. Operations divide by signer: * **coldkey** — owns funds: transfers, staking, registration, subnet ownership. * **hotkey** — operates on subnets: weights, serving an axon. The [wallets page](/docs/concepts/wallets) covers how the two keys relate and how to manage them. # The transaction model (/docs/concepts/transactions) Every state change goes through one shape, whether you drive it from Python, the CLI, or an agent tool call: 1. Describe **what** you want as an **intent** — a small serializable dataclass. 2. **`plan`** it — fee, predicted effects, warnings, policy verdict. Nothing is submitted. 3. **`execute`** it — sign and submit through a single policy-gated choke point, and get a typed result back. ## Intents [#intents] An intent's fields are JSON-native, so it round-trips to and from a dict without custom encoders. Each intent knows its chain call, its signer (coldkey or hotkey), and its JSON schema. All 70+ of them are cataloged under [Transactions](/docs/tx), one page each. ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AddStake(hotkey_ss58="5F...validator", netuid=1, amount_tao=10) ``` Or build one by op name from a plain dict — how agents and tools do it: ```python intent = sub.intents.build("add_stake", {"hotkey_ss58": "5F...", "netuid": 1, "amount_tao": 10}) ``` ## Plan: preview everything [#plan-preview-everything] ```python async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) plan.fee # estimated fee (Balance) plan.effects # ["stake 10 TAO to 5F... on subnet 1", ...] plan.warnings # non-fatal cautions plan.violations # policy violations that would block execute plan.ok # no violations? ``` On the CLI, `--dry-run` on any `tx` command prints the same plan and submits nothing. ## Execute: one choke point [#execute-one-choke-point] ```python result = await client.execute(intent, wallet) result.success # bool result.block_hash # where it landed ``` `execute` re-plans, enforces the policy, signs with the correct key (the intent declares whether it needs the coldkey or hotkey), submits, and decodes the receipt. `client.execute_tool(op, args, wallet)` is the by-name variant. ## Policy: hard guardrails [#policy-hard-guardrails] A `Policy` bounds what any execution may do. Violations raise `PolicyError` at execute time and appear in every plan: ```python policy = sub.Policy( max_fee_tao=0.1, max_spend_tao=5.0, allowed_netuids=[1, 2], allow_raw_calls=False, # default: raw submit_call is refused ) async with sub.Client("finney", policy=policy) as client: ... ``` Intents whose spend cannot be bounded ahead of time (subnet registration, `transfer_all`, ...) report an infinite spend, so a spend cap blocks them until deliberately raised. ## Typed results and errors [#typed-results-and-errors] Failures come back as data, not prose. The `error` on a failed `ExtrinsicResult` carries the exact chain error name, a semantic [`ErrorCode`](/docs/errors) to branch on, and a remediation hint: ```python result = await client.execute(sub.BurnedRegister(netuid=999), wallet) if not result.success: print(result.error.name) # "SubnetNotExists" print(result.error.code) # ErrorCode.SUBNET_NOT_EXISTS print(result.error.remediation) # what to try next ``` Every chain error name is deliberately classified — a CI gate keeps the mapping complete in both directions — so a failure never degrades to "unknown" without a reason. ### Pool-level rejections [#pool-level-rejections] Besides dispatch errors, a transaction can be rejected before it enters the pool — the node's pre-checks fail it with `Custom error: N` instead of a named error, surfaced over RPC as error 1010 `Invalid Transaction`. The codes: | Code | Name | Meaning | | ---- | -------------------------------- | -------------------------------------------------- | | 0 | ColdkeyInSwapSchedule | Coldkey has an announced swap pending (deprecated) | | 1 | StakeAmountTooLow | Amount below the minimum stake | | 2 | BalanceTooLow | Not enough free balance | | 3 | SubnetNotExists | Target subnet does not exist | | 4 | HotkeyAccountDoesntExist | Hotkey account not found | | 5 | NotEnoughStakeToWithdraw | Unstake exceeds the stake held | | 6 | RateLimitExceeded | Weight-setting or transaction rate limit hit | | 7 | InsufficientLiquidity | Pool cannot absorb the trade | | 8 | SlippageTooHigh | Execution price outside the limit | | 9 | TransferDisallowed | Transfers disabled for this operation | | 10 | HotKeyNotRegisteredInNetwork | Hotkey not registered anywhere | | 11 | InvalidIpAddress | Axon/prometheus IP invalid | | 12 | ServingRateLimitExceeded | Axon/prometheus serving rate limit hit | | 13 | InvalidPort | Axon/prometheus port invalid | | 14 | ZeroMaxAmount | Computed max amount is zero | | 15 | InvalidRevealRound | Wrong reveal round for timelocked weights | | 16 | CommitNotFound | No matching weight commit | | 17 | CommitBlockNotInRevealRange | Reveal outside its window | | 18 | InputLengthsUnequal | Paired input vectors differ in length | | 19 | UidNotFound | Hotkey not registered on this subnet | | 20 | EvmKeyAssociateRateLimitExceeded | EVM key association rate limit hit | | 21 | ColdkeySwapDisputed | Coldkey swap under dispute | | 22 | InvalidRealAccount | Proxy real account invalid | | 23 | FailedShieldedTxParsing | Shielded payload failed to decode | | 24 | InvalidShieldedTxPubKeyHash | Shielded payload key hash mismatch | | 25 | NonAssociatedColdKey | Coldkey does not own this hotkey | | 26 | DelegateTakeTooLow | Take below the allowed minimum | | 27 | DelegateTakeTooHigh | Take above the allowed maximum | | 255 | BadRequest | Catch-all for anything else | Code 6 covers weight-setting and general transaction rate limits; axon serving has its own code 12. ## Fees [#fees] A fee-bearing extrinsic pays two components in TAO from the signer's free balance: a **weight fee**, linear in the call's dispatch weight, and a **length fee** of 1 rao per byte of the encoded extrinsic. Both are withdrawn up front, before the call runs — insufficient balance rejects the transaction outright, and a failed call does not refund them. The fee is paid to the block author, not recycled or burned (dropped in the edge case of a block with no author). `plan.fee` (and the `--dry-run` output) is this number, estimated from the chain before anything is signed. The validator hot path is free: [`set-weights`](/docs/tx/set-weights), [`commit-weights`](/docs/tx/commit-weights), [`reveal-weights`](/docs/tx/reveal-weights) and their batch and timelocked variants pay no transaction fee, as do [`serve-axon`](/docs/tx/serve-axon) and take changes. For a small set of unstake-side calls (`remove_stake` and friends), a signer with no TAO can have the fee taken in alpha instead, converted at pool price; every other call simply requires TAO. Staking, unstaking, and stake moves additionally pay a pool swap fee on the amount transacted — a property of the swap, not the extrinsic; see [swap fees](/docs/concepts/money#swap-fees). ## Rate limits [#rate-limits] Chain rate limits are block-based cooldowns armed only by **successful** operations — a failed call can be retried immediately. Notable defaults (governance-settable): * Delegate-take changes ([`increase-take`](/docs/tx/increase-take) / [`decrease-take`](/docs/tx/decrease-take)): once per 216,000 blocks (\~30 days). Childkey-take changes ([`set-childkey-take`](/docs/tx/set-childkey-take)) have the same limit. * [`set-children`](/docs/tx/set-children): once per 150 blocks (\~30 minutes) per subnet. * [`swap-hotkey`](/docs/tx/swap-hotkey): a global cooldown (live value: [`tx-rate-limit`](/docs/query/tx-rate-limit)) plus a per-subnet interval of 7,200 blocks (\~1 day). ## Mortality [#mortality] Extrinsics are signed with a mortal era: valid for 128 blocks (\~25 minutes) by default, after which an unincluded transaction lapses instead of lingering in the pool indefinitely. `execute` and `submit_call` take a `period=` argument to change it (`period=None` signs an immortal transaction); MEV-shielded inner extrinsics use a shorter 8-block era — the chain rejects shielded wrappers signed with anything longer. ## The CLI is the same machinery [#the-cli-is-the-same-machinery] Every intent is `btcli tx `; the command's options are generated from the intent's fields, so the CLI and SDK can't diverge. Interactive sessions prompt for missing required options and confirm before submitting; `--yes` skips confirmation, and a non-interactive session without `--yes` is declined rather than left hanging. # Wallets and keys (/docs/concepts/wallets) A Bittensor wallet is a pair of keys stored on disk (default `~/.bittensor/wallets//`): * The **coldkey** holds funds and signs financial operations: transfers, staking, registration, subnet ownership. It is encrypted at rest with a password. Keep it offline where possible — [proxies](/docs/concepts/advanced) exist precisely so day-to-day operations don't need it. * The **hotkey** is your operational identity on subnets: it gets the UID when you register, signs weights and axon serving, and is safe to keep on a running machine. One wallet can hold many hotkeys. ## Key material [#key-material] Coldkeys and hotkeys are **sr25519** keypairs, addressed with SS58 network prefix 42. Each key is generated from a mnemonic (12 words by default), and that mnemonic is the only recovery path: the wallet password merely decrypts the keyfile on one machine — it cannot regenerate a lost key, and no password is needed to regenerate the key from the mnemonic. Whoever holds the mnemonic owns the funds. On disk, each wallet directory contains: * `coldkey` — the coldkey secret, password-encrypted (NaCl); * `coldkeypub.txt` — the coldkey's public key and ss58 address, unencrypted (no secret material); * `hotkeys/` — one file per hotkey, **unencrypted**: the SDK's create and regen helpers always write hotkeys in plaintext, private key and mnemonic included. Treat every hotkey file as readable by anything that can read the disk. ## Create, import, inspect [#create-import-inspect] ```bash btcli wallet create -w my_coldkey # new coldkey + hotkey (prints mnemonics once — save them) btcli wallet new-hotkey -w my_coldkey -H my_hotkey btcli wallet regen-coldkey -w my_coldkey # recover from mnemonic (prompted securely) btcli wallet regen-hotkey -w my_coldkey btcli wallet regen-coldkeypub -w watch_only --ss58 5F... --public-key 0x... # watch-only, no secrets btcli wallet list # wallets on disk, multisigs, address book btcli wallet show # public keys + crypto schemes btcli wallet balance my_coldkey # free TAO + stake marked to TAO ``` The same flows exist in Python under `sub.wallets` (`create`, `regen_coldkey`, `regen_hotkey`, `list_wallets`, `sign_message`, `verify_message`, ...). Key material is handled by the SDK's native wallet module (`bittensor.wallet`, backed by the in-repo `py-sp-core` extension); this package adds thin conveniences. ```python from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") ``` ## Back up the seed phrase [#back-up-the-seed-phrase] A seed phrase fails in two ways: **loss** (the funds are gone permanently — nobody, including the Opentensor Foundation, can recover them) and **leak** (anyone who saw it can drain the wallet — respond by rotating keys, see below). Backups defend against loss; handling discipline defends against leak. * Never type a seed phrase into messaging apps, email, cloud documents, or any networked application. Assume keyloggers, screen capture, and cameras exist when generating or restoring keys. * The "hex-encoded seed" or "private key hex" is the seed phrase in another encoding — anyone asking for it is asking for the seed phrase. Nobody legitimate asks for either. "Fix your RPC settings" DMs are phishing, and unsolicited DMs offering support are scams; keep help requests in public channels. * Keep redundant physical backups in separate locations so one fire, flood, or theft cannot destroy every copy: paper in a tamper-evident envelope inside a safe or deposit box; a stamped metal plate survives what paper doesn't. * A digital backup should live on an encrypted offline drive (GPG or VeraCrypt, strong unique passphrase) that is only ever connected to a trusted, offline machine. * A hardware wallet is a signing device, not a backup: it will not export the seed phrase, and too many wrong PINs factory-reset it. Use it in addition to seed-phrase backups, never instead of them. ## Security model [#security-model] Match each key to the least-trusted machine that needs it: 1. **Public keys only** — balances and state are readable without any secret: import just the address with `btcli wallet regen-coldkeypub` and use any [query](/docs/query) from an everyday machine. 2. **Hotkey machines** — mining and validation servers run subnet code and ML dependencies, which is untrusted code sitting next to your hotkey. Create hotkeys on a trusted workstation and provision them to servers through a secrets manager (Vault, AWS/GCP secret stores) or ephemeral CI injection; never commit key files to a repository. A leaked hotkey can't move TAO, but it can submit garbage weights and burn the reputation of its UID. 3. **Coldkey workstation** — a dedicated, clean machine for coldkey-signed operations, ideally holding only a scoped proxy key rather than the real coldkey. 4. **Primary coldkey in a hardware wallet** — Ledger works through Talisman, Nova Wallet, or SubWallet; Polkadot Vault turns a permanently offline phone into an air-gapped signer that can sign any extrinsic via QR codes (Ledger apps cover a narrower operation set). This CLI and SDK cannot sign with hardware wallets. That is what the proxy pattern is for: the hardware-held primary coldkey creates a single delayed `NonTransfer` [proxy](/docs/concepts/advanced), and that proxy creates and revokes narrower scoped proxies (`Staking`, `Registration`, ...) for daily work. Only two operations ever strictly require the primary coldkey: the first add-proxy, and coldkey rotation. ### Supply-chain risk [#supply-chain-risk] Pin exact package versions in anything unattended, and upgrade only to releases announced on official channels. This is not hypothetical: in July 2024 a compromised PyPI release of the legacy `bittensor` package (6.12.2) exfiltrated coldkey material from machines that installed it, and the chain ran in safe mode from July 2 to July 12, 2024 while the damage was contained. For high-value machines, installing from the source repository at a signed tag (`git tag -v`) beats trusting a package index. ## Signing and verification [#signing-and-verification] ```bash btcli wallet sign --message "hello" -w my_coldkey # coldkey signature btcli wallet sign --message "hello" --use-hotkey -w my_coldkey btcli wallet verify --message "hello" --signature 0x... --ss58 5F... ``` Every transaction declares which key signs it — the `signer` column on each [transaction page](/docs/tx). Staking and transfers are coldkey-signed; weights and serving are hotkey-signed. ## How address arguments resolve [#how-address-arguments-resolve] Any address option in the CLI (`--dest`, `--hotkey`, `--coldkey`, ...) accepts three forms: * a raw **ss58 address**; * a **local name** — hotkey options take `HOTKEY` or `WALLET/HOTKEY`, coldkey options take a wallet name (its coldkey is used), and address-book or proxy-book names resolve too; * **omitted** — `--hotkey` / `--coldkey` fall back to the configured wallet's own keys. Destination-style options never default. ```bash btcli query hotkey-owner --hotkey my_coldkey/my_hotkey btcli wallet balance my_coldkey ``` ## Address hygiene [#address-hygiene] Transfers are irreversible, and **address poisoning** exploits that: attackers grind vanity addresses matching the first and last characters of addresses you transact with, then send you a dust transfer so the lookalike appears in your history — waiting for you to copy the wrong entry. Defenses: * read the whole address, not just both ends; * know the shape: every Bittensor ss58 address starts with "5" (SS58 prefix 42\) — anything else isn't a Bittensor address; * pay from a saved address book (`btcli wallet list` shows yours), never from transaction history; * treat unexpected dust transfers as hostile; * send a small test transaction before any large transfer. Always send TAO to a **coldkey** address. Transferring to a hotkey address is technically possible but can strand the funds. And there is no undo anywhere in this system: no one — the Opentensor Foundation included — can reverse a theft or recover lost keys. ## Key rotation and recovery [#key-rotation-and-recovery] **Hotkey swap.** [`swap-hotkey`](/docs/tx/swap-hotkey) replaces a hotkey with a new one, moving its registrations and delegated stake. Swapping across all subnets recycles 0.1 TAO from the coldkey; swapping on a single subnet recycles 0.001 TAO and is limited to once per subnet per 7,200 blocks (\~1 day). **Coldkey swap** is the response to a leaked coldkey — but only worth it if the coldkey has registrations or owns a subnet. A plain holder or staker should simply transfer their TAO and stake to a fresh coldkey instead. The swap is a two-step, delayed process: 1. [`announce-coldkey-swap`](/docs/tx/announce-coldkey-swap) declares the destination and starts a 36,000-block (\~5-day) waiting period. The first announcement costs 0.1 TAO, which must be in the coldkey at announcement time (reannouncing later is free). During the window the wallet is locked — no transfers or staking; the coldkey can only execute or dispute. 2. [`swap-coldkey-announced`](/docs/tx/swap-coldkey-announced) executes after the delay, moving everything — balance, stake, hotkeys, registrations, subnet ownership — to the destination, which must be an unused coldkey with no existing stake, registrations, or child hotkeys. Check a pending announcement with the [`coldkey-swap-announcement`](/docs/query/coldkey-swap-announcement) read. An announcement can be reannounced (overwriting it and restarting the clock) or cleared only after a further 7,200 blocks (\~1 day). The delay exists so the real owner can catch a thief's announcement: [`dispute-coldkey-swap`](/docs/tx/dispute-coldkey-swap) freezes the key until the dispute is manually resolved. ## Non-interactive unlock [#non-interactive-unlock] For automation, the coldkey password resolves in this order (first match wins): an explicit `password=` argument in Python; the `BT_WALLET_PASSWORD` environment variable; the per-wallet variable named after the keyfile path (`BT_PW__...`, from the SDK); a password file via `--wallet-password-file` or `BT_WALLET_PASSWORD_FILE`; the macOS Keychain (`btcli wallet keychain`); then an interactive prompt. A browser-extension signer is also available so the coldkey never touches the machine running the CLI — see `--signer extension`. # Conviction locks (/docs/guides/conviction) Conviction is **time-weighted commitment**: when you [`lock-stake`](/docs/tx/lock-stake) alpha on a subnet, the locked amount accrues **conviction** toward the lock's target hotkey. Conviction is not spendable stake — it is a score the chain uses to recognize long-horizon alignment, including the automatic **subnet ownership** transfer when enough aggregate conviction exists. This guide walks through a fictional **Subnet 7 ("Atlas")** with three stakers. The numbers are round teaching examples, not live chain state — always read real values with [`subnet-convictions`](/docs/query/subnet-convictions) before acting. ## What locking does [#what-locking-does] A lock is a **floor on unstaking**, not a separate bucket: * Your coldkey's **total** staked alpha on the subnet must stay at or above the locked mass. Anything above remains freely unstakable. * Locked alpha **keeps earning** validator dividends and emissions — locking changes liquidity, not rewards. * Conviction is credited to the **hotkey you choose** at lock time (often your validator). Stake and conviction hotkeys can differ, but repeat [`lock-stake`](/docs/tx/lock-stake) calls must target the same hotkey. One lock per coldkey per subnet. Top-ups add mass; conviction continues from its current value. ## Example subnet: Atlas (netuid 7) [#example-subnet-atlas-netuid-7] | Field | Value | Meaning | | ------------------- | ---------------- | ------------------------------------------------------ | | `SubnetAlphaOut` | 8,000,000 α | Outstanding alpha on the subnet | | Ownership threshold | 800,000 α | 10% of alpha out — aggregate conviction gate | | Subnet age | > 1 year | Required before ownership can transfer | | Alice (owner) | 250,000 α locked | Perpetual lock on **owner hotkey** — conviction = mass | | Bob (validator) | 600,000 α locked | Perpetual lock toward his validator hotkey | | Carol (staker) | 200,000 α locked | Decaying lock (default) toward a validator | Drag **elapsed time** forward: Bob's perpetual conviction climbs toward 600k α. Carol's decaying conviction **rises then falls** as her locked mass frees. When **total conviction** crosses 800k α and the subnet is old enough, the **highest-conviction hotkey** becomes the new owner. ## Perpetual vs decaying [#perpetual-vs-decaying] New locks are **decaying** by default. Opt into **perpetual** with [`set-perpetual-lock`](/docs/tx/set-perpetual-lock). | Mode | Locked mass | Conviction | | ------------- | --------------------------------- | --------------------------------------------- | | **Perpetual** | Fixed | Approaches mass: `c = m − (m − c₀)·e^(−Δt/τ)` | | **Decaying** | Exponential decay on `UnlockRate` | Integral of decaying mass — peaks, then falls | On mainnet today `MaturityRate` is **311,622 blocks** (\~43 days) and `UnlockRate` is **934,866 blocks** (\~130 days). Both are governance-set storage values — read them live before planning, do not hardcode them. Switching to **decaying** mode emits a public on-chain event — an advance exit signal to other stakers. Locks on the **subnet owner hotkey** mature instantly: conviction always equals locked mass. ## Subnet ownership via conviction [#subnet-ownership-via-conviction] After each epoch, the chain runs `change_subnet_owner_if_needed` when **all** of these hold: 1. Subnet age ≥ **ONE\_YEAR** (2,629,800 blocks from registration). 2. **Total aggregate conviction** ≥ 10% of `SubnetAlphaOut`. 3. A hotkey with the highest rolled aggregate conviction resolves to a real coldkey owner (not the default account). The winning hotkey becomes `SubnetOwnerHotkey`; its coldkey becomes `SubnetOwner`. If the leader already owns the subnet, nothing changes. This is **not** the same as the per-hotkey projection in [`subnet-convictions`](/docs/query/subnet-convictions) — that API estimates when *one* hotkey might reach 10% of alpha out; ownership requires **aggregate** conviction across all lockers. ## Lock a position on a real subnet [#lock-a-position-on-a-real-subnet] Replace netuid `7` with your target subnet: ```bash # Inspect existing locks and conviction leaderboard btcli query subnet-convictions --netuid 7 --json btcli query hotkey-conviction --hotkey --netuid 7 --json btcli query coldkey-lock --coldkey --netuid 7 --json # Lock 1,000 alpha toward a validator hotkey (dry-run first) btcli tx lock-stake --netuid 7 --amount-alpha 1000 --dry-run -w my_coldkey btcli tx lock-stake --netuid 7 --amount-alpha 1000 -w my_coldkey # Switch to perpetual mode (irreversible public signal when switching to decaying) btcli tx set-perpetual-lock --netuid 7 --enabled true -w my_coldkey ``` The same flow in Python — lock, opt into perpetual, then read conviction back: ```python await client.execute(sub.LockStake(netuid=7, hotkey_ss58="5F...", amount_alpha=1000), wallet) await client.execute(sub.SetPerpetualLock(netuid=7, enabled=True), wallet) lock = await client.read("coldkey_lock", coldkey_ss58="5C...", netuid=7) conviction = await client.read("hotkey_conviction", hotkey_ss58="5F...", netuid=7) ``` ## Rules worth remembering [#rules-worth-remembering] * [`move-lock`](/docs/tx/move-lock) to another hotkey **owned by a different coldkey** resets conviction to zero; same-owner moves preserve it. * Subnet **deregistration** deletes lock records — conviction is lost even if stake is paid out through the pool. * A **coldkey swap** fails if the destination has active locked mass on any subnet. For staking mechanics beyond locks, see the [Staking guide](/docs/guides/staking). # EVM (/docs/guides/evm) Subtensor runs a full EVM runtime: standard smart contracts, standard `eth_*` JSON-RPC, MetaMask, Hardhat, and Remix all work. Contracts execute on the Bittensor chain (not Ethereum) with **TAO** as the native currency. This guide starts with the smallest working path — create a key, fund it, check the balance — then walks through every `btcli evm` command and the deeper concepts (address mappings, decimals, precompiles). ## Quick start: your first EVM key [#quick-start-your-first-evm-key] **Prerequisites:** `bittensor[cli]` installed (includes `eth-account` for EVM signing). A configured coldkey wallet (`btcli wallet list`). ### 1. Check the EVM endpoint [#1-check-the-evm-endpoint] ```bash btcli evm doctor ``` Confirms the JSON-RPC URL, chain ID, gas price, and (if you have one) your default EVM key's balance. On localnet, start the chain first; if chain ID is unset, see [Connectivity](#connectivity) below. ### 2. Create an encrypted EVM key [#2-create-an-encrypted-evm-key] ```bash btcli evm key new -w my_coldkey btcli evm key list -w my_coldkey ``` Creates a random secp256k1 key stored as **Ethereum keystore V3** JSON next to your hotkeys (`~/.bittensor/wallets//evmkeys/`). The file imports directly into MetaMask (`btcli evm key export`). The default EVM key is **not** derived from your coldkey mnemonic — its compromise surface (browser wallets, dapp signing) stays separate from the coldkey seed. Use `btcli evm key import --mnemonic` only if you manage that seed yourself. ### 3. Fund the key from your coldkey [#3-fund-the-key-from-your-coldkey] ```bash btcli evm fund --amount-tao 1 -w my_coldkey btcli evm balance -w my_coldkey ``` `fund` transfers TAO from the coldkey to the EVM key's **ss58 mirror** (substrate extrinsic, coldkey-signed). The balance then appears on the EVM side — MetaMask shows it with **18 decimals** (see [Decimals](#the-decimals-trap)). Preview first with `--dry-run`: ```bash btcli evm fund --amount-tao 1 --dry-run -w my_coldkey ``` ### 4. (Optional) Connect MetaMask [#4-optional-connect-metamask] ```bash btcli evm config --format metamask ``` Paste the network settings into MetaMask (Add network → Add manually). Import the keystore with `btcli evm key export --out ./mykey.json`. You now have a funded EVM account on Subtensor. The sections below explain *why* funding uses a mirror address, what every other command does, and how to move TAO back to ss58. ## Two address domains [#two-address-domains] **Takeaways:** * **ss58** (`5…`) — native accounts; sign extrinsics with sr25519/ed25519. * **h160** (`0x…`) — EVM accounts; sign transactions with secp256k1. * **Mirror mapping** — fund an arbitrary EVM address: `btcli evm mirror 0x…` * **Truncated mapping** — claim a MetaMask send into your coldkey: `btcli evm deposit-address` Utility commands: ```bash btcli evm mirror 0x742d35Cc6634C0532925a3b844Bc454e4438f44e # ss58 mirror of an h160 btcli evm pubkey 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY # bytes32 for precompiles btcli evm deposit-address -w my_coldkey # where MetaMask should send ``` ## Money flows [#money-flows] ### Naming cheat sheet [#naming-cheat-sheet] | Goal | Command | Who signs | Pays gas | | -------------------------- | ------------------------- | --------- | --------- | | Coldkey → EVM key balance | `btcli evm fund` | coldkey | substrate | | EVM → EVM | `btcli evm send` | EVM key | EVM (wei) | | EVM key → any ss58 | `btcli evm send-to-ss58` | EVM key | EVM (wei) | | MetaMask deposit → coldkey | `btcli evm claim-deposit` | coldkey | substrate | **Do not confuse** `send-to-ss58` with `claim-deposit`. The first spends from a **stored EVM key** via the BalanceTransfer precompile. The second pulls a **MetaMask deposit** into your coldkey via the substrate `EVM.withdraw` extrinsic (`btcli tx evm-withdraw` is the same intent). ### Worked example: fund → send → exit to ss58 [#worked-example-fund--send--exit-to-ss58] ```bash # Fund btcli evm fund --amount-tao 2 -w my_coldkey # Send 0.5 TAO to another EVM address btcli evm send --to 0xabc… --amount-tao 0.5 -w my_coldkey # Send 0.5 TAO to a friend's ss58 (they need no EVM key) btcli evm send-to-ss58 --to 5F… --amount-tao 0.5 -w my_coldkey ``` ### Worked example: MetaMask → coldkey [#worked-example-metamask--coldkey] ```bash btcli evm deposit-address -w my_coldkey # Send TAO from MetaMask to the printed 0x deposit address btcli evm claim-deposit --amount-tao 1 -w my_coldkey ``` ## `btcli evm` command reference [#btcli-evm-command-reference] All commands accept `-w` / `--wallet`, `-n` / `--network`, `--dry-run`, `--yes`, and `--json` like the rest of btcli. EVM transaction commands also accept `--rpc-url` to override the network's JSON-RPC endpoint. ### EVM keys (`btcli evm key …`) [#evm-keys-btcli-evm-key-] | Command | What it does | | ------------ | ---------------------------------------------------------------- | | `key new` | Generate a random EVM key; store encrypted keystore V3. | | `key import` | Import from `--private-key`, `--keystore` file, or `--mnemonic`. | | `key export` | Print or write keystore JSON (`--out` writes mode `0600`). | | `key list` | Name, h160 address, and ss58 mirror for each stored key. | | `key show` | Details for one key. | `--evm-key` elsewhere accepts a key **name** (`default`) or `WALLET/NAME`. ### Address helpers [#address-helpers] | Command | What it does | | ------------------ | --------------------------------------------------------------------- | | `mirror [ADDRESS]` | ss58 mirror of an h160 (where native TAO lands for that EVM account). | | `pubkey SS58` | 32-byte public key as 0x-hex (what precompiles expect as `bytes32`). | | `deposit-address` | Truncated h160 + mirror for MetaMask → coldkey deposits. | ### Money movement [#money-movement] | Command | What it does | | --------------------------------------- | -------------------------------------------------------------------------- | | `balance [ADDRESS]` | EVM balance in TAO and wei via JSON-RPC. | | `fund --amount-tao N` | Coldkey → EVM key mirror ([`fund-evm-key`](/docs/tx/fund-evm-key)). | | `send --to 0x… --amount-tao N` | Ordinary EVM value transfer between h160 accounts. | | `send-to-ss58 --to SS58 --amount-tao N` | EVM key → ss58 via BalanceTransfer precompile. | | `claim-deposit --amount-tao N` | Coldkey claims MetaMask deposit ([`evm-withdraw`](/docs/tx/evm-withdraw)). | ### Hotkey association [#hotkey-association] | Command | What it does | | ---------------------- | --------------------------------------------------------------------- | | `associate --netuid N` | Link stored EVM key to wallet hotkey on a subnet (proof + extrinsic). | Some subnets require an associated EVM key. The command: 1. Reads substrate block height (not EVM RPC). 2. Unlocks the EVM key and produces an **EIP-191** signature over `hotkey_pubkey (32B) ++ keccak(SCALE-u64(block_number))`. 3. Submits [`associate-evm-key`](/docs/tx/associate-evm-key) signed by the hotkey. Query the link with [`associated-evm-key`](/docs/query/associated-evm-key). ### Precompiles & chain [#precompiles--chain] | Command | What it does | | ------------------------ | ---------------------------------------------------------------- | | `precompiles` | Catalog: name, address, description. | | `abi NAME` | Address + ABI JSON for Hardhat/ethers/viem. | | `call NAME [FN] [ARGS…]` | View calls free via `eth_call`; writes need `--evm-key`. | | `stake add/remove/show` | Staking-v2 precompile sugar (TAO/alpha unit conversion handled). | Examples: ```bash btcli evm call metagraph getUidCount 1 # view — no key, no gas btcli evm call staking-v2 getStake HOTKEY MIRROR 1 # view stake position btcli evm stake add --netuid 1 --hotkey 5F… --amount-tao 2 ``` ### Setup & diagnosis [#setup--diagnosis] | Command | What it does | | -------------------------- | --------------------------------------------------------- | | `networks` | Chain ID and RPC URL per preset network. | | `config --format metamask` | Paste-ready MetaMask / Hardhat / Remix settings. | | `doctor` | Probe RPC reachability, chain ID, gas price, key balance. | ## Connectivity [#connectivity] | Network | EVM RPC | Chain ID | | ---------------- | ---------------------------------- | -------------- | | Mainnet (finney) | `https://lite.chain.opentensor.ai` | 964 | | Testnet | `https://test.chain.opentensor.ai` | 945 | | Localnet | `http://127.0.0.1:9944` | none until set | Mainnet is also on [ChainList](https://chainlist.org) as chain **964**. A fresh localnet has no EVM chain ID — set one with the sudo extrinsic `adminUtils.sudoSetEvmChainId` (945 for testnet-like, 964 for mainnet-like) before MetaMask accepts the network. Override the RPC for any command: ```bash btcli evm balance --rpc-url http://127.0.0.1:9944 export BT_EVM_ENDPOINT=http://127.0.0.1:9944 # local preset default ``` ## The decimals trap [#the-decimals-trap] Native TAO has **9 decimals** (1 TAO = 1e9 rao). The EVM side uses Ethereum's **18 decimals**: 1 TAO = 1e18 wei in a transaction's `value` field. MetaMask always assumes 18, so displayed EVM balances use a different exponent than substrate wallets — **the funds are the same**. Contract code forwarding `msg.value` into a precompile must convert to rao: ```solidity uint256 amountRao = msg.value / 1e9; ``` Precompile **amount parameters** are in rao, not 1e18 TAO. ## Precompiles [#precompiles] Beyond standard Ethereum precompiles (ECRecover, Sha256, …), subtensor exposes chain operations at fixed addresses (the 20-byte zero-padded **index** — e.g. index `2053` → `0x…0805`): | Name | Index | Address | Purpose | | ---------------- | ----- | ------- | ------------------------------------------------ | | balance-transfer | 2048 | `0x800` | Send TAO from EVM to any ss58 (`msg.value`). | | staking (v1) | 2049 | `0x801` | Deprecated — use staking-v2. | | metagraph | 2050 | `0x802` | Read neuron/subnet state. | | subnet | 2051 | `0x803` | Subnet registration and owner params. | | neuron | 2052 | `0x804` | Weights, registration, serving. | | staking-v2 | 2053 | `0x805` | add/remove/move/transfer stake (amounts in rao). | | uid-lookup | 2054 | `0x806` | UIDs for an EVM address on a subnet. | | alpha | 2056 | `0x808` | Subnet alpha token info. | | crowdloan | 2057 | `0x809` | Crowdloan from EVM. | | leasing | 2058 | `0x80a` | Subnet leasing. | | proxy | 2059 | `0x80b` | Proxy delegations. | | address-mapping | 2060 | `0x80c` | On-chain h160 → mirror bytes32. | | ed25519-verify | 1026 | `0x402` | Verify ed25519 (prove ss58 ownership). | | sr25519-verify | 1027 | `0x403` | Verify sr25519. | Semantics that surprise Ethereum developers: * When a **contract** calls a precompile, the **contract address** is the coldkey — it must hold the funds. * Hotkey/coldkey arguments are **`bytes32` public keys**, not ss58 strings. `btcli evm call` converts ss58 automatically; Solidity must pass raw keys. * Amount parameters are in **rao** (see decimals above). Source: [subtensor/precompiles](https://github.com/opentensor/subtensor/tree/main/precompiles). Examples: [opentensor/evm-bittensor](https://github.com/opentensor/evm-bittensor/tree/main/examples). ## Python SDK [#python-sdk] The same layer lives in `bittensor.evm` (install `bittensor[evm]`): ```python import bittensor as sub from bittensor.evm import h160_to_ss58, association_proof from bittensor.evm.keys import create_evm_key, unlock_evm_key from bittensor.evm.precompiles import encode_call, get_precompile ``` Substrate-side intents ([`fund-evm-key`](/docs/tx/fund-evm-key), [`evm-withdraw`](/docs/tx/evm-withdraw), [`associate-evm-key`](/docs/tx/associate-evm-key)) use the normal `client.plan` / `client.execute` flow. EVM-side sends use `bittensor.evm.rpc` and `bittensor.evm.transactions` directly. ## Hosted bridges [#hosted-bridges] Third-party services (not part of this SDK): * [tao.app/bridge](https://tao.app/bridge) — TAO between substrate and EVM wallets. * vTAO — liquid-staked TAO ERC-20 on the Bittensor EVM, bridgeable to Base. ## Tooling notes [#tooling-notes] * Compile with **Solidity 0.8.24 or lower, EVM target Cancun**. Newer targets can fail deploy with `InvalidCode(Opcode)` or gas-estimation errors. * `eth_estimateGas` failures mean *any* invalid transaction — insufficient balance, bad calldata, unset chain ID, or localnet deployment whitelist — not just gas problems. * MetaMask nonces stuck after a localnet restart: Settings → Advanced → "Clear activity tab data". * Localnet contract deployment may require disabling the whitelist via sudo `evm.disableWhitelist`. ## See also [#see-also] * [Local development](/docs/guides/local-development) — run a chain for testing. * [Transfer](/docs/tx/transfer) — send TAO to an EVM mirror manually. * [Staking guide](/docs/guides/staking) — native-side stake; compare with `btcli evm stake add` for EVM-key-funded stake. # Governance (/docs/guides/governance) Bittensor governance runs through the **root network** (netuid 0) and, operationally, through **sudo**: privileged operations (runtime upgrades, protocol changes) are dispatched through sudo, held by the **Triumvirate** — a multisig of three Opentensor Foundation keys. ## The root network [#the-root-network] Registration on root is stake-based, not burn-based: [`root-register`](/docs/tx/root-register) joins a hotkey, and placement depends on the stake behind it — root slots are limited, so joining a full root network evicts the member with the least stake. Root stake also earns cross-subnet dividends; see [Staking](/docs/guides/staking#root-stake-and-dividends) for claiming. ## Voting [#voting] There is no on-chain voting today. The legacy senate-vote extrinsic — and the collective and membership pallets behind it — have been removed from the runtime, so top root members no longer form a voting senate. Privileged operations run through sudo held by the Triumvirate; the collective system below remains designed but undeployed. ## Root claims [#root-claims] Configure how root dividends pay out with [`set-root-claim-type`](/docs/tx/set-root-claim-type) and collect them with [`claim-root`](/docs/tx/claim-root). ## The planned collective system [#the-planned-collective-system] A two-stage collective governance system is designed but **not deployed on mainnet as of mid-2026** — sudo remains the operative mechanism. As designed: 1. **Triumvirate stage**: the three members have 7 days to vote; 2-of-3 ayes advance a proposal, 2-of-3 nays or timeout rejects it. The Triumvirate would no longer hold sudo directly — it becomes just the first referendum stage. 2. **Review stage**: the combined Economic and Building collectives vote during a delay that starts at 24 hours and stretches to 2 days as nay votes accumulate. At least 75% aye fast-tracks execution to the next block; at least 51% nay cancels the proposal. Absent a blocking nay majority, the proposal is enacted automatically when the delay ends — enact-by-default, not approve-to-pass. Proposals could only be submitted by a curated **Proposers** group of roughly 20 community members. The review-stage electorate is the deduplicated union of 16 Building seats (top subnet owners) and 16 Economic seats (top root validators) — at most 32 voters. The reviewing collectives rotate every 60 days: Economic seats go to the top coldkeys by a stake EMA (fed from root-network registrations), Building seats to the top subnet-owner coldkeys by their best subnet's moving price. Until those collectives are populated, the review stage adds no check on the Triumvirate. # Local development (/docs/guides/local-development) A local chain is a private, isolated subtensor: you get sudo, a pre-funded dev account, and (by default) 0.25-second blocks — the fastest way to test transactions, subnet mechanics, or SDK code without spending real TAO. ## Start a localnet with Docker [#start-a-localnet-with-docker] ```bash docker run --rm --name local_chain \ -p 9944:9944 -p 9945:9945 \ ghcr.io/opentensor/subtensor-localnet:devnet ``` Append `False` to the command to run real 12-second blocks instead of the default fast-blocks mode (0.25 s). `--rm` discards chain state when the container exits; omit it to persist state across restarts. Re-pull the image regularly — it tracks mainnet behavior. A fresh localnet ships with subnet 0 (root) and subnet 1 already created. ## Or build from source [#or-build-from-source] For custom runtime flags or chain modifications, build the node yourself: ```bash git clone https://github.com/RaoFoundation/subtensor.git cd subtensor ./scripts/init.sh # rust nightly toolchain + wasm target ./scripts/localnet.sh # build, purge state, launch in fast-blocks mode ``` `localnet.sh` accepts `False` (12-second blocks), `--no-purge` (keep existing chain state), and `--build-only` (compile and generate the chainspec without starting the node). On macOS, two prerequisites before building: Apple Silicon needs Rosetta (`softwareupdate --install-rosetta`), and OpenSSL must be installed via Homebrew. `localnet.sh` builds with the `pow-faucet` cargo feature (the `FEATURES` line in the script), which enables the `faucet` extrinsic — disabled on mainnet — so you can mint test TAO to any coldkey with a small proof-of-work. The grant is 1,000 TAO per call, a hardcoded constant in the registration pallet (`pallets/subtensor/src/subnets/registration.rs`) you can edit for more. ## Connect [#connect] The network name `local` resolves to `ws://127.0.0.1:9944`. A quick connectivity check that also tells you which block mode the chain runs ([`is-fast-blocks`](/docs/query/is-fast-blocks)): ```bash btcli query is-fast-blocks --network local ``` ```python import bittensor as sub async with sub.Client("local") as client: print(await client.block()) ``` Set `BT_CHAIN_ENDPOINT` to point `local` at a different endpoint — a source-built localnet typically listens on `ws://127.0.0.1:9945` rather than 9944\. Any `ws://` URL also works directly as the `--network` value. ## The Alice dev account [#the-alice-dev-account] Every localnet pre-funds the well-known dev account **Alice** (`5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY`) with 1,000,000 TAO. Her key is derived from the standard Substrate dev URI `//Alice`, which corresponds to a publicly known seed. Materialize her as a local wallet with that seed: ```bash btcli wallet regen-coldkey -w alice --no-password \ --seed 0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a ``` (The seed is public — it works only because localnet genesis funds this address. Never reuse dev keys outside a local chain.) In Python, any SDK call that takes a wallet also accepts a raw keypair, so you can skip wallet files entirely: ```python from bittensor.keyfiles import Keypair import bittensor as sub alice = Keypair.create_from_uri("//Alice") async with sub.Client("local") as client: result = await client.execute( sub.Transfer(dest_ss58="5F...", amount_tao=1000), alice ) ``` ## A typical development loop [#a-typical-development-loop] Create a wallet per role, fund each from Alice, then walk the subnet lifecycle: ```bash btcli wallet create -w owner -H default --no-password btcli wallet create -w validator -H default --no-password btcli wallet create -w miner -H default --no-password btcli tx transfer --dest owner --amount-tao 2000 -w alice --network local -y btcli tx transfer --dest validator --amount-tao 100 -w alice --network local -y btcli tx transfer --dest miner --amount-tao 100 -w alice --network local -y btcli tx register-subnet -w owner --network local # creates netuid 2 btcli tx start-call --netuid 2 -w owner --network local # activate emissions btcli tx burned-register --netuid 2 -w validator --network local btcli tx burned-register --netuid 2 -w miner --network local btcli query metagraph --netuid 2 --network local --json ``` See [`register-subnet`](/docs/tx/register-subnet), [`start-call`](/docs/tx/start-call), [`burned-register`](/docs/tx/burned-register), and [`metagraph`](/docs/query/metagraph) for the details of each step. **Fast-blocks caveat:** anything the chain measures in blocks — rate limits, immunity periods, activation delays, epoch tempo — elapses 48× faster than mainnet wall-clock time. If you are testing timing behavior, run the localnet with `False` (12-second blocks) instead. ## Serving real traffic: run your own node [#serving-real-traffic-run-your-own-node] Public OTF endpoints are rate-limited (roughly 1 request per second per IP). For serious validator, miner, or indexer workloads against mainnet or testnet, run your own node: a **lite node** (warp sync, \~128 GB disk, recent state only) covers most uses; an **archive node** (full history, \~3.5 TB and growing) is needed for querying blocks older than the recent window. Port 9944 is the WebSocket port and should accept only localhost connections; port 30333 is the p2p socket and must be reachable by peers. Full node operation is out of scope here — see the [subtensor repository](https://github.com/opentensor/subtensor) for setup. # Mining (/docs/guides/mining) A miner is a hotkey registered on a subnet, doing whatever work that subnet's incentive mechanism rewards. The chain side of mining is small: register, tell validators where to reach you, and don't get evicted. The work itself — the model, the service, the actual commodity — is defined by each subnet's own codebase, not by the chain. Mining (and validating) is not supported on Windows. Wallet operations work under WSL 2, but run miners on Linux or macOS. ## 1. Scope the subnet [#1-scope-the-subnet] ```bash btcli subnets list btcli subnets show 1 btcli query burn --netuid 1 --json # current registration price btcli query subnet-hyperparameters --netuid 1 --json # immunity period, limits btcli query metagraph --netuid 1 --json # who you're competing with ``` Choosing where to start matters more than the registration price. Eviction only happens when a subnet is at capacity, so an under-capacity or new subnet lets you learn without deregistration risk; a mature high-emission subnet means competing against miners tuned over months. Each subnet's repo conventionally documents hardware needs (`min_compute.yml` or the README), and most subnets run on a testnet netuid — test there or on a local chain before paying the registration burn. Note also whose hotkey you register: mining on the subnet owner's hotkey (or its associated hotkeys) is pointless — miner emission directed at owner-associated hotkeys is never paid out, it is burned or recycled (burned by default, per the subnet's `RecycleOrBurn` setting). ## 2. Register [#2-register] [`burned-register`](/docs/tx/burned-register) burns the subnet's current registration cost from your coldkey and assigns your hotkey a **UID**: ```bash btcli tx burned-register --netuid 1 --dry-run -w my_coldkey btcli tx burned-register --netuid 1 -w my_coldkey ``` ```python price = await client.read("burn", netuid=1) # current registration cost await client.execute(sub.BurnedRegister(netuid=1), wallet) ``` The UID goes to the wallet's hotkey by default; pass `hotkey_ss58` to register a different one. Registration is continuous — there are no registration windows. Admission is governed entirely by a dynamic burn price: * The price **decays over time**, halving every `BurnHalfLife` blocks (default 360, about one tempo; owner-settable). * Each successful registration **multiplies the price** by `BurnIncreaseMult` (default 1.26), so a registration rush gets expensive fast. * The price is clamped between `MinBurn` (default 500,000 rao = 0.0005 TAO) and `MaxBurn` (default 100 TAO). All three are per-subnet hyperparameters. Read the live price with [`burn`](/docs/query/burn). Know before you send: * The burned TAO is **recycled, not staked** — deregistering does not refund it. Under the hood the TAO is swapped into the subnet's pool and the resulting alpha is removed from the subnet's outstanding supply. * The cost floats and is only known at execution time, so a configured `Policy` spend cap blocks this call until raised. Confirm with [`uid`](/docs/query/uid) or [`netuids-for-hotkey`](/docs/query/netuids-for-hotkey). One hotkey can hold UIDs on multiple subnets simultaneously, but only one UID per subnet. ## 3. UID capacity and eviction [#3-uid-capacity-and-eviction] A subnet has a fixed number of UID slots: default 256, owner-trimmable down to a floor of 64. When the subnet is full, each new registration **evicts one existing neuron** — the one with the lowest pruning score that is not immune. The rules, as implemented in the chain's pruning logic: * The pruning score is **emission-based**. The chain does not distinguish miners from validators when pruning; whoever earns least is first out. * Immunity is `(current_block - registered_at) < immunity_period`. The chain default is 4096 blocks (about 13.7 hours at 12-second blocks), but real subnets often set it much higher — read the actual value with [`immunity-period`](/docs/query/immunity-period) before you plan around it. * If every neuron is still immune, the lowest-scoring immune neuron is pruned anyway; immunity is a preference, not a guarantee. * Ties on pruning score evict the **older** registration first. * The subnet owner's hotkey is permanently immune (bounded by an owner-immune limit, default 1 hotkey). Practical consequence: your immunity period is your runway. If the subnet's `immunity_period` is 4096 blocks, you have about half a day to start earning emissions before you're evictable. ## 4. Publish your endpoint [#4-publish-your-endpoint] Validators find you through the axon info stored on chain. [`serve-axon`](/docs/tx/serve-axon) writes your `ip:port`; it does **not** start a server — running the actual service is your job: ```bash btcli tx serve-axon --netuid 1 --ip 203.0.113.7 --port 8091 -w my_coldkey -H my_hotkey ``` Use [`serve-axon-tls`](/docs/tx/serve-axon-tls) if peers should verify a TLS certificate, and [`reset-axon`](/docs/tx/reset-axon) to stop advertising. Re-publishing is rate-limited by the chain. **Moving to another machine:** start the miner on the new machine and publish the new axon first, wait until the old machine stops receiving validator requests, then stop it. Validators take time to pick up an updated IP. All miner traffic comes through validators — only they can score you, so there is no reason to serve anyone else. Requests transit validators and miners with no confidentiality guarantee in either direction; subnets are not a channel for private data. ## 5. Watch your standing [#5-watch-your-standing] ```bash btcli query metagraph --netuid 1 --json # your rank, incentive, emission btcli query blocks-since-last-update --netuid 1 --json btcli query immunity-period --netuid 1 ``` Expect discovery lag. Emissions are credited at **tempo boundaries** (default tempo 360 blocks, about 72 minutes), and validators typically refresh their metagraph periodically rather than every block — a freshly registered miner can sit at zero for a while before validators start querying it. That lag is exactly what the immunity period is for. When the subnet runs commit-reveal, add the reveal period to every number you watch: validator scores on chain are stale by that window, so do **not** use your on-chain score as a liveness signal — by the time it drops, the failure is old and deregistration is likely already unavoidable. Monitor your own service directly. Longevity compounds. Validators' bonds toward a miner build up as an exponential moving average, so an established miner out-earns an identical newcomer until the newcomer's bonds mature — see [emissions](/docs/concepts/emissions) for the bond math. Rewards accrue to the hotkey as stake. Have them auto-staked to a hotkey of your choice with [`set-auto-stake`](/docs/tx/set-auto-stake). ## Key hygiene [#key-hygiene] * Sign operational calls with the hotkey; keep the coldkey off the mining box entirely. Mining stacks pull in large untrusted dependency trees (ML frameworks, model code), and any of it could exfiltrate a key file — see [wallets](/docs/concepts/wallets) for the coldkey/hotkey split. Use a [proxy](/docs/concepts/advanced) if the box must submit coldkey-signed calls. * Create hotkeys on a trusted machine and transfer only the hotkey file (or mnemonic) to the miner. * If the hotkey is compromised, [`swap-hotkey`](/docs/tx/swap-hotkey) replaces it while keeping its registrations. # Staking (/docs/guides/staking) Staking backs a validator (a **hotkey**) with your TAO. On a subnet, staking swaps TAO into the subnet's alpha at the pool price; your position's value then follows the pool price and the validator's performance. On the root network (netuid 0) stake stays TAO-denominated. ## Pick a validator [#pick-a-validator] ```bash btcli query delegates --json # all delegate hotkeys btcli query delegate --hotkey 5F... # one delegate's details btcli query delegate-take --hotkey 5F... # the fraction the delegate keeps ``` The **take** is the fraction of staking emissions the validator keeps before distributing the rest to its nominators. The chain default is 18% (stored as 11796/65535); a delegate can lower it any time with [`decrease-take`](/docs/tx/decrease-take), but raising it ([`increase-take`](/docs/tx/increase-take) / [`set-take`](/docs/tx/set-take)) is rate-limited to once per 216,000 blocks (\~30 days). Read the current value with [`delegate-take`](/docs/query/delegate-take). ## Stake and unstake [#stake-and-unstake] Quote first — staking is a swap, so large amounts incur slippage: ```bash btcli query quote-stake --netuid 1 --amount-tao 100 --json btcli tx add-stake --hotkey 5F... --netuid 1 --amount-tao 100 --dry-run btcli tx add-stake --hotkey 5F... --netuid 1 --amount-tao 100 -w my_coldkey ``` ```python intent = sub.AddStake(hotkey_ss58="5F...", netuid=1, amount_tao=100) await client.execute(intent, wallet) ``` Every stake operation must move at least the chain minimum — by default 2,000,000 rao (0.002 TAO), stored in `MinStake`. The chain can also sweep nominations that fall below a nominator minimum (a governance-set fraction of the minimum stake; currently zero, so the sweep is effectively disabled). Exit with [`remove-stake`](/docs/tx/remove-stake), or exit everything at once with [`unstake-all`](/docs/tx/unstake-all). Quote the exit the same way you quote the entry: ```python quote = await client.read("quote_unstake", netuid=1, amount_alpha=50) # TAO out, fee, slippage await client.execute(sub.RemoveStake(hotkey_ss58="5F...", netuid=1, amount_alpha="all"), wallet) ``` `"all"` resolves to the full position at build time — only the remove-stake variants accept it. To bound the execution price instead of accepting whatever the pool gives, use [`add-stake-limit`](/docs/tx/add-stake-limit) / [`remove-stake-limit`](/docs/tx/remove-stake-limit). Compute the limit from the spot price and your tolerance (see [how money moves](/docs/concepts/money) for the pool mechanics): ```python spot = await client.read("alpha_price", netuid=1) intent = sub.AddStakeLimit( hotkey_ss58="5F...", netuid=1, amount_tao=100, limit_price_rao=int(spot["price_rao"] * 1.02), # accept at most 2% above spot allow_partial=False, ) await client.execute(intent, wallet) ``` There is no unbonding period: plain unstaking settles immediately, with the TAO spendable at once. The only time constraints in the staking system are conviction locks (opt-in, below) and the rate limits on child and take changes. ## Move positions without exiting [#move-positions-without-exiting] * [`move-stake`](/docs/tx/move-stake) — different hotkey (and optionally subnet). * [`swap-stake`](/docs/tx/swap-stake) — same hotkey, different subnet. * [`transfer-stake`](/docs/tx/transfer-stake) — different coldkey (gives the position away). ```python intent = sub.MoveStake(origin_hotkey_ss58="5F...", origin_netuid=1, dest_hotkey_ss58="5G...", dest_netuid=1, amount_alpha=25) await client.execute(intent, wallet) ``` Same-subnet moves just change which validator backs the stake; cross-subnet moves swap through both pools and can incur slippage on each leg. ## Inspect what you have [#inspect-what-you-have] ```bash btcli stake show --hotkey 5F... --netuid 1 btcli query stake-for-coldkey --coldkey my_coldkey --json # all positions btcli query stake-value-for-coldkey --coldkey my_coldkey --json # marked to TAO at spot ``` Spot valuation is `alpha × price`, excluding slippage and fees — use [`quote-unstake`](/docs/query/quote-unstake) for what you would actually receive. ## Conviction locks [#conviction-locks] [`lock-stake`](/docs/tx/lock-stake) commits alpha on a subnet as **locked mass** — a floor on unstaking that accrues **conviction** toward a target hotkey over time. Locked alpha keeps earning rewards; locking changes liquidity, not emissions. For a full walkthrough with interactive charts on a worked example subnet, see the [Conviction locks guide](/docs/guides/conviction). Quick reference: * One lock per coldkey per subnet; top-ups add mass, conviction continues. * **Perpetual** vs **decaying** modes — opt in with [`set-perpetual-lock`](/docs/tx/set-perpetual-lock). * The two timescales are governance-set storage values, not fixed constants. On mainnet today `MaturityRate` is 311,622 blocks (\~43 days) and `UnlockRate` is 934,866 blocks (\~130 days) — read them live before planning around either. * Aggregate conviction can trigger **subnet ownership** after one year when total conviction ≥ 10% of `SubnetAlphaOut`. * Query with [`subnet-convictions`](/docs/query/subnet-convictions), [`hotkey-conviction`](/docs/query/hotkey-conviction), [`coldkey-lock`](/docs/query/coldkey-lock). ## Root stake and dividends [#root-stake-and-dividends] Stake on the root network earns dividends from across subnets. Each subnet's root dividends are split between TAO and alpha according to the root proportion of stake, and the alpha part accrues per subnet until claimed. Unlike subnet staking, root staking and unstaking is not a pool swap: no swap fee, no slippage, no MEV exposure — you move TAO in and out at face value. See [how money moves](/docs/concepts/money) for the swap mechanics that root stake skips. How the alpha pays out is your choice, set with [`set-root-claim-type`](/docs/tx/set-root-claim-type): **Swap** (the chain default) sells the alpha dividends into TAO on your root position, **Keep** restakes them as alpha on the subnet, and a per-subnet map mixes the two. Read your setting with [`root-claim-type`](/docs/query/root-claim-type). You normally don't need to claim at all: every block the chain picks a handful of staking coldkeys pseudo-randomly and processes any of their accrued dividends that exceed the per-subnet claim threshold (default 500,000 rao; settable by the subnet owner or root). Each account comes up automatically every few days, depending on how many staking coldkeys exist. Manual [`claim-root`](/docs/tx/claim-root) is optional — it just claims sooner and takes at most 5 subnets per call. Unclaimed dividends keep accruing — there is no deadline. For small positions the extrinsic fee of a manual claim can exceed the dividends it collects — let the automatic sweep handle small amounts. ## Child hotkeys [#child-hotkeys] A validator can delegate a fraction of its stake weight to **child hotkeys** per subnet with [`set-children`](/docs/tx/set-children). Two reasons to do this: confine a hotkey compromise to a single subnet (one child per subnet instead of one hotkey exposed everywhere), or lend stake weight to another operator without moving stake. The chain enforces: * At most **5 children per netuid**; a child can have multiple parents. Proportions cannot be zero, and any proportion not assigned to children stays with the parent. * Setting or revoking children is rate-limited to once per 150 blocks (\~30 minutes) per hotkey per subnet, and changes only take effect after a cooldown (default 7,200 blocks, \~24 hours) — see [`pending-children`](/docs/query/pending-children). * The child's take ([`set-childkey-take`](/docs/tx/set-childkey-take)) ranges 0–18% and defaults to 0; increases are rate-limited to once per 216,000 blocks (\~30 days). * The parent must hold a minimum total stake (the chain's `StakeThreshold`, set by governance — read it live rather than assuming a value). Dividends settle back along the same links: a child hotkey's dividends are split among its parents in proportion to the stake weight each contributed (alpha plus TAO × `TaoWeight`), after deducting the childkey take. The deducted take is added to the child validator's own dividends — and since those distribute to the child's nominators like any validator dividends, most of the take flows onward to that validator's stakers rather than its operator. Because stake weight flows without infrastructure, a coldkey can act as a stakeless "validator": hold stake on its own hotkey and childkey-delegate the weight to operating validators, running no machines at all. Inspect relationships with [`children`](/docs/query/children) and [`parents`](/docs/query/parents). ## Automation [#automation] [`set-auto-stake`](/docs/tx/set-auto-stake) makes future rewards on a subnet stake themselves to a hotkey of your choice instead of accumulating unstaked. ## Security [#security] Two habits for meaningful position sizes: * **Bound the price and shield the submission.** These protect against different things: limit variants ([`add-stake-limit`](/docs/tx/add-stake-limit)) cap slippage however it arises, while [MEV-shielded submission](/docs/concepts/advanced#mev-shielded-submission) hides the transaction from front-runners until inclusion. Use both. * **Use delayed proxies, and monitor them.** A leaked zero-delay `Staking` proxy cannot transfer your balance, but it can still drain value by forcing repeated high-slippage stake/unstake round trips that counterparties profit from. Give staking proxies an announcement delay and watch for announcements you didn't make — see [proxies](/docs/concepts/advanced#proxy-keep-the-coldkey-offline). # Running a subnet (/docs/guides/subnets) A subnet owner defines an incentive mechanism: what miners must produce and how validators must score it. The chain side — creating the subnet, tuning its hyperparameters, publishing its identity — is all reachable through the SDK. The mechanism itself is code you write and run off-chain. ## Create [#create] [`register-subnet`](/docs/tx/register-subnet) creates a new subnet owned by your coldkey, with your wallet's hotkey as the subnet-owner hotkey: ```bash btcli query subnet-registration-cost --json # check the lock cost FIRST btcli tx register-subnet --dry-run -w my_coldkey btcli tx register-subnet -w my_coldkey ``` The cost **doubles** each time anyone registers a subnet, then decays linearly back over the lock-reduction interval (100,800 blocks, \~2 weeks), floored at `NetworkMinLockCost` — 1,000 TAO by default. It is only known at execution time, so a `Policy` spend cap blocks this call until raised; read the live price with [`subnet-registration-cost`](/docs/query/subnet-registration-cost). Subnet registrations are also chain-rate-limited to one per 7,200 blocks (\~1 day) network-wide. The lock is not a refundable deposit. The **entire** lock is transferred into the new subnet's pool as its initial TAO reserve (floored at `NetworkMinLockCost`); the matching alpha reserve is sized so the starting price equals the median alpha price across subnets. Nothing is recycled at registration. You get it back only by owning a subnet that earns. Identity can be set atomically at registration: the chain's `register_network_with_identity` extrinsic takes an optional identity record. `register-subnet` wraps plain `register_network`, so the identity variant needs the [raw-call escape hatch](/docs/concepts/advanced). A new subnet is a two-step commitment: register, then activate with [`start-call`](/docs/tx/start-call) — until then it earns nothing. ## Activate [#activate] Once the chain's activation delay has passed, flip the subnet on with [`start-call`](/docs/tx/start-call) — owner-only, once per subnet: ```bash btcli tx start-call --netuid 42 -w my_coldkey ``` Check the schedule with [`subnet-start-schedule`](/docs/query/subnet-start-schedule). `start-call` enables the subnet's token and starts its epochs, but the subnet's share of network TAO emission is a separate root-controlled switch: new subnets register with emission disabled, and only root (via `sudo_set_subnet_emission_enabled`) turns it on. Until then the subnet earns no TAO emission share. ## The subnet economy [#the-subnet-economy] Each subnet is an automated market maker with two reserves: TAO in and alpha in. The alpha price is the pool's weighted reserve ratio — exactly TAO-in / alpha-in at the default 0.5/0.5 pool weights — and moves on every stake and unstake; emission injections shift the pool weights instead of the price. The subnet's share of network TAO emissions follows the exponential moving average of that price — stakers vote with their TAO — so a subnet's income is set by market demand for its token, not by governance. See [emissions](/docs/concepts/emissions) for the full mechanics. Each tempo the subnet's alpha emission splits three ways: 18% to the owner, \~41% to miners (incentive), \~41% to validators and their stakers (dividends). The owner cut can be auto-locked into a conviction position via the `owner_cut_auto_lock_enabled` hyperparameter instead of arriving as free stake; conviction locking and its unlock schedule are covered in the [staking guide](/docs/guides/staking). Three owner habits worth adopting. Decide your `registration_allowed` policy before activation — owners pause registration during mechanism upgrades so participants can update without eviction risk, and the toggle is root-gated here, so plan ahead. Run a validator on the owner hotkey: the owner cut and validation rewards land on the same key, and the 18% cut can be staked behind it. Do **not** mine on the owner hotkey — miner emission directed at owner hotkeys is burned or recycled, never paid. ## Tune hyperparameters [#tune-hyperparameters] Owner-settable hyperparameters go through [`set-hyperparameter`](/docs/tx/set-hyperparameter) (the equivalent of btcli's `sudo set`); read them back with [`subnet-hyperparameters`](/docs/query/subnet-hyperparameters): ```bash btcli query subnet-hyperparameters --netuid 42 --json btcli tx set-hyperparameter --netuid 42 --name commit_reveal_weights_enabled --value 1 -w my_coldkey ``` The important owner-settable parameters, with chain defaults: | Parameter | Default | What it does | | ------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tempo` \* | 360 | Blocks per epoch. Owner changes bounded 360–50,400. | | `immunity_period` | 4,096 | Blocks a fresh UID is protected from pruning and trimming. | | `min_burn` / `max_burn` | 0.0005 τ / 100 τ | Clamp on the neuron registration price. Read the live price with [`burn`](/docs/query/burn). | | `burn_half_life` | 360 | Blocks for the registration price to decay halfway toward `min_burn` when nobody registers. | | `burn_increase_mult` | 1.26 | Multiplier applied to the registration price after each successful registration. | | `max_allowed_uids` | 256 | UID slots. Floor 64; times mechanism count must stay ≤ 256. | | `activity_cutoff_factor` \* | 13,889 | Per-mille of tempo; effective cutoff = factor × tempo / 1000 blocks (5,000 at tempo 360; bounds 1,000–50,000). A validator that hasn't set weights within the cutoff sits out until next epoch. Replaces the deprecated `activity_cutoff` (5,000). | | `serving_rate_limit` | 50 | Minimum blocks between `serve-axon` / `serve-prometheus` calls per key. | | `commit_reveal_weights_enabled` | true | Weights are committed hashed, revealed later. | | `commit_reveal_period` | 1 | Tempos between commit and reveal. | | `liquid_alpha_enabled` | false | Per-weight-pair bond smoothing between `alpha_low` and `alpha_high`. Only takes effect with `yuma3_enabled` on. | | `alpha_values` \* | 0.7 / 0.9 | The `alpha_low` / `alpha_high` bounds used by liquid alpha. | | `yuma3_enabled` | false | Runs Yuma Consensus v3 instead of v2. | | `bonds_moving_avg` | 900,000 | Bond EMA window; larger is smoother and slower, smaller is faster and more volatile. | | `transfers_enabled` | true | Allows stake transfers on the subnet. | | `recycle_or_burn` \* | burn | Whether miner emission directed at burn UIDs is destroyed or recycled back into the pool. | | `owner_cut_auto_lock_enabled` | false | Auto-locks the owner cut into conviction instead of paying it as free stake. | | `immune_owner_uids_limit` \* | 1 | How many owner hotkeys are permanently immune from pruning and trimming (`ImmuneOwnerUidsLimit`). Owner-raisable to a maximum of 10. | Parameters marked \* have their own extrinsics on chain rather than a name in `set-hyperparameter`; if the SDK doesn't expose one, use the [raw-call escape hatch](/docs/concepts/advanced). Chain reads also return `rho`, `difficulty`, `adjustment_interval`, `adjustment_alpha`, and `target_regs_per_interval` — legacy registration and consensus parameters that still exist as storage but no longer drive neuron admission; the burn price decay/bump mechanism replaced the old PoW and adjustment-interval machinery. (`max_registrations_per_block` is still enforced.) Two chain-side constraints on owner changes: * **Rate limit** — each hyperparameter can be changed roughly once per two tempos (default `OwnerHyperparamRateLimit` = 2), tracked independently per parameter, so you can change several different parameters in one tempo but not the same one twice. * **Freeze window** — owner admin calls are rejected during the last \~10 blocks of each tempo (`AdminFreezeWindow`), so parameters can't change while an epoch is being computed. Root-only parameters — `kappa` (default 32,767 ≈ 0.5), `max_allowed_validators` (128), `weights_set_rate_limit` (100 blocks between weight submissions), tempo outside the owner bounds, `registration_allowed`, and others — need root privileges and the [raw-call escape hatch](/docs/concepts/advanced). ## Trim UIDs [#trim-uids] [`trim-subnet`](/docs/tx/trim-subnet) lowers `max_allowed_uids` and evicts the excess neurons. Rules: * Rate-limited to one trim per 216,000 blocks (\~30 days). * Victims are the lowest-emission UIDs; temporally immune UIDs (within `immunity_period`) and owner-immune UIDs are skipped, and immune UIDs may not exceed 80% of the new maximum. * You cannot trim below 64 UIDs. * Surviving UIDs are renumbered consecutively from 0 — a miner's UID can change even if it survives. * A trimmed UID's on-chain state (hotkey mapping, registration block, weights, bonds, axon info) is permanently deleted. ## Mechanisms [#mechanisms] A subnet can run multiple scoring mechanisms, each with its own weights. [`set-mechanism-count`](/docs/tx/set-mechanism-count) and [`set-mechanism-emission-split`](/docs/tx/set-mechanism-emission-split) configure them; validators target one via the `mechid` field on [`set-weights`](/docs/tx/set-weights). Read the current count with [`mechanism-count`](/docs/query/mechanism-count). * The chain currently caps subnets at **2** mechanisms. * Each mechanism runs Yuma Consensus independently, with its own weight matrix and separate bond pools. Miners keep one UID across all mechanisms — no separate registration — and their total emission is the sum across mechanisms. * The same applies to validators: emission is computed per mechanism from that mechanism's own consensus, then combined according to the emission split. * Mechanisms were formerly called "subsubnets"; the old name survives in some APIs and code comments. * `max_allowed_uids` × mechanism count must stay ≤ 256; reduce `max_allowed_uids` first if raising the count would exceed it. * The emission split is a vector of u16 weights summing to 65,535 (e.g. `[13107, 52428]` is 20%/80%); unset means an even split. Changing the mechanism count resets the split to even. * Count and split changes are each rate-limited to once per 7,200 blocks (\~24 hours). ## Deregistration [#deregistration] There is no inactivity-based deregistration; pruning is price-based. When the subnet limit (128) is reached, the next [`register-subnet`](/docs/tx/register-subnet) prunes the non-immune subnet with the lowest EMA alpha price (ties go to the oldest) and reuses its netuid. New subnets are immune for 1,296,000 blocks (\~6 months) from registration; if every subnet is immune the new registration fails instead. Price-ranked deregistration has history: it existed pre-dTAO (ranked by emission then), was removed at dTAO's launch in February 2025, and returned price-ranked in September 2025. Deregistering a subnet deregisters all of its neurons with it — every UID's on-chain state is deleted along with the subnet. On dissolution the subnet's TAO reserve is distributed pro-rata over all alpha — credited directly to each holder's coldkey balance, not restaked. Two caveats: protocol-held alpha (the pool's alpha-in plus alpha bought back by the chain) counts in the denominator and its share returns to the chain, diluting holder payouts; and conviction locks are deleted before liquidation — locked stake is paid out like any other, but accumulated conviction is not compensated. Owners of subnets registered since dTAO get no lock refund. The payout can also cut the other way: liquidation pays the pool's TAO-per-alpha rate, so if that exceeds the market price at dissolution, holders receive more than market value. ## Identity and token [#identity-and-token] * [`set-subnet-identity`](/docs/tx/set-subnet-identity) — the subnet's public profile (name, links, contact, logo). Public and permanent; each call overwrites the whole record. * [`update-symbol`](/docs/tx/update-symbol) — the subnet token's ticker, chosen from a fixed on-chain list of available symbols. The most common identity mistake: `logo_url` must point at a raw image file (e.g. a raw\.githubusercontent.com URL), not an HTML page that displays the image. ## Financing: leases and crowdloans [#financing-leases-and-crowdloans] A subnet can be financed collectively: create a crowdloan ([`create-crowdloan`](/docs/tx/create-crowdloan), contributions via [`contribute-crowdloan`](/docs/tx/contribute-crowdloan)) and register the network under a lease that shares emissions with contributors ([`register-leased-network`](/docs/tx/register-leased-network), ended with [`terminate-lease`](/docs/tx/terminate-lease)). Inspect with the [`crowdloans`](/docs/query/crowdloans) and [`leases`](/docs/query/leases) reads. Economics worth knowing before committing: * The lease's `emissions_share` is a percentage **of the owner cut** (18%), not of total emissions: a 50% share pays contributors 9% of the subnet's emission. * Contributor dividends are swapped from alpha to TAO and paid automatically during coinbase, on a fixed distribution interval (100 blocks), pro-rata by contribution. If pool liquidity is too thin to swap, the alpha accumulates for a later interval. * At lease creation, whatever remains of the raised cap after paying the registration cost is refunded to contributors pro-rata (rounding remainder to the beneficiary) — so set the cap above the projected lock cost, not wildly above it. * Crowdloans must run between 50,400 and 432,000 blocks (7–60 days). Contributions are clipped to the remaining cap rather than rejected. Refunds after a failed raise process at most 50 contributors per [`refund-crowdloan`](/docs/tx/refund-crowdloan) call, so large campaigns need several. ## Bootstrapping validation [#bootstrapping-validation] A brand-new subnet has no validators, and the owner gets no special treatment: setting weights requires meeting the same stake threshold as any validator (see [validating](/docs/guides/validating)). The common tactic is to ask an established root validator to parent your validator hotkey as a childkey ([`set-children`](/docs/tx/set-children)), lending it stake weight for consensus participation until the subnet attracts its own stake. # Timelock (/docs/guides/timelock) Timelock encryption binds data to a future round of the drand **quicknet** randomness beacon. Once that round's signature is published — every 3 seconds, worldwide, no keys or accounts involved — anyone can decrypt. Before then, nobody can, including the author. This is the same mechanism the chain uses for commit-reveal weights, exposed directly so you can timelock anything. The reveal moment is wall-clock time, not chain blocks: a round happens every 3 seconds regardless of block speed, so `reveal_in="1h"` means one hour on any network, including fast-blocks local chains. Encryption is pure local cryptography — no network, no keys, nothing to keep secret afterwards except the plaintext. Only decryption fetches the round signature from the drand HTTP API. ## Python [#python] ```python from bittensor import timelock sealed = timelock.encrypt("the answer is 42", reveal_in="1h") sealed.reveal_at # datetime of the reveal, UTC sealed.remaining # timedelta until then; zero once passed sealed.revealed # False until the round is published sealed.hex() # portable ciphertext — publish it anywhere ``` The hex is all anyone needs — the reveal round is embedded in the ciphertext, so there is no key or metadata to carry alongside it. Decrypt from the hex, a `Timelocked`, or raw bytes: ```python timelock.decrypt(sealed_hex) # raises TimelockNotReady if early timelock.decrypt(sealed_hex, wait=True) # sleeps until the reveal, then opens sealed.decrypt(wait=True) # same, as a method ``` `TimelockNotReady` carries `.reveal_round`, `.reveal_at`, and `.remaining`, so callers can schedule a retry instead of polling. A `timeout=` caps the total wait in seconds, and `timelock.decrypt_async` does the same on the event loop. Give the reveal moment exactly one way: ```python timelock.encrypt(data, reveal_in="15m") # duration: "30s", "1h30m", "2d", seconds, timedelta timelock.encrypt(data, reveal_at="2026-07-08T12:00Z") # absolute: datetime or ISO-8601, no offset = UTC timelock.encrypt(data, reveal_round=30212846) # explicit drand round ``` Round arithmetic is exact local math from the quicknet genesis (3 seconds per round), available directly: ```python timelock.current_round() # latest published round, computed locally timelock.round_at("2026-07-08T12:00Z") # first round at or after a moment timelock.reveal_time(30212846) # UTC datetime a round is published ``` ## CLI [#cli] ```bash btcli timelock encrypt "the answer is 42" --in 1h # prints ciphertext hex btcli timelock encrypt --file plan.pdf --in 2d --out plan.sealed btcli timelock encrypt "..." --at 2026-07-08T12:00Z # or --round 30212846 ``` Hex goes to stdout (pipeable); the reveal note goes to stderr. `--out` writes raw ciphertext bytes to a file instead of printing hex. ```bash btcli timelock decrypt # fails with the reveal time if early btcli timelock decrypt plan.sealed --wait --timeout 600 btcli timelock show # when it unlocks, without decrypting ``` `decrypt` and `show` take the hex or a file path as the argument (or `--file`); files may hold raw bytes or hex text. `decrypt --out` writes the plaintext bytes to a file — useful for binary payloads. ## Use cases [#use-cases] * **Sealed challenge answers** — a subnet mechanism publishes the answer timelocked, so miners provably cannot have seen it early. * **Embargoed announcements** — publish now, readable at the embargo time, with no trusted party holding the key. * **Sealed-bid auctions** — all bids open simultaneously at the deadline. * **Delayed configuration** — ship a config that activates itself at a known moment. ## Relationship to weight commit-reveal [#relationship-to-weight-commit-reveal] Validator weights use this machinery automatically: the chain timelocks committed weights and reveals them on schedule, with no action needed from you — see [`commit-weights`](/docs/tx/commit-weights) and the [validating guide](/docs/guides/validating). This page is for timelocking your own data. ## Mechanics [#mechanics] * The ciphertext carries its reveal round in a trailer, so no separate bookkeeping is needed — parsing the hex recovers everything. * Round arithmetic never touches the network: rounds map to timestamps by exact local math from the quicknet genesis. * After the reveal time passes, the beacon or HTTP relay may lag by a few seconds; decryption retries within a short grace window before giving up. # Validating (/docs/guides/validating) A validator is a hotkey that scores miners. It needs enough stake behind it to hold a validator permit on the subnet, and it earns dividends (shared with its nominators) for setting weights that agree with consensus. ## Get in position [#get-in-position] Register the hotkey on the subnet like any neuron ([`burned-register`](/docs/tx/burned-register)), then accumulate stake — your own ([`add-stake`](/docs/tx/add-stake)) and delegated. Whether you hold a validator permit is visible in the [`metagraph`](/docs/query/metagraph). To attract delegation, publish who you are with [`set-identity`](/docs/tx/set-identity) and set your [take](/docs/tx/set-take) — the fraction of staking emissions you keep before distributing the rest to nominators. Take increases are rate-limited and capped by the chain; [`set-take`](/docs/tx/set-take) dispatches whichever of `increase_take`/`decrease_take` reaches your target. Validators are also the gateway to a subnet: external applications can only query miners through a validator's registered hotkey, which is why validators build API businesses on top of their position. Stake cuts both ways here — miners prioritize queries from higher-stake validators, and on some subnets miners ignore weak validators entirely. The chain minimum is far below the practical bar. Meeting the stake threshold and landing a top-K permit gets you consensus participation, not competitive returns — validating seriously typically needs five figures of TAO-equivalent stake (the community rule of thumb is \~20k+). And because root-validator returns are additive across every subnet the validator serves, serious validators register broadly rather than on one subnet. ## Permits [#permits] Every epoch, the chain recomputes validator permits from the current stake distribution: the top **K** neurons by stake weight get one, where K is the `MaxAllowedValidators` hyperparameter (default 128; owner/root settable — read the live value via [`subnet-hyperparameters`](/docs/query/subnet-hyperparameters)). A chain-wide minimum stake weight (`StakeThreshold`) also applies: stake below it counts as zero, and it doubles as the minimum stake to set weights at all. The runtime default is zero, but mainnet runs a non-zero threshold (around 1,000 TAO-equivalent at the time of writing). Stake weight is not raw TAO. It is the alpha staked to your hotkey on that subnet, plus your TAO stake discounted by the global `TaoWeight` factor (governance-set; currently 0.18 on mainnet, against a runtime default of \~0.053) — so alpha counts far more than TAO. The subnet owner's UID is exempt from both gates: it always receives a validator permit, and its stake is not zeroed by the threshold. The permit gates four things: setting weights on other neurons (any neuron can self-weight), participation in Yuma Consensus, retaining bonds, and counting toward active stake. Losing the permit — falling out of the top K or below the threshold — does **not** deregister you, but it stops your dividends and **deletes your bonds**, so you rebuild from zero when you regain it. With no emissions, a permitless validator eventually drifts to the bottom of the pruning order like any idle neuron. One more clock to respect: the activity cutoff. It scales with the tempo — cutoff = `activity_cutoff_factor` × tempo / 1000 blocks, with the factor defaulting to 13,889 (≈5,000 blocks at the default tempo of 360). A validator that hasn't submitted weights within that window is treated as inactive — its stake is masked out of consensus until it submits again. ## Set weights [#set-weights] [`set-weights`](/docs/tx/set-weights) is the one entry point. It conforms your weights to the subnet's hyperparameters (max-weight clip, u16 quantization, minimum weight count) and submits via whichever path the subnet runs — plain weights when commit-reveal is off, or a timelock-encrypted commit (auto-revealed by the chain) when it is on. `SetWeights` takes the scores in either shape — a `{uid: weight}` mapping (preferred) or parallel `uids`/`weights` lists: ```python intent = sub.SetWeights(netuid=1, weights={0: 0.1, 1: 0.7, 2: 0.2}) intent = sub.SetWeights( # same submission, parallel lists netuid=1, uids=[0, 1, 2], weights=[0.1, 0.7, 0.2], ) result = await client.execute(intent, wallet) # signed by the hotkey ``` Values are **relative**, not absolute — only the proportions matter. Before submission they are clipped to the subnet's max-weight limit, normalized, and quantized to u16 (zeros dropped); the canonical implementation is `normalize` in `bittensor.intents` if you want to reproduce exactly what hits the chain. On subnets running multiple [mechanisms](/docs/guides/subnets#mechanisms), pass `mechid=` to score one mechanism (default 0 — for most subnets the only one): ```python intent = sub.SetWeights(netuid=1, mechid=1, weights={0: 0.4, 3: 0.6}) ``` On the CLI, the dedicated `btcli weights` group and the generated `btcli tx set-weights` submit the same operation — the group just adds convenience (comma-separated lists, weight-specific help): ```bash btcli weights set --netuid 1 --uids 0,1,2 --weights 0.5,0.3,0.2 -w my_coldkey -H my_hotkey btcli tx set-weights --netuid 1 --uids 0,1,2 --weights 0.5,0.3,0.2 -w my_coldkey -H my_hotkey ``` `btcli weights set` takes `--netuid`, `--uids`, `--weights` (comma-separated, parallel), and optionally `--mechid` and `--version-key`, plus the usual global wallet/network flags. Before signing, the SDK preflights the checks the chain would reject on — a rate-limited or unregistered submission fails fast with the same error the chain would return, and the rate-limit error says how many blocks to wait. Chain-side rules to know: * Submissions are rate-limited per UID by `WeightsSetRateLimit` (default 100 blocks between sets) — read it with [`weights-rate-limit`](/docs/query/weights-rate-limit). * Every submission carries a **weights version key**. If the subnet's stored version key is higher than yours, the call fails with `IncorrectWeightVersionKey`. Subnet owners bump the key to force validators onto new subnet code — if you hit this error, update your subnet software. * Setting weights requires the same minimum stake (`StakeThreshold`) that gates permits; below it the call fails with `NotEnoughStakeToSetWeights`. Timing reads: [`weights-rate-limit`](/docs/query/weights-rate-limit), [`blocks-since-last-update`](/docs/query/blocks-since-last-update), [`epoch-status`](/docs/query/epoch-status), [`reveal-period`](/docs/query/reveal-period). Reading back what the chain holds: [`client.read("weights", netuid=1)`](/docs/query/weights) returns every validator's row as normalized fractions, and [`client.read("bonds", netuid=1)`](/docs/query/bonds) the bond matrix your dividends flow through. ## Commit-reveal [#commit-reveal] Commit-reveal exists to defeat **weight copying**. Weights are public after consensus runs, and a lazy validator can skip evaluating miners entirely and submit the stake-weighted median of everyone else's revealed weights. Because Yuma Consensus rewards agreement with consensus, such copiers historically earned *better* vtrust and dividends per TAO than the honest validators they copied — while contributing nothing. With commit-reveal on, weights are **timelock-encrypted** when submitted: the ciphertext can only be decrypted (by anyone, including you) once a designated [Drand](https://drand.love) randomness round arrives. The reveal round is computed from the subnet's `commit_reveal_period`, measured in tempos (default 1), and the chain decrypts and applies the weights automatically at reveal time — there is no manual reveal step, no reveal-uptime requirement, and no selective-reveal exploit (earlier commit-reveal versions had manual reveals, which copiers abused by revealing only when it helped their vtrust). Copiers therefore only ever see weights that are at least one concealment period stale; if miner rankings moved in the meantime, copying them lands away from consensus and wrecks the copier's vtrust. Two caveats: * Commit-reveal only defends a subnet whose rankings actually **change** over the concealment window. If miner performance is static, stale weights are still accurate and copying still pays. * Subnet owners must keep `immunity_period` (in blocks) greater than `commit_reveal_period × tempo`, or new miners can be pruned before their first scores are ever revealed and counted. * Scores you see are stale by the reveal period. A miner you started weighting only surfaces in revealed weights after the reveal period elapses, and a crashed miner's score collapse is delayed the same way — don't read on-chain weights as a live view of the subnet. Commit-reveal is a per-subnet toggle — check it with [`commit-reveal-enabled`](/docs/query/commit-reveal-enabled) and the window with [`reveal-period`](/docs/query/reveal-period). `set-weights` picks the right path automatically. **Forcing the commit path.** `sub.CommitWeights` / [`btcli weights commit`](/docs/tx/commit-weights) always submits a timelocked commit, even on subnets that would accept plain weights — same inputs and preflight as `SetWeights`, just the path pinned. The counterpart `reveal` (`sub.RevealWeights` / [`btcli weights reveal`](/docs/tx/reveal-weights)) exists only for the **legacy salt-based** commit flow: it must reproduce the exact committed uids, weights, salt, and version key, and is never needed for timelocked commits, which the chain reveals on its own. ## Bonds and liquid alpha [#bonds-and-liquid-alpha] Dividends flow through **bonds**: each validator accumulates bond value toward the miners it weights, as an exponential moving average, and is paid in proportion to bonds × miner incentive. The EMA is the incentive to evaluate honestly and early — a validator that identifies a good miner before consensus catches up builds bond value cheaply and earns more when the miner rises. With the **liquid alpha** hyperparameter enabled, the EMA rate is no longer one constant: it becomes dynamic per validator–miner pair, moving between the subnet's `alpha_low` and `alpha_high` bounds (defaults 0.7 and 0.9) based on how the validator's weight relates to consensus. It only takes effect on subnets that also have `yuma3_enabled` on — the classic bond path ignores it. See [emissions](/docs/concepts/emissions) for the full mechanics. ## Childkeys: delegate stake weight [#childkeys-delegate-stake-weight] [`set-children`](/docs/tx/set-children) lets a parent hotkey delegate a fraction of its stake weight to other hotkeys on one subnet — commonly used to point stake at a separate validating key without moving the stake itself. The call replaces the full child set (empty list revokes all), is rate-limited, and takes effect after a chain-defined cooldown. Inspect with [`children`](/docs/query/children) / [`parents`](/docs/query/parents) / [`pending-children`](/docs/query/pending-children). ## Root and governance [#root-and-governance] Register on the root network with [`root-register`](/docs/tx/root-register) — placement is stake-based. The legacy on-chain senate-vote extrinsic has been removed from the runtime; see [Governance](/docs/guides/governance) for how governance currently works. ## Operational hygiene [#operational-hygiene] Validating is not supported on Windows (wallet operations work under WSL 2). Keep the coldkey off the validator box: weights are hotkey-signed, and anything coldkey-signed can go through a [proxy](/docs/concepts/advanced). Publish your endpoint with [`serve-axon`](/docs/tx/serve-axon) if your subnet's protocol requires validators to be reachable. # Benchmarks and weights (/docs/internals/benchmarks-and-weights) Every extrinsic in the runtime has a **weight** — a measure of the computational resources it consumes. Weights are used to calculate transaction fees and to prevent blocks from being overloaded. Weights are defined in `weights.rs` files inside each pallet and are generated by running benchmarks on reference hardware. ## Tools [#tools] | Tool | Purpose | | -------------------------- | ------------------------------------------------------------------- | | `scripts/benchmark_all.sh` | Generate `weights.rs` for one or all pallets (runs real benchmarks) | | `weight-compare` | Compare two `weights.rs` files and report drift (used by CI) | `weight-compare` lives in `support/weight-tools/` and has no heavy dependencies (no runtime build required). ## Adding a new pallet [#adding-a-new-pallet] 1. Write your benchmarks in `pallets//src/benchmarking.rs` using `#[benchmarks]` and `#[benchmark]` macros. 2. Create `pallets//src/weights.rs` manually. Copy the structure from any existing pallet (e.g. `pallets/drand/src/weights.rs`) and replace the function signatures with yours, using `Weight::from_parts(0, 0)` as the body so the pallet compiles immediately: ```rust pub trait WeightInfo { fn my_extrinsic() -> Weight; } pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn my_extrinsic() -> Weight { Weight::from_parts(0, 0) } } impl WeightInfo for () { fn my_extrinsic() -> Weight { Weight::from_parts(0, 0) } } ``` 3. Add `pub mod weights;` to your pallet's `lib.rs`. 4. Add `type WeightInfo: crate::weights::WeightInfo;` to your pallet's `Config` trait. 5. Use `T::WeightInfo::extrinsic_name()` in `#[pallet::weight(...)]` annotations instead of hardcoded `Weight::from_parts(...)`. 6. Wire up in `runtime/src/lib.rs`: ```rust type WeightInfo = pallet_::weights::SubstrateWeight; ``` 7. Add `type WeightInfo = ();` to all test mocks implementing your pallet's `Config`. 8. Register the pallet in the `define_benchmarks!` macro in `runtime/src/lib.rs` so the benchmark runner can discover it: ```rust define_benchmarks!( // ...existing pallets... [pallet_, PalletInstance] ); ``` The benchmark scripts auto-discover pallets by scanning for directories under `pallets/` that have `src/weights.rs` plus `src/benchmarking.rs` (or `src/benchmarks.rs`), and that are registered in `define_benchmarks!`. No manual registration in scripts is needed. Careful with step 8 (`define_benchmarks!`): an unregistered pallet is filtered out by discovery and **silently skipped** in all-pallets mode and in CI; only the single-pallet invocation (`./scripts/benchmark_all.sh pallet_`) fails, with an "unknown pallet" error. CI will generate real weights automatically when the PR is opened. ## Adding a new extrinsic to an existing pallet [#adding-a-new-extrinsic-to-an-existing-pallet] 1. Write the benchmark in `benchmarking.rs`. 2. Add the function signature to the `WeightInfo` trait in `weights.rs`, and a `Weight::from_parts(0, 0)` body to both the `SubstrateWeight` and `()` impls so the pallet continues to compile: ```rust // in trait WeightInfo: fn new_extrinsic() -> Weight; // in both impls: fn new_extrinsic() -> Weight { Weight::from_parts(0, 0) } ``` 3. Add `#[pallet::weight(T::WeightInfo::new_extrinsic())]` to the extrinsic. CI will generate real weights automatically when the PR is opened. ## Parameterized weights [#parameterized-weights] For extrinsics whose cost scales with an input, use `Linear` parameters in the benchmark. You can use one or more parameters: ```rust // Single parameter #[benchmark] fn refund(k: Linear<1, 100>) { // setup with k contributors... #[extrinsic_call] _(origin, crowdloan_id); } // Multiple parameters #[benchmark] fn transfer_batch(n: Linear<1, 256>, m: Linear<1, 64>) { // setup with n recipients and m tokens each... #[extrinsic_call] _(origin, recipients, amounts); } ``` This generates weight functions with matching signatures: ```rust fn refund(k: u32) -> Weight; fn transfer_batch(n: u32, m: u32) -> Weight; ``` The generated weight includes base values plus per-parameter slope terms (e.g., `base + slope_k * k` for single parameter, or `base + slope_n * n + slope_m * m` for multiple). Reference them as: ```rust #[pallet::weight(T::WeightInfo::refund(T::MaxContributors::get()))] #[pallet::weight(T::WeightInfo::transfer_batch(recipients.len() as u32, max_tokens))] ``` ## CI workflow [#ci-workflow] The `Validate-Benchmarks` workflow (`.github/workflows/run-benchmarks.yml`) runs on every PR: 1. Builds the node with `--features runtime-benchmarks` 2. Runs benchmarks for every pallet, generating new `weights.rs` to temp files 3. Uses `weight-compare` to compare old vs new values with a **40% threshold** * Base weight: threshold-based (allows measurement noise) * Reads/writes: exact match (these are deterministic) * Component slopes: threshold-based for weights, exact for reads/writes 4. If drift is detected, prepares a patch in `.bench_patch/` 5. Adding the `apply-benchmark-patch` label auto-applies the patch To skip benchmarks on a PR, add the `skip-validate-benchmarks` label. This can be added at any point during the job — it's checked between expensive steps. ## Running benchmarks locally [#running-benchmarks-locally] ```sh # Build + generate weights for all pallets ./scripts/benchmark_all.sh # Build + generate weights for a single pallet ./scripts/benchmark_all.sh pallet_subtensor # Run benchmark unit tests (fast, no real measurements — just checks setup) cargo test -p pallet-subtensor --features runtime-benchmarks benchmarks # Compare two weight files cargo run -p subtensor-weight-tools --bin weight-compare -- \ --old pallets/foo/src/weights.rs \ --new /tmp/new_weights.rs \ --threshold 40 ``` ## Weight file structure [#weight-file-structure] Generated `weights.rs` files contain: * `WeightInfo` trait — one function per benchmarked extrinsic * `SubstrateWeight` impl — used in the runtime, references `T::DbWeight` * `()` impl — used in tests, references `RocksDbWeight` The `()` fallback uses `RocksDbWeight` constants directly. The production runtime uses `T::DbWeight::get()`, which `runtime/src/lib.rs` also configures as `type DbWeight = RocksDbWeight` (matching the node's default RocksDb backend), so in this repo both impls resolve to the same constants. The files are generated by the `frame-benchmarking-cli` using the Handlebars template at `.maintain/frame-weight-template.hbs`. # Yuma Consensus (/docs/internals/consensus) Bittensor uses a subjective utility consensus mechanism called Yuma Consensus which rewards subnet validators with scoring incentive for producing evaluations of miner-value which are in agreement with the subjective evaluations produced by other subnet validators weighted by stake. Subnet servers receive incentive for their share of the utility according to the subnet validator consensus. Yuma Consensus pertains to subnet validation, instead of blockchain validation (substrate), so this writing will always refer to subnet validators and servers. ### Problem definition [#problem-definition] > How do we automatically detect and penalize reward manipulation in high-volume subjective utility networks? Most utility networks serve objectively measurable utility, such as data storage (Filecoin, Siacoin, Storj, Arweave), computation (Golem, TrueBit, Flux, Render, iExec RLC, Near), wireless networking (Helium), video streaming (Theta, Livepeer), and music streaming (Audius). Each of these networks employs specific, quantifiable metrics for their services, ranging from gigabytes of storage and CPU/GPU cycles to network coverage, bandwidth utilization and streaming quality. Objectively measurable utility makes it easy to detect and penalize reward manipulation, without the need for sophisticated consensus. Cryptographic proofs verify storage claims (Filecoin, Siacoin), outlier detection identifies and isolates inaccurate assessments (Ocean), cross-validation by multiple independent oracles reduce impact of single dishonest oracle (Chainlink), redundant computation prevents false result validation (Golem, TrueBit), random checks and challenges ensure honesty in computation results evaluation (iExec RLC), and continuous verification of actual network coverage (Helium). Objective utility networks typically need only basic majority consensus to correct malicious validation. Subjective utility networks (such as Steemit, Hive, LBRY, Minds, Voice, etc.) typically revolve around community-generated and curated content. Lower-volume content production allows for manual voting and tipping by users, or measurements of user and viewer engagement to calculate rewards. These subjective utility networks predominantly rely on manual mechanisms for reward distribution and detection of malicious activity. Community-driven reporting and moderation (Steemit, Hive, Minds), manual content evaluation (LBRY, Voice), and real identity verification (Voice) deter and penalize reward manipulation. Community oversight (as in Steemit) must identify wrongful downvoting, but only indirect remediation via counter-voting can penalize bad actors. The absence of voting reputation means users can only downvote the content of bad actors as retribution and thereby only damage content reputation, because no automated penalty mechanism exists. Similarly, users can upvote their own content and potentially receive nominal reward according to their stake, so reward manipulation may go unchecked. High-volume, on-demand generative content (as in Bittensor) demands automated evaluation and divide-and-conquer validation, but introduces subjectivity both in the automated value measures and mutually exclusive task subsets across subnet validators. A coalition of validators can collude to skew scoring of subnet servers in their favour, which is harder to detect because of the inherent subjectivity. Existing consensus mechanisms will fail to deter reward manipulation for such high-volume subjective utility networks, so the need for a more sophisticated consensus arises. *** ### Consensus Mechanism [#consensus-mechanism] Yuma Consensus guarantees long-term network honesty despite persistent adversarial presence in high-volume subjective utility networks. It directly penalizes selfish scoring by down-correction to the majority consensus and slashing of cabal voting stake, and also penalizes low-scoring of honest servers via forfeited validator rewards when cabals don’t score at consensus. Yuma Consensus is adversarially-resilient when majority stake is honest, via stake-based median scoring that punishes selfish weighting by minority stake (cabal). We clip excess weight above the maximum weight supported by at least $\kappa$-majority stake, which ensures selfish weighting is corrected and reduces voting stake of cabal validators via bonds penalty $\beta$. **Max weight supported by $\kappa$-majority stake**: We can plot a consensus graph for each active miner by ordering weights (y-axis) set on the target miner and recording cumulative stake (x-axis) of the source validators. The weight read at $x=1-\kappa$ ratio of total active stake is the stake-based median, which means that at least $\kappa$ stake supports that weight decision. Typically $\kappa=0.5$, which means that 50% of stake needs to agree on the relative utility of a miner, and we clip excess weight above this median to encourage subnet validators to vote fairly and uphold consensus. **Cabal sets low weight on honest majority**: The median calculation ignores selfish subsets that vote dishonestly (cabals), if they have minority stake (less than $\kappa$-majority), when they set low weights on the honest majority. This means that a minority cabal cannot negatively influence the consensus weight on honest servers. **Cabal sets high self-weight**: Cabal servers with poor utility will receive low weights from majority stake, and high self-weight from minority cabals will then get reduced to the low consensus. This means that minority cabals lose voting power as penalty for unfair voting while still receiving low consensus weight despite high self-weight. This consensus mechanism thus protects against selfish weighting if the majority stake is honest. *** ### Game-theoretic framework [#game-theoretic-framework] #### Preliminaries [#preliminaries] We consider a two-team game between (protagonist) honest stake ($0.5< S_H\le 1$) and (adversarial) cabal stake ($1 - S_H$), with $|H|$ honest and $|C|$ cabal players, that have $S_H = \sum_{i\in H}S_i$ honest stake and $1-S_H = \sum_{i\in C}S_i$ cabal stake. They compete for total fixed reward $E_H + E_C = 1$, with honest emission $E_H$ and cabal emission $E_C$, respectively. Then the stake updates to $S_H'=S_H+\tau E_H$ and $S_C'=(1 - S_H)+\tau E_C$, where $\tau$ decides the emission schedule. We normalize stake after the emission update, so that $\sum S'=1$. The honest objective $S_H\le E_H$ at least retains scoring power $S_H$ over all action transitions in the game, otherwise when $E_H\le S_H$ honest emission will erode to 0 over time, despite a starting condition of $0.5\lt S_H$. We assume honest stake sets objectively correct weights $W_H$ on itself, and $1 - W_H$ on the cabal, where honest weight $W_H$ represents an ongoing expense of the honest player such as utility production, sustained throughout the game. However, cabal stake has an action policy that freely sets weight $W_C$ on itself, and $1 - W_C$ on the honest player, at no cost to the cabal player. Independent smaller cabals will downweight each other thereby weakening overall adversarial power, so we consider the worst-case of a unified cabal. Specifically, honest players $i\in H$ set $W_H = \sum_{j\in H}W_{ij}$ self-weight and $1-W_H = \sum_{j\in C}W_{ij}$ weight on cabal players, while cabal players $i\in C$ set $W_C = \sum_{j\in C}W_{ij}$ self-weight and $1-W_C = \sum_{j\in H}W_{ij}$ weight on honest players. The cabal has the objective to maximize the required honest self-weight expense $W_H$ via $$ W_C^*=\arg\max_{W_C}E[W_H\ | \ S_H=E_H(S_H,W_H,W_C)].\tag{1} $$ We then assume the honest majority $S_H>0.5$ can counter with a consensus policy $\pi$ allowed to modify all weights modulo player labels, so it is purely based on the anonymous weight distribution itself, optimizing the Nash equilibrium $$ \min_{\pi}\max_{W_C}E[W_H\ | \ S_H=E_H(S_H,\pi(\mathbf{W}))].\tag{2} $$ #### Consensus policy [#consensus-policy] Majority stake enforces an independent and anonymous consensus policy $\pi$ (through a blockchain solution) that modifies the weights to minimize the expense $W_H$, which has been maximized by the cabal applying an objectively incorrect gratis self-weight $W_C$. Consensus aims to produce $\pi(\mathbf{W})\rightarrow (W'_H, W'_C)$ so that $W'_C=1-W'_H$, by correcting the error $\epsilon=W_C+W_H-1>0$. Note that the input cost $W_H$ remains fully expensed, and that $W'_H$ merely modifies the reward distribution that follows, but not knowing which players are honest or cabal (anonymous property). We propose a consensus policy that uses stake-based median as consensus weight $\overline{W_j}$, so that $\kappa$-stake (typically majority, i.e. $\kappa\ge 0.5$) decides the maximum supported weight on each subnet server $j$. The indicator function $\left\lbrace W_{ij} \ge w \right\rbrace$ adds stake $S_i$ if subnet validator $i$ supports a specific weight-level $w$ on server $j$. $$ \overline{W_j}=\arg \max_w \left( \sum_i S_i \cdot \left\lbrace W_{ij} \ge w \right\rbrace \ge \kappa \right)\tag{3} $$ The consensus policy applies weight correction $\overline{W_{ij}} = \min( W_{ij}, \overline{W_j} )$ to weight excess above consensus, which (i) restricts server incentive in case of selfish weighting, and (ii) penalizes selfish validators by slashing their voting stake (bonds) and validation rewards. The bonds penalty $\beta$ controls the degree to which weights for bonds are cut above consensus, which decides the penalty against subnet validator rewards. $$ \widetilde{W_{ij}} = (1-\beta) \cdot W_{ij} + \beta \cdot \overline{W_{ij}}\tag{4} $$ #### Validator bonding [#validator-bonding] A subnet validator $i$ bonds with server $j$, where the instant bond value is the normalized bonds penalty clipped weighted stake. $$ \Delta B_{ij} = S_i \cdot \widetilde{W_{ij}} \left/ \left( \sum_k S_k \cdot \widetilde{W_{kj}} \right) \right.\tag{5} $$ Validators can speculate on server utility by discovering and bonding to promising new servers, but to reward such exploration we use an exponential moving average (EMA) bond over time. Instant bond $\Delta B_{ij}$ at current timestep becomes an EMA observation. We sum typical $\alpha=10$% of the instant bond with remaining $90$% of previous EMA bond, to bond over time and reward early discovery while preventing abrupt changes and its exploitation potential. $$ B_{ij}^{(t)} = \alpha\cdot\Delta B_{ij} + (1-\alpha)\cdot B_{ij}^{(t-1)}\tag{6} $$ #### Reward distribution [#reward-distribution] Emission ratio $\xi$ decides the ratio of emission for validation rewards, and $1-\xi$ the ratio for server incentive. In the implementation $\xi$ is not a settable hyperparameter: incentive and dividends are each normalized to sum to one and then jointly renormalized, which fixes the split at $\xi=0.5$ whenever both sides are nonzero. $$ E_i = \xi \cdot D_i + (1-\xi) \cdot I_i\tag{7} $$ Subnet server incentive $I_j = R_j / \sum_k R_k$ is normalized server rank $R_j = \sum_i S_i \cdot \overline{W_{ij}}$ (sum of consensus-clipped weighted stake). Validation reward $D_i = \sum_j B_{ij} \cdot I_j$ is the subnet validator's EMA bond with server $j$ multiplied with server $j$ incentive. #### Mathematical definitions [#mathematical-definitions] | Variable | Equation | Description | | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | Weight | $W_{ij}$ | Validator $i$ weight on server $j$. | | Stake | $S_i = S'_i / \sum_k S'_k$ | Validator $i$ relative stake. | | Server prerank | $P_j = \sum_i S_i \cdot W_{ij}$ | Sum of weighted stake. | | Server consensus weight | $\overline{W_j} = \arg \max_w \left( \sum_i S_i \cdot \left\lbrace W_{ij} \ge w \right\rbrace \ge \kappa \right)$ | $\kappa$-stake supported maximum weight on server $j$. | | Consensus-clipped weight | $\overline{W_{ij}} = \min( W_{ij}, \overline{W_j} )$ | Validator $i$ consensus-clipped weight on server $j$. | | Server rank | $R_j = \sum_i S_i \cdot \overline{W_{ij}}$ | Sum of consensus-clipped weighted stake. | | Server incentive | $I_j = R_j / \sum_k R_k$ | Ratio of incentive for server $j$. | | Server trust | $T_j = R_j / P_j$ | Relative server weight remaining after consensus-clip. | | Validator trust | $T_{vi} = \sum_j \overline{W_{ij}}$ | Relative validator weight remaining after consensus-clip. | | Bonds penalty | $\beta \in [0, 1]$ | Degree to cut bonds above consensus weight. | | Weight for bonds | $\widetilde{W_{ij}} = (1-\beta) \cdot W_{ij} + \beta \cdot \overline{W_{ij}}$ | Apply bonds penalty to weights. | | Validator bond | $\Delta B_{ij} = S_i \cdot \widetilde{W_{ij}} \left/ \left( \sum_k S_k \cdot \widetilde{W_{kj}} \right) \right.$ | Validator $i$ bond with server $j$. | | Validator EMA bond | $B_{ij}^{(t)} = \alpha\cdot\Delta B_{ij} + (1-\alpha)\cdot B_{ij}^{(t-1)}$ | Validator $i$ EMA bond with server $j$. | | Validator reward | $D_i = \sum_j B_{ij} \cdot I_j$ | Validator $i$ portion of incentive. | | Emission ratio | $\xi \in [0, 1]$ | Reward/incentive ratio for emission | | Emission | $E_i = \xi \cdot D_i + (1-\xi) \cdot I_i$ | Emission for node $i$. | #### Subtensor epoch [#subtensor-epoch] Subtensor blockchain nodes calculate consensus and rewards during each subnet [`epoch`](https://github.com/RaoFoundation/subtensor/blob/main/pallets/subtensor/src/epoch/run_epoch.rs) with associated code excerpts as follows. Production runs the sparse variant (`epoch_mechanism`, invoked per mechanism from coinbase); the dense excerpt below is the test-only analogue with identical math. The EMA-bond lines show the legacy branch — subnets with `Yuma3On` enabled compute bonds through the liquid-alpha path (`compute_bonds`) instead. ```rust let mut weights: Vec> = Self::get_weights(netuid_index); // Weight let consensus: Vec = weighted_median_col(&active_stake, &weights, kappa); // Server consensus weight let mut clipped_weights: Vec> = weights.clone(); inplace_col_clip(&mut clipped_weights, &consensus); // Consensus-clipped weight let validator_trust: Vec = row_sum(&clipped_weights); // Validator trust let mut ranks: Vec = matmul(&clipped_weights, &active_stake); // Server rank inplace_normalize(&mut ranks); let incentive: Vec = ranks.clone(); // Server incentive let weights_for_bonds: Vec> = interpolate(&weights, &clipped_weights, bonds_penalty); // Weight for bonds let mut bonds_delta: Vec> = row_hadamard(&weights_for_bonds, &active_stake); // Validator bond inplace_col_normalize(&mut bonds_delta); let mut ema_bonds = Self::compute_ema_bonds_normal(&bonds_delta, &bonds, netuid); // Validator EMA bond inplace_col_normalize(&mut ema_bonds); let mut dividends: Vec = matmul_transpose(&ema_bonds, &incentive); // Validator reward inplace_normalize(&mut dividends); ``` *** ### Monte Carlo simulations [#monte-carlo-simulations] We consider a two-team game between (protagonist) honest stake ($0.5< S_H\le 1$) and (adversarial) cabal stake ($1 - S_H$), with $|H|$ honest and $|C|$ cabal players, that have $S_H = \sum_{i\in H}S_i$ honest stake and $1-S_H = \sum_{i\in C}S_i$ cabal stake. #### Network sizing [#network-sizing] A network size of $N=|H|+|C|=(|H_V|+|H_S|)+(|C_V|+|C_S|)=512$ and validator count of $|H_V|+|C_V|=64$ is considered for consensus guarantee experiments, and the honest/cabal ratio $|H|/N=S_H$ reflects the honest stake ratio $S_H$, but modifying extremes to ensure that each subset has at least one validator and at least one server. #### Stake sampling [#stake-sampling] For the Monte Carlo simulations we use Gaussian distributions for stake and weight assignments, and ensure that the honest/cabal ratios are met. Note that stake is only assigned to validator nodes $H_V$ and $C_V$ and not servers. Firstly, we sample initial validator ($i\in H_V\cup C_V$) stake values $S'_i \sim \mathcal{N}(1,\sigma_S^{2})$ with a typical $\sigma_S=0.3$ standard deviation, followed by clamping to avoid negative stake: $$ S'_i = \begin{cases} x & \text{if } x \sim \mathcal{N}(1, \sigma_S^2), x \ge 0 \\ 0 & \text{if } x \sim \mathcal{N}(1, \sigma_S^2), x < 0 \end{cases} $$ Then we normalize each honest/cabal subset and multiply by its stake proportion, which thus gives an overall normalized stake and the correct stake ratio for each subset: $$ S_{i\in H_V} = S_H \cdot S'_i \left/ \sum_{k\in H_V} S'_k\right.\qquad\qquad S_{i\in C_V} = (1-S_H)\cdot S'_i \left/ \sum_{k\in C_V}S'_k\right. $$ #### Weight sampling [#weight-sampling] Similarly, we randomize the weights that validators $H_V,C_V$ set on servers $H_S,C_S$. Specifically, honest players $i\in H$ set $W_H = \sum_{j\in H}W_{ij}$ self-weight and $1-W_H = \sum_{j\in C}W_{ij}$ weight on cabal players, while cabal players $i\in C$ set $W_C = \sum_{j\in C}W_{ij}$ self-weight and $1-W_C = \sum_{j\in H}W_{ij}$ weight on honest players. We firstly sample initial weights $W'_{ij} \sim \mathcal{N}(1,\sigma_W^{2})$ with various standard deviations ranging in $0\ge\sigma_W\ge0.4$, but then clamping to avoid negative weights: $$ W'_{ij} = \begin{cases} x & \text{if } x \sim \mathcal{N}(1, \sigma_S^2), x \geq 0 \\ 0 & \text{if } x \sim \mathcal{N}(1, \sigma_S^2), x < 0 \end{cases} $$ Weight setting between the two subsets forms quadrants $H_V\rightarrow H_S$, $H_V\rightarrow C_S$, $C_V\rightarrow H_S$, and $C_V\rightarrow C_S$, so we ensure those weight ratios are met by normalizing each weight subset and multiplying by the corresponding quadrant ratio: $$ W_{i\in H_V, j\in H_S} = W_H\cdot W'_{ij} \left/ \sum_{k\in H_S}W'_{ik}\right.\qquad\qquad W_{i\in H_V, j\in C_S} = (1-W_H)\cdot W'_{ij} \left/ \sum_{k\in C_S}W'_{ik}\right. $$ $$ W_{i\in C_V, j\in H_S} = (1-W_C)\cdot W'_{ij} \left/ \sum_{k\in H_S}W'_{ik}\right.\qquad\qquad W_{i\in C_V, j\in C_S} = W_C\cdot W'_{ij} \left/ \sum_{k\in C_S}W'_{ik}\right. $$ #### Emission calculation [#emission-calculation] Given the simulation parameters of the network size, validator count, a defined major/honest stake $S_H$, a defined major/honest utility $W_H$, and a defined minor/cabal self-weight $W_C$, we have now instantiated the network with randomly sampled stake and weights and can proceed with an emission calculation. We calculate the consensus $\overline{W_j} = \arg \max_w \left( \sum_i S_i \cdot \left\lbrace W_{ij} \ge w \right\rbrace \ge \kappa \right)$ for each server $j$, and calculate consensus-clipped weights $\overline{W_{ij}} = \min( W_{ij}, \overline{W_j} )$. This then gives us the adjusted weights that offers a measure of protection against reward manipulation. To calculate emissions for this epoch, we firstly calculate server rank $R_j = \sum_i S_i \cdot \overline{W_{ij}}$ then incentive $I_j = R_j / \sum_k R_k$, as well as validator bonds $\Delta B_{ij} = S_i \cdot \widetilde{W_{ij}} \left/ \left( \sum_k S_k \cdot \widetilde{W_{kj}} \right) \right.$ and rewards $D_i = \sum_j B_{ij} \cdot I_j$. Then we add up server incentive and validator bonds over honest nodes to obtain honest emission $E_H = \xi \cdot D_{i\in H} + (1-\xi) \cdot I_{i\in H}$ with a typical validator reward ratio of $\xi=0.5$. The objective is to prove major stake retention $S_H\ge E_H$ for a single epoch, which by extension proves retention over many epochs due to additive nature of EMA bonds, so we do not bother with validator EMA bonds in these experiments. The honest objective $S_H\le E_H$ at least retains scoring power $S_H$ over all action transitions in the game, otherwise when $E_H\le S_H$ honest emission will erode to 0 over time, despite a starting condition of $0.5\lt S_H$. *** ### Consensus guarantees [#consensus-guarantees] Yuma Consensus guarantees honest majority stake retention $S_H\le E_H$ even under worst-case adversarial attacks, given sufficiently large honest utility $W_H$. The specific honest stake and utility pairs that delineate the guarantees are complicated by natural variances inside large realistic networks. Therefore, we use extensive random sampling simulations (Monte Carlo studies) of large realistic networks and subject them to varying degrees of adversarial attacks, and calculate comprehensive consensus guarantees under representative conditions. Note the primary assumption is that the majority stake is honest, so we use majority/honest interchangeably, same with minority/cabal. #### Retention graphs [#retention-graphs] Consensus guarantees are dependent on stake, utility, and subnet validator behaviour, so we use 2D contour plots to comprehensively display guarantees across each possible set of conditions. The x-axis is major self-weight and the y-axis is minor self-weight, and each contour line is a specific major stake. Major/honest self-weight $W_H$ is the true honest utility, while minor/cabal self-weight $W_C$ is an arbitrary value a self-serving coalition may self-report.

To understand how we construct these plots, let us first consider contour plot for a single major/honest stake setting $S_H=0.6$. Here each contour value is the honest emission $E_H$, and we highlight at (1) the specific contour $E_H=0.6$ that matches the honest stake. This means that any weight setting on contour $E_H=S_H=0.6$ will retain honest stake, while any setting to the right of it will grow honest stake. Similarly, the specific emission contour plot for $S_H=0.7$, highlights the contour where the emission is $E_H=0.7$, which means with inflation the honest share ratio of $S_H=0.7$ can be retained if honest utility is at least $W_H>0.75$. A compound plot then combines all the highlighted $S_H=E_H$ contours from individual contour plots (e.g. $S_H=0.6$ and $S_H=0.7$), to show the overall retention profile. Generally, the higher the honest stake, the higher the honest utility requirement to retain stake proportion under adversarial weight setting. Retention graphs like these comprehensively capture consensus guarantees across all primary conditions, and we utilize these to analyze the effect of consensus hyperparameters. Subtensor integration tests run Monte Carlo simulations of large realistic networks under adversarial conditions, and constructs retention profiles to confirm consensus guarantees of the actual blockchain implementation. Retention profiles are reproducible by running test [`map_consensus_guarantees()`](https://github.com/RaoFoundation/subtensor/blob/main/pallets/subtensor/src/tests/consensus.rs) and plotting with [`map_consensus.py`](https://github.com/RaoFoundation/subtensor/blob/main/scripts/map_consensus.py). ```bash RUST_BACKTRACE=1 SKIP_WASM_BUILD=1 RUSTFLAGS="-C opt-level=3" cargo test --manifest-path=pallets/subtensor/Cargo.toml -- tests::consensus::map_consensus_guarantees --exact --ignored --nocapture > consensus.txt python scripts/map_consensus.py consensus.txt ``` #### Subjectivity variance [#subjectivity-variance] Yuma Consensus corrects reward manipulation in subjective utility networks, but the extent of subjectivity influences the exact consensus guarantees. In particular, we expect lower subjectivity to offer improved guarantees since there is stronger consensus. However, for higher variance in assigned weights it is easier to hide reward manipulation, we then expect poorer guarantees.

We assume normally distributed weights originating from a particular side, either honest or cabal, then we modify the weight deviation magnitude $\sigma(W)$ in terms of the mean weight $\mu(W)$. Weight deviations of $\sigma=0\mu$, $0.2\mu$, and $0.4\mu$ respectively require 60%, 67%, and 73% honest utility to preserve 60% honest stake. This confirms the expectation that higher subjectivity variance demands greater honest utility for honesty preservation. Bitcoin has its well-known 51% attack, while Yuma Consensus has a 40% stake + 30% utility attack under high subjectivity ($0.4\mu$). This means that a bad actor with 40% stake that produces 30% utility or more can gain majority stake over time. In practice, we typically observe standard deviations of $0.2\mu$ to $0.4\mu$, so Yuma Consensus guarantees honest 60% stake retention for \~70% honest utility or more. #### Majority ratio (κ) [#majority-ratio-κ] Hyperparameter `Kappa` sets the ratio of stake that decides consensus, with a typical recommended value of $\kappa=0.5$, which means that at least 50% of stake needs to be in consensus on the maximum weight assignable to a specific server. Reducing $\kappa$ weakens consensus and allows smaller cabals to manipulate rewards, in particular $\kappa=0.4$ demands higher honest utility when $S_H<0.6$ (such as $0.51$ and $0.55$). Increasing $\kappa$ demands greater honest stake, e.g. when $\kappa=0.6$ there is no protection for $S_H<0.6$ even with $W_H=1$. Hence $\kappa=0.5$ is typically the most sensible setting.

#### Bonds penalty (β) [#bonds-penalty-β] Yuma Consensus separately adjusts server incentive $I_j$ and validation reward $D_i$ to counter manipulation, where the extent of validation reward correction depends on the bonds penalty $\beta$. Server incentive always corrects fully, but validation reward correction is adjustable to control the penalty of out-of-consensus validation. Lower-stake validators may experience lower service priority, which can result in partial validation, or exploratory validators may skew weighting toward emergent high-utility. Full bonds penalty $\beta=1$ may not be desired, due to the presence of non-adversarial cases like these. Note that the chain default (`BondsPenalty`) is nonetheless full penalty $\beta=1$; subnets can lower it via hyperparameter.

We expect that greater bonds penalty will penalize out-of-consensus validators more, which means less emission going to cabals. Comprehensive simulation with $\beta = 0$, $0.5$, and $1$ respectively show 78%, 76%, and 73% honest utility requirement. This confirms the expectation, that greater bonds penalty means greater inflation going to the honest majority. #### Emission ratio (ξ) [#emission-ratio-ξ] Subnet servers need incentive to deliver high utility, and subnet validators need rewards to secure the network. We expect that more emission going to validators will improve security guarantees, since self-serving validation can then be economically disincentivized.

We set validation reward ratio at $\xi=0$, $0.25$, and $0.5$ and respectively observe 82%, 78%, 73% honest utility requirement for 60% honest stake preservation. This means that network security improves as the validation reward ratio is increased, although a significant server incentive ratio still needs to be maintained to ensure overall high utility. *** ### Reproduce Consensus Plots (Runpod) [#reproduce-consensus-plots-runpod] This guide demonstrates how to reproduce consensus retention profile plots on a minimal Runpod CPU instance. #### 1. Deploy Runpod Instance [#1-deploy-runpod-instance] Navigate to [https://www.runpod.io/console/deploy](https://www.runpod.io/console/deploy) and select the following: * **Pod Type:** CPU Pod, CPU5 (5.7 GHz • DDR5 RAM • NVMe) or equivalent. * **Instance Configuration:** Compute-Optimized (\$0.07/hr, 2 vCPUs, 4GB RAM). **Important:** Edit the template and set "Container Disk (Temporary)" to 20GB. This ensures sufficient disk space for the process. Retrieve the connection details, including the SSH command and port, under "Connect" -> "SSH over exposed TCP". You can optionally enable Jupyter access (`8888:localhost:8888`) if desired. Connect to your instance via SSH: ```bash ssh -L 8888:localhost:8888 root@ -p -i ~/.ssh/id_ed25519 # Replace placeholders ``` #### 2. Set up the Environment [#2-set-up-the-environment] 1. **Start a `tmux` session for persistence:** ```bash tmux ``` 2. **Update system packages and install prerequisites (Python, Rust, and dependencies):** ```bash sudo apt-get update && sudo apt install -y build-essential clang curl git make libssl-dev llvm libudev-dev protobuf-compiler python3 python3-pip \ && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \ && source ~/.cargo/env \ && rustup default stable \ && rustup update \ && rustup target add wasm32v1-none ``` 3. **Clone the Subtensor repository and checkout the relevant branch:** ```bash git clone https://github.com/RaoFoundation/subtensor.git cd subtensor git checkout main ``` #### 3. Simulate Networks and Generate Data [#3-simulate-networks-and-generate-data] The Subtensor integration tests simulate large, realistic networks under adversarial conditions to generate retention profiles that validate the blockchain's consensus guarantees. Building takes about 10 minutes, and the actual test itself another 15 minutes approximately. ```bash RUST_BACKTRACE=1 SKIP_WASM_BUILD=1 RUSTFLAGS="-C opt-level=3" cargo test --manifest-path=pallets/subtensor/Cargo.toml -- tests::consensus::map_consensus_guarantees --exact --ignored --nocapture > consensus.txt ``` This command runs the `map_consensus_guarantees` test and saves the output to `consensus.txt`. Replace `` with a float e.g. 1.0 (100% bonds penalty). #### 4. Generate Contour Plots [#4-generate-contour-plots] 1. **Create a Python virtual environment and install necessary libraries:** ```bash python3 -m venv .venv source .venv/bin/activate pip install numpy matplotlib jupyterlab ``` 2. **Run the plotting script:** ```bash python3 scripts/map_consensus.py consensus.txt ``` This generates an SVG file named `consensus_plot.svg` in the current directory. #### 5. Explore and Modify (Optional) [#5-explore-and-modify-optional] You can use Jupyter-lab to interactively explore and modify the generated plots: 1. **Start Jupyter-lab (on VPS):** ```bash jupyter-lab --allow-root --port=8888 ``` 2. **Connect to Jupyter:** Open the provided URL (e.g., `http://localhost:8888/tree?token=...`) in your local workstation web browser. 3. **Modify the plotting script:** Edit `scripts/map_consensus.py` to customize the plots, otherwise download the SVG file. #### Disclaimer [#disclaimer] > This reproduction procedure is provided as a guide and may require adjustments depending on your specific VPS environment and configuration. While every effort has been made to ensure accuracy and completeness, variations in system setup, software versions, or network conditions could affect the results. > > Please exercise caution when executing commands with root privileges and ensure you understand the potential implications before proceeding. The author assumes no responsibility for any issues arising from the use of this procedure. If you encounter problems or have suggestions for improvement, please open an issue on this repository. # Contributing (/docs/internals/contributing) ## Lifecycle of a pull request [#lifecycle-of-a-pull-request] 1. Develop your change on a branch and open a [pull request](https://github.com/RaoFoundation/subtensor/compare) targeting `main`. Start it as a draft until it's ready for other developers to look at. Any change to pallet or runtime code must be accompanied by unit and/or integration tests covering its edge cases — see [Testing](/docs/internals/testing) for how to run every suite. 2. CI validates the PR from several angles: * `check-rust.yml` — fmt, clippy, custom lints, zepter, `cargo test` * [Clone Upgrade Check](/docs/internals/mainnet-clone) — sudo-upgrades a clone of live mainnet with your runtime, then runs the clone regression tests, the [Python SDK suites](/docs/internals/sdk-tests), and the docs/website build. A runtime change that breaks the SDK or docs fails the PR. * `try-runtime.yml` — replays your migrations against live network state. * Spec Version Check — runtime-affecting PRs must bump `spec_version` above what mainnet is running, or carry the `no-spec-version-bump` label (see below). 3. Mark the PR "Ready for Review" once the Rust CI is green and request review from the core (Nucleus) team. Reviews may request changes; three positive reviews are required. 4. After approvals, you or an administrator merge to `main` — which starts the release train. New to the codebase? Start with the [repository layout](/docs/internals/repo-layout). ## The release train [#the-release-train] Merging to `main` triggers `release-train.yml`, a build-once promotion pipeline (full detail: [Release process](/docs/internals/release-process)): ``` push to main └─ build wasm once (deterministic srtool build) └─ deploy devnet ── smoke-check devnet └─ deploy testnet ── smoke-check testnet ── publish SDK rc └─ propose mainnet upgrade (environment gate + triumvirate multisig) ``` Key properties: * **The runtime is built exactly once per train.** Promotion between networks is a `setCode` extrinsic plus a smoke suite, never a rebuild — every network runs the identical wasm. * **The ship lever is the `spec_version` bump.** A merge without a bump builds and then no-ops at every deploy guard. This is why the Spec Version Check exists: without a bump (or the explicit opt-out label) your change would silently never ship. * **Human gates live in GitHub environment settings**, not in the workflow: devnet deploys automatically; testnet and mainnet promotion wait for whatever reviewers are configured on those environments. * **Mainnet is never upgraded directly by CI.** The train submits a multisig proposal (CI holds one key of a 2-of-2 deployment multisig with a `SudoUncheckedSetCode` proxy); the triumvirate approves 2-of-3 out-of-band. Once the upgrade executes on chain, `watch-mainnet-release.yml` cuts the GitHub release and publishes Docker images, the Python SDK, Rust crates, and the production website/docs. After your change deploys to testnet, it is your responsibility to verify it works there. If it doesn't, coordinate with a core team administrator and open a follow-up PR promptly. ## PR labels [#pr-labels] | Label | Effect | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `red-team` | Marks feature additions/changes (informational) | | `blue-team` | Marks safety measures / dev-UX improvements (informational) | | `runtime` | Marks substantive runtime or pallet changes (informational) | | `breaking-change` | Notifies the relevant teams automatically — use for anything requiring synchronized changes elsewhere | | `no-spec-version-bump` | Skips the spec-version gate for changes that deliberately ship without a runtime release | | `skip-clone-upgrade` | Skips the mainnet-clone upgrade check — reserve for changes that cannot affect the runtime, SDK, or docs | | `mainnet-clone` | Spins up a live, publicly tunneled mainnet clone running your PR's runtime and posts the `wss://` endpoint as a PR comment for interactive testing | | `apply-benchmark-patch` | Applies the benchmark bot's proposed `weights.rs` patch to the PR | | `skip-cargo-audit` | Skips the dependency audit | ## Documentation [#documentation] All documentation lives in the repo-root `docs/` folder — the single source of truth rendered to [docs.bittensor.com](https://docs.bittensor.com) by the website app (`website/apps/bittensor-website`). * User-facing concepts and guides: `docs/concepts/`, `docs/guides/` * Generated SDK reference (`docs/tx/`, `docs/query/`, `docs/errors.mdx`): never hand-edit; regenerate with `website/apps/bittensor-website/scripts/generate.py` * Runtime internals and contributor docs: `docs/internals/` The docs/website build runs as part of the Clone Upgrade Check, so a PR that breaks the docs build fails CI. # Eco-tests (indexer contract) (/docs/internals/eco-tests) `eco-tests/` is a standalone crate (excluded from the cargo workspace) that pins the **storage shapes and runtime API signatures the TAO.com ecosystem indexer depends on**. The indexer reads chain state directly — storage items like `Consensus`, `Incentive`, `Weights`, `Bonds`, ownership and childkey maps, and runtime APIs like `DelegateInfoRuntimeApi` and `StakeInfoRuntimeApi`. If a runtime change renames a storage item, changes a value type, or alters an API return shape, the indexer breaks in production even though nothing in this repo's own tests would notice. The tests are intentionally shallow: they mostly assert that each pinned storage item still exists with the expected key and value types (the test body is often just a typed `::get()` call that must compile) and that the runtime API signatures are unchanged. A compile failure *is* the signal. ## Running them [#running-them] The crate is excluded from the workspace, so run from its own directory: ```bash cd eco-tests SKIP_WASM_BUILD=1 cargo test ``` CI runs this on every PR (`eco-tests.yml`). ## When one fails on your PR [#when-one-fails-on-your-pr] A failure means your change breaks the data contract the indexer consumes. Two valid resolutions: 1. **Unintended breakage** — adjust your change to preserve the storage/API shape (e.g. keep the old item alongside the new one, or provide a migration plus an unchanged read path). 2. **Intentional change** — update the eco-test to the new shape. Any PR that touches `eco-tests/**` automatically requests review from the indexer liaison (`eco-tests-indexer-notify.yml`), so the indexer team hears about the change before it ships. Don't merge until they've acknowledged it. What you should **not** do is silently change the assertion to make CI green: the whole point of the suite is that the notification fires and the indexer team gets lead time. # Mainnet clone testing (/docs/internals/mainnet-clone) Every PR runs the **Clone Upgrade Check** (`check-clone-upgrade.yml`): it builds your proposed runtime, applies it to a local copy of *real mainnet state* via a sudo upgrade, then runs regression tests and the Python SDK e2e suite against the upgraded chain. This catches problems unit tests can't — migrations that break on real storage, changed RPC shapes, SDK incompatibilities — before anything ships. This page explains what the check does and how to reproduce each step locally. ## How it works [#how-it-works] 1. **`node-subtensor build-patched-spec`** (invoked by `clones/scripts/clone-mainnet.sh`) spins up a temporary node, syncs current mainnet state from a bootnode, exports it as a raw chainspec, and patches it for local use: block authorship (Aura/Grandpa authorities) and the sudo key are handed to the well-known dev account **Alice**. The result is written to `clones/mainnet-clone-chainspec.json` (gitignored). 2. **`clones/scripts/start-local-clone.sh`** starts a single local validator from that chainspec on `ws://127.0.0.1:9944`, authoring blocks as Alice. 3. **`npm run runtime:update:alice`** (in `clones/js-tests/`) reads your built runtime wasm from `target/release/wbuild/node-subtensor-runtime/node_subtensor_runtime.compact.compressed.wasm` and submits `sudo(system.setCode(...))` from Alice. The clone is now running mainnet state under **your** runtime, including any migrations it triggered. 4. **`npm test`** runs the smoke test; CI then runs the SDK's offline checks and its e2e suite (`pytest -m e2e`) against the upgraded chain. ## Reproducing locally [#reproducing-locally] ```bash # 1. Build the node and your runtime cargo build --release -p node-subtensor # 2. Create (or reuse) the patched mainnet clone chainspec. # First run syncs mainnet state — this takes a while and needs disk space. # Later runs reuse the existing chainspec file. ./clones/scripts/clone-mainnet.sh # 3. Start the clone node (leave this running; use a second terminal for the rest) ./clones/scripts/start-local-clone.sh # 4. Sudo-upgrade the clone to your runtime cd clones/js-tests npm ci npm run runtime:update:alice # 5. Run the smoke test (what `npm test` runs in CI) npm test # 6. When you're done cd ../.. ./clones/scripts/stop-local-clone.sh ``` To re-sync fresh mainnet state instead of reusing the cached spec, delete `clones/mainnet-clone-chainspec.json` and rerun `clone-mainnet.sh`. `start-local-clone.sh` wipes the chain database (`clones/mainnet-clone/`) on every start, so each run replays your upgrade from the cached state — restart the node to retry a failed upgrade from scratch. ### Running the SDK suites against the clone [#running-the-sdk-suites-against-the-clone] ```bash cd sdk/python uv sync --locked --all-extras --dev uv run pytest # offline unit tests E2E_ENDPOINT=ws://127.0.0.1:9944 uv run pytest -m e2e ``` ### Targeted regression tests [#targeted-regression-tests] `clones/js-tests/package.json` has one npm script per regression test, e.g.: ```bash npm run test:balancer-operation npm run test:locks-conviction npm run test:proxy-filter-security-regressions ``` Point a test at a different endpoint with `WS_ENDPOINT=ws://...`, and at a different runtime wasm with `RUNTIME_WASM_PATH=/path/to/runtime.wasm`. ## When it fails on your PR [#when-it-fails-on-your-pr] * **Upgrade step fails** — your runtime doesn't apply cleanly to mainnet state. Usually a migration panicking on real storage; reproduce with steps 1–4 above and watch the node log. Complement with [try-runtime](/docs/internals/testing#migration-tests-try-runtime). * **Smoke or regression tests fail** — your change altered on-chain behavior or storage shapes that existing features depend on. Run the specific failing script locally (see targeted tests above). * **SDK checks fail** — you changed calls, events, errors, or queries the SDK has codegen'd bindings for. Regenerate `bittensor/_generated/` against your upgraded clone (`python -m codegen ws://127.0.0.1:9944`) and commit the result; see [Testing](/docs/internals/testing#other-suites). * **Skipping**: adding the `skip-clone-upgrade` label to a PR skips the check; reserve that for changes that can't affect the runtime, SDK, or docs. # Release process (/docs/internals/release-process) Shipping is a single pipeline: **`release-train.yml`** runs on every push to `main`, builds the runtime once, and promotes that identical wasm through the networks. A second workflow, **`watch-mainnet-release.yml`**, watches the chain and publishes release artifacts once the mainnet upgrade actually executes. ## The release train [#the-release-train] ``` push to main └─ srtool build (once per train) └─ deploy devnet ───── smoke-check devnet └─ deploy testnet ── smoke-check testnet ── publish SDK rc to PyPI └─ propose mainnet upgrade (multisig) ``` ### Build once (srtool) [#build-once-srtool] The train builds the runtime with [srtool](https://github.com/paritytech/srtool) — a pinned Docker build environment that makes the wasm **byte-reproducible**. The artifact (`subtensor.wasm` + digest) is uploaded once and reused by every deploy job; promotion never rebuilds. Anyone can verify the hash independently by running `scripts/srtool/build-srtool-image.sh` followed by `scripts/srtool/run-srtool.sh` locally (see [Repository scripts](/docs/internals/scripts)). ### The ship lever: `spec_version` [#the-ship-lever-spec_version] Every deploy job first compares the built runtime's `spec_version` (`runtime/src/lib.rs`) with what the target chain is running, and **no-ops unless the build is newer**. Consequences: * A merge without a `spec_version` bump builds and then does nothing — this is deliberate, and it's why the Spec Version Check on PRs exists (label `no-spec-version-bump` to opt out knowingly). * Stale queued trains are harmless: they no-op at each guard. * Rollback is not a concept here — you ship a *newer* fixed version. ### Promotion gates [#promotion-gates] Human approval lives in GitHub **environment settings**, not workflow code: | Stage | Environment | Gate | | ------- | ----------- | -------------------------------------------------------------------------------------------- | | devnet | `devnet` | none — deploys automatically, then runs the devnet smoke suite | | testnet | `testnet` | environment reviewers (one-click approval), then smoke suite + SDK release candidate to PyPI | | mainnet | `mainnet` | environment reviewers **plus** the multisig ceremony below | Deploys to devnet and testnet are direct `setCode` transactions via the CI deploy key; each is followed by an on-chain `spec_version` verification and a smoke suite. ### The mainnet multisig ceremony [#the-mainnet-multisig-ceremony] CI never upgrades mainnet directly. The train's final job submits a **multisig proposal**: the CI key is one half of a 2-of-2 deployment multisig that holds a `SudoUncheckedSetCode` proxy on the sudo key. The triumvirate then approves the proposal 2-of-3 **out-of-band** — no GitHub credential can unilaterally change the mainnet runtime. Until they sign, nothing happens on chain. For rehearsal, the `mainnet-clone` PR label spins up a live clone of mainnet running your runtime with a public endpoint ([mainnet clone testing](/docs/internals/mainnet-clone)). ## The release watcher [#the-release-watcher] `watch-mainnet-release.yml` polls mainnet every 10 minutes. When the on-chain `spec_version` matches the `main` branch and no GitHub release exists for it yet, it cuts the release train's artifacts: 1. **GitHub release** `v` 2. **Python SDK + py-sp-core wheels** to PyPI (trusted publishing) 3. **Publishable Rust crates** to crates.io (auto-discovered; workspace crates are `publish = false` while they depend on the patched polkadot-sdk fork) 4. **Production website/docs** to Vercel This ordering means the release always reflects what is *actually running on mainnet*, not what was merged. ### Known gap: Docker images don't publish on release [#known-gap-docker-images-dont-publish-on-release] `docker.yml` and `docker-localnet.yml` trigger on `release: published` and are what tag `ghcr.io` images (including `:latest`) for a mainnet release. However, the watcher creates the release with the default `GITHUB_TOKEN`, and **GitHub does not trigger workflows from events created by the default token** (a recursion guard). So the production node image is currently *not* published when a release is cut. Until fixed (use a PAT/app token for `gh release create`, or have the watcher call `gh workflow run docker.yml -f branch-or-tag=v`), publish manually via `docker.yml`'s `workflow_dispatch` with the release tag. Note the release also carries no attached artifacts — the srtool wasm/digest live only as 90-day workflow artifacts on the train run. ## Hotfixes [#hotfixes] The same pipeline applies: land the fix on `main` with a `spec_version` bump and let the train promote it. There is no side channel that skips devnet and testnet validation. # Repository layout (/docs/internals/repo-layout) The repo is a monorepo: the Substrate chain, the Python SDK, the docs, and the websites all live together so a single PR (and a single CI run) can validate a runtime change against everything it might break. ## The chain (Rust, cargo workspace) [#the-chain-rust-cargo-workspace] | Directory | What it is | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `pallets/` | The FRAME pallets — the chain's business logic. `pallets/subtensor/` is the core (staking, subnets, emissions, consensus); others include `swap` (liquidity), `admin-utils`, `commitments`, `crowdloan`, `drand`, `limit-orders`, `proxy`, `shield`, `transaction-fee`, `utility` | | `runtime/` | Composes the pallets into the actual runtime (`construct_runtime!`), plus migrations, runtime APIs, and the `spec_version` (`runtime/src/lib.rs`) — bump it to ship a release | | `node/` | The node binary: networking, RPC, chain specs for each network (`node/src/chain_spec/`), and the `build-patched-spec` subcommand used by [mainnet clone testing](/docs/internals/mainnet-clone) | | `precompiles/` | EVM precompiles exposing substrate functionality (staking, balance transfers, …) to EVM contracts | | `chain-extensions/` | ink!/wasm contract chain extensions — the substrate-side counterpart of `ink-contract/` (see [Wasm contracts](/docs/internals/wasm-contracts)) | | `primitives/` | Small shared crates: `safe-math`, `share-pool`, `swap-interface` | | `common/` | `subtensor-runtime-common` — types shared between runtime, node, and pallets (`NetUid`, balances, …) | | `support/` | Tooling crates: custom lints (`linting/`), proc macros (`macros/`), `procedural-fork`, `weight-tools` (the `weight-compare` CI gate), `tools` | | `py-sp-core/` | PyO3 bindings around `sp-core` (keypairs, signing) — the native backbone of the Python SDK, published as wheels | | `src/` + `build.rs` (root) | A stub crate whose build script runs the custom lints in CI — not application code | ## Tests and test harnesses [#tests-and-test-harnesses] | Directory | What it is | | ------------ | ------------------------------------------------------------------------------------------------------------- | | `ts-tests/` | Moonwall/zombienet TypeScript integration tests ([Testing](/docs/internals/testing)) | | `clones/` | Mainnet-clone harness: scripts + JS regression tests ([Mainnet clone testing](/docs/internals/mainnet-clone)) | | `eco-tests/` | Indexer contract tests, excluded from the workspace ([Eco-tests](/docs/internals/eco-tests)) | ## SDK, docs, web [#sdk-docs-web] | Directory | What it is | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sdk/python/` | The `bittensor` Python SDK and `btcli` — bindings generated from chain metadata ([SDK tests](/docs/internals/sdk-tests)) | | `docs/` | This documentation — the single source of truth, rendered to docs.bittensor.com by the website app. `docs/tx/`, `docs/query/`, and `docs/errors.mdx` are generated; never hand-edit them | | `website/` | Yarn 4 + Turbo monorepo. `apps/bittensor-website` is the production site (marketing + these docs); `packages/` holds shared UI/API libraries | | `ink-contract/` | Standalone ink! contract source used by the wasm-contract tests | ## Chain data and operations [#chain-data-and-operations] | Directory / file | What it is | | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainspecs/` | Committed chain specs. The raw finney/testfinney specs are loaded by docker-compose and the clone scripts; **all six files are genesis anchors** for `scripts/build_all_chainspecs.sh` — the genesis wasm is not reproducible, so never delete them | | `snapshot.json` | Genesis state loaded by the embedded finney/test\_finney chain specs and bundled into Docker images | | `scripts/` | CI and developer scripts — each one documented in [Repository scripts](/docs/internals/scripts) | | `.github/workflows/` | CI: PR checks, the [release train](/docs/internals/release-process), Docker publishing | | `Dockerfile` / `docker-compose.yml` | Production node image and compose services for running mainnet/testnet nodes | | `Dockerfile-localnet` / `docker-compose.localnet.yml` | The localnet image used for [local development](/docs/guides/local-development) and SDK e2e tests | # Rust setup (/docs/internals/rust-setup) This guide is for reference only, please check the latest information on getting starting with Substrate [here](https://docs.polkadot.com/develop/parachains/install-polkadot-sdk/). This page will guide you through the **2 steps** needed to prepare a computer for **Substrate** development. Since Substrate is built with [the Rust programming language](https://www.rust-lang.org/), the first thing you will need to do is prepare the computer for Rust development - these steps will vary based on the computer's operating system. Once Rust is configured, you will use its toolchains to interact with Rust projects; the commands for Rust's toolchains will be the same for all supported, Unix-based operating systems. ## Build dependencies [#build-dependencies] Substrate development is easiest on Unix-based operating systems like macOS or Linux. The examples in the [Substrate Docs](https://docs.polkadot.com) use Unix-style terminals to demonstrate how to interact with Substrate from the command line. ### Ubuntu/Debian [#ubuntudebian] Use a terminal shell to execute the following commands: ```bash sudo apt update # May prompt for location information sudo apt install -y git clang curl libssl-dev llvm libudev-dev make pkg-config protobuf-compiler ``` ### Arch Linux [#arch-linux] Run these commands from a terminal: ```bash pacman -Syu --needed --noconfirm curl git clang ``` ### Fedora [#fedora] Run these commands from a terminal: ```bash sudo dnf update sudo dnf install clang curl git openssl-devel ``` ### OpenSUSE [#opensuse] Run these commands from a terminal: ```bash sudo zypper install clang curl git openssl-devel llvm-devel libudev-devel ``` ### macOS [#macos] > **Apple M1 ARM** > If you have an Apple M1 ARM system on a chip, make sure that you have Apple Rosetta 2 > installed through `softwareupdate --install-rosetta`. This is only needed to run the > `protoc` tool during the build. The build itself and the target binaries would remain native. Open the Terminal application and execute the following commands: ```bash # Install Homebrew if necessary https://brew.sh/ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" # Make sure Homebrew is up-to-date, install protobuf and openssl brew update brew install protobuf openssl llvm@16 ``` Also, add the following lines at the end of your \~/.zshrc: ``` # LLVM 16 from Homebrew export PATH="/opt/homebrew/opt/llvm@16/bin:$PATH" export CC="/opt/homebrew/opt/llvm@16/bin/clang" export CXX="/opt/homebrew/opt/llvm@16/bin/clang++" export LIBCLANG_PATH="/opt/homebrew/opt/llvm@16/lib/libclang.dylib" export LDFLAGS="-L/opt/homebrew/opt/llvm@16/lib" export CPPFLAGS="-I/opt/homebrew/opt/llvm@16/include" ``` ### Windows [#windows] ***PLEASE NOTE:*** Native Windows development of Substrate is *not* very well supported! It is *highly* recommend to use [Windows Subsystem Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) (WSL) and follow the following [instructions](https://docs.polkadot.com/develop/parachains/install-polkadot-sdk/#windows-wsl). ## Rust developer environment [#rust-developer-environment] This guide uses the [rustup.rs](https://rustup.rs) installer and the `rustup` tool to manage the Rust toolchain. First install and configure `rustup`: ```bash # Install curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Configure source ~/.cargo/env ``` This repository pins its Rust version and the `wasm32v1-none` target in `rust-toolchain.toml`, so `rustup` installs the correct toolchain automatically the first time you run `cargo` inside the repo. Alternatively, run the setup script: ```bash ./scripts/init.sh ``` ## Test your set-up [#test-your-set-up] Now the best way to ensure that you have successfully prepared a computer for Substrate development is to follow the steps in [our first Substrate tutorial](https://docs.polkadot.com/tutorials/v3/create-your-first-substrate-chain/). ## Troubleshooting builds [#troubleshooting-builds] To see what Rust toolchain you are presently using, run: ```bash rustup show ``` Inside the repository the active toolchain should be the version pinned in `rust-toolchain.toml` (with the `wasm32v1-none` target installed), regardless of your global default — `rustup` reads the pin automatically. If the pinned toolchain or its target is missing, install it explicitly: ```bash rustup toolchain install $(grep -oE '[0-9]+\.[0-9]+' rust-toolchain.toml | head -1) rustup target add wasm32v1-none ``` Substrate uses [WebAssembly](https://webassembly.org) (Wasm) to produce portable blockchain runtimes, which is why the `wasm32v1-none` target is required. Unlike upstream Substrate development, this repository does not use nightly Rust for the runtime build — the pinned toolchain builds everything. # Repository scripts (/docs/internals/scripts) Every script in `scripts/` is either wired into CI/Docker or kept as a manual developer tool. Unreferenced scripts were removed in the July 2026 cleanup (`raonet.sh`, `run/subtensor.sh`, `install_rust.sh`, `build.sh`, `publish.sh`, `release_notes.rs`, `specs/raonet.json`, `try-runtime-upgrade.sh`, `update-deps-to-path.sh` — the latter two are replaced by the inline instructions below). ## Used by CI / Docker [#used-by-ci--docker] Do not remove or rename these without updating their consumers. | Script | Used by | | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `benchmark_action.sh` | `run-benchmarks.yml` — validates benchmark weights on PRs | | `benchmark_all.sh` | Manual companion to the above; regenerates `weights.rs` for all pallets using `.maintain/frame-weight-template.hbs` (see [Benchmarks and weights](/docs/internals/benchmarks-and-weights)) | | `discover_pallets.sh` | Helper sourced by both benchmark scripts | | `build_all_chainspecs.sh` | `update-chainspec.yml` — rebuilds `chainspecs/*.json` while preserving each file's genesis. The genesis wasm is not reproducible across build architectures, so the committed chainspec files are the canonical genesis source. Never delete them. | | `docker_entrypoint.sh` | `Dockerfile` entrypoint for both image targets | | `fix_rust.sh` | PR template, AI-review tooling, and agent skills; runs fmt + clippy fix + zepter | | `install_build_env.sh` | `check-bittensor-e2e-tests.yml`, `docker-localnet.yml`, `Dockerfile-localnet` | | `install_prebuilt_binaries.sh` | `Dockerfile-localnet` | | `localnet.sh` | E2E CI, `Dockerfile-localnet`, SDK EVM setup, and the [local development guide](/docs/guides/local-development) — spins up a local dev chain (writes `specs/local.json`, gitignored) | | `localnet_patch.sh` | E2E CI and `docker-localnet.yml` | | `srtool/build-srtool-image.sh` | `propose-mainnet-upgrade.yml` — builds the local srtool Docker image | ## Chainspec files [#chainspec-files] The six JSON files in `chainspecs/` look like build artifacts but are all load-bearing. The genesis wasm is **not reproducible across build architectures**, so the committed files are the canonical genesis source: `build_all_chainspecs.sh` reads the genesis out of each existing file and splices it into the freshly built spec. Deleting any of them breaks the ability to regenerate specs with a matching genesis. Never delete them. | File | Role | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `raw_spec_finney.json` | Loaded at runtime by `docker-compose.yml` (mainnet nodes), `clones/scripts/clone-mainnet.sh`, and bundled into Docker images; also the finney genesis anchor | | `raw_spec_testfinney.json` | Loaded by `docker-compose.yml` (testnet nodes) and bundled into Docker images; testfinney genesis anchor | | `raw_spec_devnet.json` | Not loaded directly (the node embeds its devnet config in `node/src/chain_spec/devnet.rs`), but is the devnet genesis anchor for `build_all_chainspecs.sh` | | `plain_spec_finney.json` | Human-readable counterpart maintained by `build_all_chainspecs.sh`; genesis anchor for the plain finney spec | | `plain_spec_testfinney.json` | Same, for testfinney | | `plain_spec_devnet.json` | Same, for devnet | `update-chainspec.yml` (manual dispatch) runs `build_all_chainspecs.sh` and commits the result. The localnet spec is different: `localnet.sh` generates `scripts/specs/local.json` on the fly and it is gitignored. ## Doc-referenced [#doc-referenced] | Script | Purpose | | ------------------ | ----------------------------------------------------------------------------------------------- | | `code-coverage.sh` | Coverage run described in the root README (requires `cargo-tarpaulin`) | | `init.sh` | Rust toolchain init referenced by the [local development guide](/docs/guides/local-development) | | `map_consensus.py` | Generates the consensus plots referenced in [Yuma Consensus](/docs/internals/consensus) | ## Manual developer tools [#manual-developer-tools] These have no CI consumers but are kept for occasional use. ### `srtool/run-srtool.sh` [#srtoolrun-srtoolsh] Reproduces the deterministic srtool runtime build locally so you can verify that the wasm hash CI proposed on-chain (via `propose-mainnet-upgrade.yml`, which inlines the same `docker run`) matches an independent build. Run `srtool/build-srtool-image.sh` first to create the local `srtool` image. Requires `CARGO_HOME` to be set. Output lands in `runtime/node-subtensor/subtensor-digest.json`. ### Running try-runtime locally [#running-try-runtime-locally] CI tests runtime migrations on every PR via `.github/actions/try-runtime`. For an ad-hoc local run against an arbitrary endpoint or snapshot (requires `try-runtime-cli`): ```bash cargo build -p node-subtensor-runtime --release --features "metadata-hash,try-runtime" # Against a live chain try-runtime \ --runtime ./target/release/wbuild/node-subtensor-runtime/node_subtensor_runtime.compact.wasm \ on-runtime-upgrade \ --no-weight-warnings --disable-spec-version-check --disable-idempotency-checks \ --checks=all --blocktime 12000 \ live --uri wss://your-node:443 # Against a previously captured snapshot: replace the last line with # snap --path ./snapshot.bin ``` ### `test_specific.sh` [#test_specificsh] Runs a single pallet test file or case quickly, skipping the wasm build: ```bash ./scripts/test_specific.sh [pallet] [features] # defaults: pallet-subtensor, pow-faucet ``` Enables `--nocapture --exact` and `RUST_LOG=DEBUG`. ### Working against local `polkadot-sdk` / `frontier` checkouts [#working-against-local-polkadot-sdk--frontier-checkouts] When hacking on the forked `polkadot-sdk` or `frontier` alongside this repo, point the git dependencies at your local checkout with a `[patch]` section at the bottom of the root `Cargo.toml` instead of rewriting each dependency: ```toml [patch."https://github.com/RaoFoundation/polkadot-sdk.git"] sp-core = { path = "../polkadot-sdk/substrate/primitives/core" } # ...one entry per crate you are modifying ``` Cargo resolves everything else from the git dependency as usual. Don't commit the patch section. # Python SDK tests (/docs/internals/sdk-tests) The Python SDK lives in `sdk/python/` and is tested on every PR by the [Clone Upgrade Check](/docs/internals/mainnet-clone): offline gates first, then the e2e suite against a sudo-upgraded mainnet clone. A runtime change can fail your PR through these gates even if you never touched Python — this page explains how to run them and what to regenerate when metadata changes. Everything runs through [uv](https://docs.astral.sh/uv/) against the locked environment, orchestrated by the `justfile` in `sdk/python/`: ```bash cd sdk/python just sync # uv sync --locked --all-extras --dev just check # all offline gates, same as CI: lint, typecheck, unit tests, codegen-static ``` ## The test layers [#the-test-layers] | Layer | Needs a chain? | Command | | -------------------------- | ----------------------- | -------------------------------- | | Unit tests (`tests/unit/`) | No | `just test` (or `uv run pytest`) | | Codegen static gates | No | `just codegen-static` | | E2E tests (`tests/e2e/`) | Yes — localnet or clone | `just e2e [endpoint]` | | Codegen drift gate | Yes | `just drift [endpoint]` | Tests marked `e2e` are **deselected by default** (`addopts = "-m 'not e2e'"` in `pyproject.toml`), so a bare `pytest` run is always offline-safe. Run a single test the usual pytest way: ```bash uv run pytest tests/unit/test_codec_golden.py -k storage_keys -x ``` ## How unit tests avoid the network [#how-unit-tests-avoid-the-network] Two mechanisms make the offline suite possible: * **`FakeSubstrate`** (`tests/harness/fake_substrate.py`): the SDK's only chain-access seam is the `Substrate` protocol (`bittensor/_substrate.py`); the executor, intents, reads, and namespaces never touch a websocket directly. `FakeSubstrate` implements the whole protocol over plain dicts — `seed(module, item, params, value)` pins storage entries, unseeded items fall back to permissive defaults, and submitted extrinsics are recorded (never applied) so tests assert on the composed call. * **Golden fixtures** (`tests/fixtures/golden.json`, \~1.3 MB, committed): a corpus of byte-exact artifacts — storage keys, composed calls, signing payloads, extrinsic encodings — recorded from a live localnet along with the raw metadata they were produced against. The golden tests (`test_codec_golden.py`, `test_storage_golden.py`) replay these through the codec using only the recorded metadata, proving the wire format stays byte-identical without a node. ## E2E tests [#e2e-tests] `tests/e2e/` exercises real chain behavior (staking, governance, multisig, proxies, subnet lifecycle). The conftest picks its chain in order: 1. `E2E_ENDPOINT` set → attach to that node. This is the dev inner loop: run a [localnet](/docs/guides/local-development) yourself and iterate without docker churn. `just e2e` defaults to `ws://127.0.0.1:9944`. 2. Otherwise → start a disposable docker localnet (override the image with `LOCALNET_IMAGE`). CI runs the same suite with `E2E_ENDPOINT` pointed at the upgraded mainnet clone. ## After a runtime change: what to regenerate [#after-a-runtime-change-what-to-regenerate] The SDK's call/query/error bindings (`bittensor/_generated/`) are **generated from chain metadata** and committed. CI enforces two things: * **Static gates** (offline, every PR): every call is wrapped or explicitly raw-only (`codegen.check --coverage`), and every classified error name still exists (`codegen.check --names`). * **Drift gate**: the committed `_generated/` must match the node's actual metadata (`codegen.check --drift `). If your runtime change adds/removes/modifies extrinsics, storage, events, or errors, regenerate against a node running your runtime and commit the result: ```bash cd sdk/python just regen # ws://127.0.0.1:9944 by default ``` `just regen` does two things: `python -m codegen ` rewrites `bittensor/_generated/`, and `scripts/record_golden.py` re-records `tests/fixtures/golden.json`. Re-record the golden fixture **deliberately** — it is the byte-exactness baseline, so eyeball the diff before committing. Never hand-edit `_generated/` (ruff excludes it from formatting for the same reason). A convenient node to regen against is the sudo-upgraded [mainnet clone](/docs/internals/mainnet-clone), since that's exactly what CI tests you with. # Testing (/docs/internals/testing) The repo has several test layers. CI runs all of them on every PR, so knowing how to reproduce each locally saves round-trips: | Suite | What it covers | Run locally | CI workflow | | ---------------------------------------------- | ---------------------------------------------------------- | -------------------------------- | ------------------------- | | Rust unit tests | Pallet and runtime logic | `cargo test --workspace` (below) | `check-rust.yml` | | TypeScript / Moonwall | End-to-end chain behavior, EVM, zombienet multi-node | `ts-tests/` (below) | `typescript-e2e.yml` | | Migration checks | `on_runtime_upgrade` against live state | try-runtime CLI (below) | `try-runtime.yml` | | [Mainnet clone](/docs/internals/mainnet-clone) | Runtime upgrade + regression tests on cloned mainnet state | `clones/` scripts | `check-clone-upgrade.yml` | | [Python SDK](/docs/internals/sdk-tests) | SDK unit + e2e tests, codegen drift gates | `cd sdk/python && just check` | `check-clone-upgrade.yml` | | [eco-tests](/docs/internals/eco-tests) | Storage/RPC shapes the TAO.com indexer depends on | `cd eco-tests && cargo test` | `eco-tests.yml` | ## Rust tests [#rust-tests] Contributor guide rule: any pallet or runtime change must come with unit tests covering its edge cases. This is how you run them. ### All tests [#all-tests] ```bash SKIP_WASM_BUILD=1 cargo test --workspace ``` or, equivalently (the justfile exports `SKIP_WASM_BUILD=1` for you): ```bash just test ``` `SKIP_WASM_BUILD=1` skips compiling the wasm runtime blob, which the unit tests don't need — without it every test run pays a multi-minute wasm build first. CI (`check-rust.yml`) runs the same thing with all features enabled: ```bash SKIP_WASM_BUILD=1 cargo test --workspace --all-features ``` ### One pallet [#one-pallet] ```bash SKIP_WASM_BUILD=1 cargo test -p pallet-subtensor ``` Pallet tests live inside each crate under `src/tests/` (e.g. `pallets/subtensor/src/tests/staking.rs`), organized as modules of the library target. ### One test (or a group of tests) [#one-test-or-a-group-of-tests] Pass a name filter — every test whose full path contains the string runs: ```bash # Everything in the staking test module SKIP_WASM_BUILD=1 cargo test -p pallet-subtensor tests::staking # One test, with log output visible SKIP_WASM_BUILD=1 cargo test -p pallet-subtensor test_add_stake_ok_no_emission -- --nocapture # Exact match only (skip other tests whose names contain the same prefix) SKIP_WASM_BUILD=1 cargo test -p pallet-subtensor -- --exact tests::staking::test_add_stake_ok_no_emission ``` Add `RUST_LOG=debug` before the command to see runtime log lines, and `--release` if a test is too slow in debug mode (epoch/consensus tests often are). `scripts/test_specific.sh` wraps a release-mode single-test run with these flags preconfigured. ### Feature-gated tests [#feature-gated-tests] Some code only compiles under feature flags. The two you'll hit most: ```bash # Benchmark tests (also what `just benchmarks` runs) SKIP_WASM_BUILD=1 cargo test --workspace --features runtime-benchmarks # The faucet extrinsic used by localnet SKIP_WASM_BUILD=1 cargo test -p pallet-subtensor --features pow-faucet ``` When in doubt, mirror CI with `--all-features`. ### Migration tests (try-runtime) [#migration-tests-try-runtime] Storage migrations get two kinds of coverage. Unit tests live next to the other pallet tests (see `pallets/subtensor/src/tests/migration.rs`). To exercise a migration against **real chain state**, use try-runtime — this is what `try-runtime.yml` does on every PR against devnet, testnet, and mainnet state: ```bash cargo install --git https://github.com/paritytech/try-runtime-cli --locked ``` See [Repository scripts](/docs/internals/scripts#running-try-runtime-locally) for the build and invocation commands to replay `on_runtime_upgrade` against a live endpoint or snapshot. ## TypeScript integration tests [#typescript-integration-tests] TypeScript tests are run with [Moonwall](https://github.com/Moonsong-Labs/moonwall). You will need Node (see `ts-tests/.nvmrc`) and pnpm: ```bash cd ts-tests # Use the correct Node version nvm use # Install pnpm sudo npm i -g pnpm # Install dependencies pnpm i # Run manual seal dev tests pnpm moonwall test dev # Run zombienet tests (one environment per suite) pnpm moonwall test zombienet_staking pnpm moonwall test zombienet_shield pnpm moonwall test zombienet_coldkey_swap pnpm moonwall test zombienet_evm pnpm moonwall test zombienet_subnets # If you have MacOS, you might need to run zombienet tests with sudo, because tmp folder sudo pnpm moonwall test zombienet_staking # Run smoke tests (against devnet, testnet, or mainnet) pnpm moonwall test smoke_devnet pnpm moonwall test smoke_testnet pnpm moonwall test smoke_mainnet ``` Moonwall lets you also run the testing environment without performing any tests on it, as a method for you to manually test certain things: ```bash # Dev tests in run mode pnpm moonwall run dev # Zombienet test with run mode pnpm moonwall run zombienet_staking ``` ## Other suites [#other-suites] * **Mainnet clone** (`clones/`): clones live mainnet state, sudo-upgrades it to your runtime, and runs JS regression tests against it. Full walkthrough: [Mainnet clone testing](/docs/internals/mainnet-clone). * **Python SDK** (`sdk/python/`): `just sync && just check` runs the same offline gates as CI; e2e tests need a running localnet, and runtime metadata changes require regenerating the codegen bindings. Full walkthrough: [Python SDK tests](/docs/internals/sdk-tests). * **eco-tests** (`eco-tests/`): excluded from the cargo workspace, so run them from their own directory: `cd eco-tests && cargo test`. They pin the storage and RPC shapes the TAO.com ecosystem indexer consumes — see [Eco-tests](/docs/internals/eco-tests) for what to do when one fails. # Transaction priority (/docs/internals/transaction-priority) In Subtensor, transaction priority is determined by custom transaction extensions, which alter or override the default Substrate SDK behavior. Extensions affecting transaction priority are: * **`ChargeTransactionPaymentWrapper`** (wraps `ChargeTransactionPayment`) * **`DrandPriority`** Substrate SDK combines priorities from all transaction extensions using addition. ## `ChargeTransactionPaymentWrapper` [#chargetransactionpaymentwrapper] In the Substrate SDK, `ChargeTransactionPayment` normally calculates transaction priority based on: * **Tip** — an extra fee paid by the sender. * **Weight** — computational complexity of the transaction. * **Dispatch class** — category of the transaction (`Normal`, `Operational`, `Mandatory`). However, in Subtensor, `ChargeTransactionPaymentWrapper` **overrides** this logic. It replaces the dynamic calculation with a **flat priority scale** based only on the dispatch class. ### Current priority values [#current-priority-values] | Dispatch Class | Priority Value | Notes | | -------------- | ---------------- | ------------------------------------------------------------ | | `Normal` | `1` | Standard transactions | | `Mandatory` | `1` | Rarely used, same as `Normal` | | `Operational` | `10_000_000_000` | Reserved for critical system extrinsics (e.g.: `sudo` calls) | ## `DrandPriority` [#drandpriority] Special pallet\_drand priority: 10\_000 for `write_pulse` extrinsic. # WASM smart contracts (/docs/internals/wasm-contracts) Subtensor supports WebAssembly (WASM) smart contract functionality through the integration of `pallet-contracts`, enabling developers to deploy and execute WASM smart contracts on the network. Contracts are written in [ink!](https://use.ink/), a Rust-based embedded domain-specific language (eDSL) for writing smart contracts on Substrate-based chains. For compatibility, WASM contracts can also be compiled from Solidity using [Solang](https://github.com/hyperledger-solang/solang). If you're looking for information on EVM contracts, see the [EVM guide](/docs/guides/evm). ## Getting Started [#getting-started] For general smart contract development on Subtensor, please refer to the official ink! documentation: * [ink! Documentation](https://use.ink/docs/v5/) * [ink! Getting Started Guide](https://use.ink/docs/v5/getting-started/setup) * [ink! Examples](https://github.com/use-ink/ink-examples/tree/v5.x.x) ink! `>= 6.0` drops support for `pallet-contracts`, please use `ink < 6.0`. See the [ink! 6.0 release notes](https://github.com/use-ink/ink/releases/tag/v6.0.0-alpha). ## Subtensor-Specific Features [#subtensor-specific-features] ### Chain Extension [#chain-extension] Subtensor provides a custom chain extension that allows smart contracts to interact with Subtensor-specific functionality: #### Available Functions [#available-functions] | Function ID | Name | Description | Parameters | Returns | | ----------- | ------------------------------------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | 0 | `get_stake_info_for_hotkey_coldkey_netuid` | Query stake information | `(AccountId, AccountId, NetUid)` | `Option` | | 1 | `add_stake` | Delegate stake from coldkey to hotkey | `(AccountId, NetUid, TaoBalance)` | Error code | | 2 | `remove_stake` | Withdraw stake from hotkey back to coldkey | `(AccountId, NetUid, AlphaBalance)` | Error code | | 3 | `unstake_all` | Unstake all TAO from a hotkey | `(AccountId)` | Error code | | 4 | `unstake_all_alpha` | Unstake all Alpha from a hotkey | `(AccountId)` | Error code | | 5 | `move_stake` | Move stake between hotkeys | `(AccountId, AccountId, NetUid, NetUid, AlphaBalance)` | Error code | | 6 | `transfer_stake` | Transfer stake between coldkeys | `(AccountId, AccountId, NetUid, NetUid, AlphaBalance)` | Error code | | 7 | `swap_stake` | Swap stake allocations between subnets | `(AccountId, NetUid, NetUid, AlphaBalance)` | Error code | | 8 | `add_stake_limit` | Delegate stake with a price limit | `(AccountId, NetUid, TaoBalance, TaoBalance, bool)` | Error code | | 9 | `remove_stake_limit` | Withdraw stake with a price limit | `(AccountId, NetUid, AlphaBalance, TaoBalance, bool)` | Error code | | 10 | `swap_stake_limit` | Swap stake between subnets with price limit | `(AccountId, NetUid, NetUid, AlphaBalance, TaoBalance, bool)` | Error code | | 11 | `remove_stake_full_limit` | Fully withdraw stake with optional price limit | `(AccountId, NetUid, Option)` | Error code | | 12 | `set_coldkey_auto_stake_hotkey` | Configure automatic stake destination | `(NetUid, AccountId)` | Error code | | 13 | `add_proxy` | Add a staking proxy for the caller | `(AccountId)` | Error code | | 14 | `remove_proxy` | Remove a staking proxy for the caller | `(AccountId)` | Error code | | 15 | `get_alpha_price` | Query the current alpha price for a subnet | `(NetUid)` | `u64` (price × 10⁹) | | 16 | `recycle_alpha` | Recycle alpha stake, reducing SubnetAlphaOut (supply reduction) | `(AccountId, NetUid, AlphaBalance)` | `u64` (actual amount recycled) | | 17 | `burn_alpha` | Burn alpha stake without reducing SubnetAlphaOut (supply neutral) | `(AccountId, NetUid, AlphaBalance)` | `u64` (actual amount burned) | | 18 | `add_stake_recycle` | Atomically add stake then recycle the resulting alpha | `(AccountId, NetUid, TaoBalance)` | `u64` (alpha amount recycled) | | 19 | `add_stake_burn` | Atomically add stake then burn the resulting alpha | `(AccountId, NetUid, TaoBalance)` | `u64` (alpha amount burned) | | 20 | `caller_add_stake` | Caller-origin variant of `add_stake` (ID 1) | `(AccountId, NetUid, TaoBalance)` | Error code | | 21 | `caller_remove_stake` | Caller-origin variant of `remove_stake` (ID 2) | `(AccountId, NetUid, AlphaBalance)` | Error code | | 22 | `caller_unstake_all` | Caller-origin variant of `unstake_all` (ID 3) | `(AccountId)` | Error code | | 23 | `caller_unstake_all_alpha` | Caller-origin variant of `unstake_all_alpha` (ID 4) | `(AccountId)` | Error code | | 24 | `caller_move_stake` | Caller-origin variant of `move_stake` (ID 5) | `(AccountId, AccountId, NetUid, NetUid, AlphaBalance)` | Error code | | 25 | `caller_transfer_stake` | Caller-origin variant of `transfer_stake` (ID 6) | `(AccountId, AccountId, NetUid, NetUid, AlphaBalance)` | Error code | | 26 | `caller_swap_stake` | Caller-origin variant of `swap_stake` (ID 7) | `(AccountId, NetUid, NetUid, AlphaBalance)` | Error code | | 27 | `caller_add_stake_limit` | Caller-origin variant of `add_stake_limit` (ID 8) | `(AccountId, NetUid, TaoBalance, TaoBalance, bool)` | Error code | | 28 | `caller_remove_stake_limit` | Caller-origin variant of `remove_stake_limit` (ID 9) | `(AccountId, NetUid, AlphaBalance, TaoBalance, bool)` | Error code | | 29 | `caller_swap_stake_limit` | Caller-origin variant of `swap_stake_limit` (ID 10) | `(AccountId, NetUid, NetUid, AlphaBalance, TaoBalance, bool)` | Error code | | 30 | `caller_remove_stake_full_limit` | Caller-origin variant of `remove_stake_full_limit` (ID 11) | `(AccountId, NetUid, Option)` | Error code | | 31 | `caller_set_coldkey_auto_stake_hotkey` | Caller-origin variant of `set_coldkey_auto_stake_hotkey` (ID 12) | `(NetUid, AccountId)` | Error code | | 32 | `caller_add_proxy` | Caller-origin variant of `add_proxy` (ID 13) | `(AccountId)` | Error code | | 33 | `caller_remove_proxy` | Caller-origin variant of `remove_proxy` (ID 14) | `(AccountId)` | Error code | | 34 | `get_subnet_registration_state` | Query whether a subnet exists and which registration generation currently owns the netuid | `(NetUid)` | `SubnetRegistrationState { netuid, exists, registered_subnet_counter }` | | 35 | `get_coldkey_lock` | Query the current rolled-forward lock state for a coldkey on a subnet | `(AccountId, NetUid)` | `Option` | | 36 | `get_stake_availability` | Query total, locked, and currently available alpha for a coldkey on a subnet | `(AccountId, NetUid)` | `StakeAvailability { netuid: NetUid, total: AlphaBalance, locked: AlphaBalance, available: AlphaBalance }` | Functions **1–14** dispatch with the **contract's own address** as the signed origin. Their caller-origin mirrors (functions **20–33**) dispatch with the **original transaction signer's** origin instead; parameters and return values are identical to the corresponding base function. Functions **16** and **17** use the decoded argument order **`(hotkey, netuid, amount)`**, matching [`SubtensorChainExtension`](https://github.com/RaoFoundation/subtensor/blob/main/chain-extensions/src/lib.rs). If your ink! contract encoded **`(hotkey, amount, netuid)`** for those functions, **recompile and redeploy**; the runtime will decode the older layout incorrectly. Example usage in your ink! contract: ```rust #[ink::chain_extension(extension = 0)] pub trait SubtensorExtension { type ErrorCode = SubtensorError; #[ink(function = 0)] fn get_stake_info( hotkey: AccountId, coldkey: AccountId, netuid: u16, ) -> Result, SubtensorError>; } ``` #### Error Codes [#error-codes] Chain extension functions that modify state return error codes as `u32` values. The following codes are defined: | Code | Name | Description | | ---- | --------------------------------- | ------------------------------------------------- | | 0 | `Success` | Operation completed successfully | | 1 | `RuntimeError` | Unknown runtime error occurred | | 2 | `NotEnoughBalanceToStake` | Insufficient balance to complete stake operation | | 3 | `NonAssociatedColdKey` | Coldkey is not associated with the hotkey | | 4 | `BalanceWithdrawalError` | Error occurred during balance withdrawal | | 5 | `NotRegistered` | Hotkey is not registered in the subnet | | 6 | `NotEnoughStakeToWithdraw` | Insufficient stake available for withdrawal | | 7 | `TxRateLimitExceeded` | Transaction rate limit has been exceeded | | 8 | `SlippageTooHigh` | Price slippage exceeds acceptable threshold | | 9 | `SubnetNotExists` | Specified subnet does not exist | | 10 | `HotKeyNotRegisteredInSubNet` | Hotkey is not registered in the specified subnet | | 11 | `SameAutoStakeHotkeyAlreadySet` | Auto-stake hotkey is already configured | | 12 | `InsufficientBalance` | Account has insufficient balance | | 13 | `AmountTooLow` | Transaction amount is below minimum threshold | | 14 | `InsufficientLiquidity` | Insufficient liquidity for swap operation | | 15 | `SameNetuid` | Source and destination subnets are the same | | 16 | `ProxyTooMany` | Too many proxies registered | | 17 | `ProxyDuplicate` | Proxy already exists | | 18 | `ProxyNoSelfProxy` | Cannot add self as proxy | | 19 | `ProxyNotFound` | Proxy relationship not found | | 20 | `CannotUseSystemAccount` | A system account cannot be used in this operation | | 21 | `CannotBurnOrRecycleOnRootSubnet` | Cannot burn or recycle on the root subnet | | 22 | `SubtokenDisabled` | Subtoken is not enabled for the specified subnet | ### Call Filter [#call-filter] For security, contracts can only directly dispatch a limited set of runtime calls: **Whitelisted Calls:** * `Proxy::proxy` - Execute proxy calls All other runtime calls are restricted and cannot be dispatched from contracts. ### Configuration Parameters [#configuration-parameters] | Parameter | Value | Description | | ------------------------ | -------- | ------------------------------------------ | | Maximum code size | 128 KB | Maximum size of contract WASM code | | Call stack depth | 5 frames | Maximum nested contract call depth | | Runtime memory | 1 GB | Memory available during contract execution | | Validator runtime memory | 2 GB | Memory available for validators | | Transient storage | 1 MB | Maximum transient storage size | ## Additional Resources [#additional-resources] * [cargo-contract CLI Tool](https://github.com/paritytech/cargo-contract) * [Contracts UI](https://contracts-ui.substrate.io/) # alpha-price (/docs/query/alpha-price) **Category:** Prices & swaps ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | --------------------------------- | | `netuid` | integer | Subnet whose alpha price to read. | ## CLI [#cli] ```bash btcli query alpha-price --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("alpha_price", netuid=1) ``` # alpha-prices (/docs/query/alpha-prices) **Category:** Prices & swaps ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query alpha-prices --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("alpha_prices") ``` # associated-evm-key (/docs/query/associated-evm-key) **Category:** Neurons & registration ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ----------------------------------- | | `netuid` | integer | Subnet the neuron is registered on. | | `uid` | integer | UID of the neuron on that subnet. | ## CLI [#cli] ```bash btcli query associated-evm-key --netuid --uid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("associated_evm_key", netuid=1, uid=1) ``` # auto-stake-all (/docs/query/auto-stake-all) One entry per subnet where a destination hotkey is set; subnets with no entry have auto-stake unset. **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ---------------------------------------------- | | `coldkey_ss58` | string | Coldkey whose auto-stake destinations to list. | ## CLI [#cli] ```bash btcli query auto-stake-all --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("auto_stake_all", coldkey_ss58="5F...") ``` # auto-stake (/docs/query/auto-stake) **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------- | --------------------------------------------- | | `coldkey_ss58` | string | Coldkey whose auto-stake destination to read. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query auto-stake --coldkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("auto_stake", coldkey_ss58="5F...", netuid=1) ``` # balance (/docs/query/balance) **Category:** Accounts & keys ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ----------------------------------- | | `coldkey_ss58` | string | Coldkey whose free balance to read. | ## CLI [#cli] ```bash btcli query balance --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("balance", coldkey_ss58="5F...") ``` # balances (/docs/query/balances) **Category:** Accounts & keys ## Parameters [#parameters] | Parameter | Type | Description | | --------------- | ----- | ------------------------------------- | | `coldkey_ss58s` | array | Coldkeys whose free balances to read. | ## CLI [#cli] ```bash btcli query balances --coldkey-ss58s --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("balances", coldkey_ss58s=[...]) ``` # block-info (/docs/query/block-info) Programmatic callers wanting the fully decoded extrinsics and raw header should use `client.block_info()` instead. **Category:** Chain ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ------------------------- | | `block` | integer | Block number to describe. | ## CLI [#cli] ```bash btcli query block-info --block --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("block_info", block=1) ``` # block-time (/docs/query/block-time) **Category:** Chain ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query block-time --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("block_time") ``` # blocks-since-last-step (/docs/query/blocks-since-last-step) **Category:** Epochs & timing ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query blocks-since-last-step --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("blocks_since_last_step", netuid=1) ``` # blocks-since-last-update (/docs/query/blocks-since-last-update) (mechanism 0), or None. On commit-reveal subnets LastUpdate is bumped at commit time, not when the weights are revealed and applied. None means the subnet has no LastUpdate vector or the uid does not exist. The standard "is this validator still setting weights" health check. **Category:** Epochs & timing ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | --------------------------------- | | `netuid` | integer | Subnet to query. | | `uid` | integer | UID of the neuron on that subnet. | ## CLI [#cli] ```bash btcli query blocks-since-last-update --netuid --uid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("blocks_since_last_update", netuid=1, uid=1) ``` # blocks-until-next-epoch (/docs/query/blocks-until-next-epoch) Derived from the chain's own `next_epoch_start_block` at one pinned block. The actual fire can shift by a few blocks (per-block epoch cap defers, owner triggers pull earlier) — to act on the epoch itself, use `client.wait_for_epoch()`. **Category:** Epochs & timing ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query blocks-until-next-epoch --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("blocks_until_next_epoch", netuid=1) ``` # bonds (/docs/query/bonds) Bonds are the slow EMA of a validator's stake-weighted weights that pays its dividends; unlike weights their magnitude is meaningful, so values are scaled by the u16 maximum (1.0 = the largest bond the chain can store) rather than row-normalized. **Category:** Weights & bonds ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ----------------------------------------------------------------------- | | `netuid` | integer | Subnet whose bond matrix to fetch. | | `mechid` | integer | Mechanism index within the subnet; 0 (the default) on ordinary subnets. | ## CLI [#cli] ```bash btcli query bonds --netuid --mechid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("bonds", netuid=1, mechid=1) ``` # burn (/docs/query/burn) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query burn --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("burn", netuid=1) ``` # children (/docs/query/children) Proportions are u64-normalized fractions of the parent's stake, where u64::MAX means 100%. **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | ------------------------------------- | | `hotkey_ss58` | string | Parent hotkey whose children to list. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query children --hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("children", hotkey_ss58="5F...", netuid=1) ``` # coldkey-lock (/docs/query/coldkey-lock) `locked_alpha` is denominated in the subnet's alpha and rolled forward (decayed) to the current block; `is_perpetual` locks do not decay. **Category:** Locks & conviction ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------- | --------------------------- | | `coldkey_ss58` | string | Coldkey whose lock to read. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query coldkey-lock --coldkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("coldkey_lock", coldkey_ss58="5F...", netuid=1) ``` # coldkey-swap-announcement (/docs/query/coldkey-swap-announcement) This is the `swap-check` status: `ColdkeySwapAnnouncements` stores the block at which the swap becomes executable and the BlakeTwo256 hash committed to. **Category:** Accounts & keys ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ------------------------------------------------- | | `coldkey_ss58` | string | Coldkey whose pending swap announcement to check. | ## CLI [#cli] ```bash btcli query coldkey-swap-announcement --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("coldkey_swap_announcement", coldkey_ss58="5F...") ``` # commit-reveal-enabled (/docs/query/commit-reveal-enabled) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query commit-reveal-enabled --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("commit_reveal_enabled", netuid=1) ``` # commitment (/docs/query/commitment) **Category:** Identity & commitments ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | --------------------------------------- | | `netuid` | integer | Subnet the commitment was published on. | | `hotkey_ss58` | string | Hotkey that published the commitment. | ## CLI [#cli] ```bash btcli query commitment --netuid --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("commitment", netuid=1, hotkey_ss58="5F...") ``` # commitments (/docs/query/commitments) One row per hotkey that has (or had) a commitment — including sealed timelocked payloads waiting on drand (`is_revealed` false, `reveals_at` set) and fully-revealed ones whose live storage entry the chain already dropped. `commitment` is the currently visible content: the plaintext, or the latest chain-decrypted payload; null while still sealed. **Category:** Identity & commitments ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | --------------------------------- | | `netuid` | integer | Subnet whose commitments to list. | ## CLI [#cli] ```bash btcli query commitments --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("commitments", netuid=1) ``` # crowdloan-contributors (/docs/query/crowdloan-contributors) **Category:** Leases & crowdloans ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------- | ----------------------------------------------- | | `crowdloan_id` | integer | Id of the crowdloan whose contributors to list. | ## CLI [#cli] ```bash btcli query crowdloan-contributors --crowdloan-id --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("crowdloan_contributors", crowdloan_id=1) ``` # crowdloan (/docs/query/crowdloan) Amounts are TAO; `end` is the block number at which contributions close. **Category:** Leases & crowdloans ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------- | ------------------------------- | | `crowdloan_id` | integer | Id of the crowdloan to look up. | ## CLI [#cli] ```bash btcli query crowdloan --crowdloan-id --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("crowdloan", crowdloan_id=1) ``` # crowdloans (/docs/query/crowdloans) **Category:** Leases & crowdloans ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query crowdloans --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("crowdloans") ``` # delegate-take (/docs/query/delegate-take) `take`, `min`, and `max` are fractions in 0..1; `take_u16` is the raw on-chain u16 value. **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------ | ----------------------------------- | | `hotkey_ss58` | string | Delegate hotkey whose take to read. | ## CLI [#cli] ```bash btcli query delegate-take --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("delegate_take", hotkey_ss58="5F...") ``` # delegate (/docs/query/delegate) **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------ | --------------------------- | | `hotkey_ss58` | string | Delegate hotkey to look up. | ## CLI [#cli] ```bash btcli query delegate --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("delegate", hotkey_ss58="5F...") ``` # delegated (/docs/query/delegated) Each `stake` is denominated in the subnet's own currency: subnet alpha, or TAO when netuid is 0. **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ---------------------------------- | | `coldkey_ss58` | string | Coldkey whose nominations to list. | ## CLI [#cli] ```bash btcli query delegated --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("delegated", coldkey_ss58="5F...") ``` # delegates (/docs/query/delegates) **Category:** Delegation ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query delegates --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("delegates") ``` # difficulty (/docs/query/difficulty) **Category:** Hyperparameters ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query difficulty --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("difficulty", netuid=1) ``` # epoch-status (/docs/query/epoch-status) Bundles tempo, the last epoch run, the chain-computed next start block, and the remaining blocks/seconds (seconds use the chain's detected block time, so they are correct on fast-blocks chains too). `pending_epoch_at` is the block of an owner-triggered epoch, or None when none is pending. **Category:** Epochs & timing ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query epoch-status --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("epoch_status", netuid=1) ``` # existential-deposit (/docs/query/existential-deposit) **Category:** Accounts & keys ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query existential-deposit --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("existential_deposit") ``` # hotkey-conviction (/docs/query/hotkey-conviction) Returns `conviction_alpha` denominated in the subnet's alpha, rolled forward to the current block. **Category:** Locks & conviction ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | -------------------------------- | | `hotkey_ss58` | string | Hotkey whose conviction to read. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query hotkey-conviction --hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("hotkey_conviction", hotkey_ss58="5F...", netuid=1) ``` # hotkey-identities (/docs/query/hotkey-identities) **Category:** Identity & commitments ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ----- | ---------------------------------- | | `hotkey_ss58s` | array | Hotkeys to resolve identities for. | ## CLI [#cli] ```bash btcli query hotkey-identities --hotkey-ss58s --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("hotkey_identities", hotkey_ss58s=[...]) ``` # hotkey-owner (/docs/query/hotkey-owner) **Category:** Neurons & registration ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------ | --------------------------------------- | | `hotkey_ss58` | string | Hotkey whose owning coldkey to look up. | ## CLI [#cli] ```bash btcli query hotkey-owner --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("hotkey_owner", hotkey_ss58="5F...") ``` # identity (/docs/query/identity) **Category:** Identity & commitments ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ------------------------------- | | `coldkey_ss58` | string | Coldkey whose identity to read. | ## CLI [#cli] ```bash btcli query identity --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("identity", coldkey_ss58="5F...") ``` # immunity-period (/docs/query/immunity-period) **Category:** Hyperparameters ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query immunity-period --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("immunity_period", netuid=1) ``` # Queries (/docs/query) Reads are free, unsigned, and safe to call at any time. Each CLI command is `btcli query ` (add `--json` for machine-readable output); in Python use `await client.read("", ...)`. The machine-readable catalog is at [`/catalog/reads.json`](/catalog/reads.json) or via `sub.reads.list_reads()`. ## Accounts & keys [#accounts--keys] | Query | Summary | | -------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | [`balance`](/docs/query/balance) | Free TAO balance of a coldkey address. | | [`balances`](/docs/query/balances) | Free TAO balance for several coldkey addresses in one batched request. | | [`coldkey-swap-announcement`](/docs/query/coldkey-swap-announcement) | A coldkey's pending swap announcement (execute block, new-key hash, disputed), or None. | | [`existential-deposit`](/docs/query/existential-deposit) | Minimum balance an account must keep to stay alive. | | [`multisig`](/docs/query/multisig) | A pending multisig operation (opening timepoint, approvals, depositor), or None. | | [`proxies`](/docs/query/proxies) | Proxy delegations of an account: who may sign on its behalf, and the reserved deposit. | ## Chain [#chain] | Query | Summary | | -------------------------------------------------------- | ------------------------------------------------------------------------------------ | | [`block-info`](/docs/query/block-info) | A block's hash, timestamp, and extrinsics (as module.function summaries). | | [`block-time`](/docs/query/block-time) | Seconds per block, detected from the chain (12.0 mainnet, 0.25 fast-blocks). | | [`is-fast-blocks`](/docs/query/is-fast-blocks) | Whether the chain runs fast blocks (0.25s slots, local/e2e testing mode). | | [`mev-shield-next-key`](/docs/query/mev-shield-next-key) | The MEV Shield ML-KEM-768 public key (0x-hex) used to encrypt shielded txs, or None. | | [`timestamp`](/docs/query/timestamp) | Current chain time (from the Timestamp pallet) and the block it was read at. | | [`tx-rate-limit`](/docs/query/tx-rate-limit) | Global transaction rate limit in blocks. | ## Delegation [#delegation] | Query | Summary | | -------------------------------------------------- | ------------------------------------------------------------------------------- | | [`children`](/docs/query/children) | Child hotkeys of a parent on a subnet, as (proportion, child\_ss58) pairs. | | [`delegate`](/docs/query/delegate) | Delegate info for one hotkey, or None if it is not a delegate. | | [`delegate-take`](/docs/query/delegate-take) | A hotkey's delegate take (emission fraction it keeps) with the allowed min/max. | | [`delegated`](/docs/query/delegated) | Every nomination a coldkey holds: (delegate, netuid, stake) per position. | | [`delegates`](/docs/query/delegates) | Every delegate hotkey on the network, with take and registrations. | | [`is-delegate`](/docs/query/is-delegate) | Whether a hotkey is a delegate. | | [`parents`](/docs/query/parents) | Parent hotkeys of a child on a subnet, as (proportion, parent\_ss58) pairs. | | [`pending-children`](/docs/query/pending-children) | Proposed child hotkeys of a parent still in cooldown, and when they apply. | ## Epochs & timing [#epochs--timing] | Query | Summary | | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | | [`blocks-since-last-step`](/docs/query/blocks-since-last-step) | Blocks since the subnet's last epoch step ran. | | [`blocks-since-last-update`](/docs/query/blocks-since-last-update) | Blocks since a neuron last set or committed weights on a subnet | | [`blocks-until-next-epoch`](/docs/query/blocks-until-next-epoch) | Blocks until the subnet's next epoch fires, or None if tempo is 0. | | [`epoch-status`](/docs/query/epoch-status) | Where a subnet is in its epoch cycle, in one block-pinned read. | | [`next-epoch-start-block`](/docs/query/next-epoch-start-block) | Block at which the subnet's next epoch is expected to fire, or None if tempo is 0. | ## Hyperparameters [#hyperparameters] | Query | Summary | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`difficulty`](/docs/query/difficulty) | Current PoW registration difficulty for a subnet. | | [`immunity-period`](/docs/query/immunity-period) | Blocks a newly registered neuron is immune from deregistration. | | [`max-weight-limit`](/docs/query/max-weight-limit) | Normalized-fraction cap on any single weight: after the chain normalizes the submitted vector, no weight may exceed max\_weight\_limit/65535 of the total. Not a raw u16 ceiling per weight. | | [`min-allowed-weights`](/docs/query/min-allowed-weights) | Minimum number of weights a validator must set on a subnet. A pure self-weight (a single entry pointing at the caller's own uid) bypasses the minimum, and the subnet owner bypasses validator-permit rules. | | [`reveal-period`](/docs/query/reveal-period) | Commit-reveal reveal window, in epochs, for a subnet. | | [`weights-rate-limit`](/docs/query/weights-rate-limit) | Blocks a hotkey must wait between weight sets on a subnet. | ## Identity & commitments [#identity--commitments] | Query | Summary | | -------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | [`commitment`](/docs/query/commitment) | The commitment a hotkey has published on a subnet, or None. | | [`commitments`](/docs/query/commitments) | Every commitment on a subnet, newest first: hotkey, uid, content, block, age, reveal state. | | [`hotkey-identities`](/docs/query/hotkey-identities) | On-chain identities for hotkeys (via each hotkey's owner coldkey), keyed by hotkey. | | [`identity`](/docs/query/identity) | The on-chain identity of a coldkey (name, links, description), or None. | | [`revealed-commitment`](/docs/query/revealed-commitment) | The revealed (timelock-decrypted) commitment for a hotkey on a subnet, or None. | ## Leases & crowdloans [#leases--crowdloans] | Query | Summary | | -------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [`crowdloan`](/docs/query/crowdloan) | A crowdloan's state (creator, deposit, raised, cap, end, target/call), or None. | | [`crowdloan-contributors`](/docs/query/crowdloan-contributors) | Contributors and amounts for a crowdloan, with amounts in TAO. | | [`crowdloans`](/docs/query/crowdloans) | All crowdloans on chain (id and summary fields). | | [`lease`](/docs/query/lease) | A subnet lease by id (beneficiary, emissions share, end block, netuid, cost), or None. | | [`leases`](/docs/query/leases) | Every subnet lease on the network. | ## Locks & conviction [#locks--conviction] | Query | Summary | | ------------------------------------------------------------ | ------------------------------------------------------------------- | | [`coldkey-lock`](/docs/query/coldkey-lock) | Lock state for a coldkey on a subnet, or None if no lock exists. | | [`hotkey-conviction`](/docs/query/hotkey-conviction) | Conviction metrics for a hotkey on a subnet. | | [`locks-for-coldkey`](/docs/query/locks-for-coldkey) | Every lock a coldkey holds (one per subnet), rolled forward to now. | | [`most-convicted-hotkey`](/docs/query/most-convicted-hotkey) | Hotkey with the highest conviction on a subnet, if any. | | [`subnet-convictions`](/docs/query/subnet-convictions) | Every hotkey with locked stake on a subnet, rolled forward to now. | ## Neurons & registration [#neurons--registration] | Query | Summary | | ------------------------------------------------------ | ------------------------------------------------------------------------------------ | | [`associated-evm-key`](/docs/query/associated-evm-key) | The EVM address associated with a neuron (by netuid + uid) and the block it was set. | | [`hotkey-owner`](/docs/query/hotkey-owner) | The coldkey that owns a hotkey, or None if unowned. | | [`netuids-for-hotkey`](/docs/query/netuids-for-hotkey) | Every subnet a hotkey is registered on, as a sorted list of netuids. | | [`neurons`](/docs/query/neurons) | Every neuron on a subnet in ONE runtime-API call (the metagraph fast path). | | [`owned-hotkeys`](/docs/query/owned-hotkeys) | Every hotkey owned by a coldkey (the reverse of `hotkey_owner`). | | [`uid`](/docs/query/uid) | UID of a hotkey on a subnet, or None if not registered there. | ## Prices & swaps [#prices--swaps] | Query | Summary | | -------------------------------------------- | ---------------------------------------------------------------------------------- | | [`alpha-price`](/docs/query/alpha-price) | Current spot alpha price for a subnet, as TAO per alpha. | | [`alpha-prices`](/docs/query/alpha-prices) | Spot alpha price for every subnet, as TAO per alpha keyed by netuid. | | [`quote-stake`](/docs/query/quote-stake) | Simulate staking `amount_tao` TAO into a subnet: alpha out, fee, and slippage. | | [`quote-unstake`](/docs/query/quote-unstake) | Simulate unstaking `amount_alpha` alpha from a subnet: TAO out, fee, and slippage. | ## Staking [#staking] | Query | Summary | | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | [`auto-stake`](/docs/query/auto-stake) | The hotkey a coldkey auto-stakes its rewards to on a subnet, or None if unset. | | [`auto-stake-all`](/docs/query/auto-stake-all) | Every auto-stake destination configured for a coldkey. | | [`root-claim-type`](/docs/query/root-claim-type) | How a coldkey claims root alpha emission: Swap, Keep, or KeepSubnets(+subnets). | | [`stake`](/docs/query/stake) | Alpha staked by a coldkey to a hotkey on a subnet (TAO when netuid is 0). | | [`stake-for-coldkey`](/docs/query/stake-for-coldkey) | Every stake position held by a coldkey, across all hotkeys and subnets. | | [`stake-for-coldkeys`](/docs/query/stake-for-coldkeys) | Every stake position for several coldkeys at once, in one runtime call. | | [`stake-value-for-coldkey`](/docs/query/stake-value-for-coldkey) | A coldkey's stake marked to TAO at spot prices (excludes slippage/fees), block-pinned. | | [`stake-value-for-coldkeys`](/docs/query/stake-value-for-coldkeys) | Spot-price stake valuation for several coldkeys at once, at one block. | | [`staking-hotkeys`](/docs/query/staking-hotkeys) | Every hotkey a coldkey has stake with, owned or nominated. | ## Subnets [#subnets] | Query | Summary | | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | | [`burn`](/docs/query/burn) | Current burn (recycle) cost to register on a subnet. | | [`commit-reveal-enabled`](/docs/query/commit-reveal-enabled) | Whether commit-reveal weights are enabled on a subnet. | | [`mechanism-count`](/docs/query/mechanism-count) | Current mechanism count configured for a subnet. | | [`mechanism-emission-split`](/docs/query/mechanism-emission-split) | Emission split between mechanisms on a subnet. | | [`metagraph`](/docs/query/metagraph) | The full metagraph for a subnet in one call (stakes, ranks, emissions, axons, ...). | | [`subnet`](/docs/query/subnet) | Tempo, burn, and neuron count for one subnet (the three reads run concurrently). | | [`subnet-hyperparameters`](/docs/query/subnet-hyperparameters) | All hyperparameters for a subnet (named fields; version-dependent set). | | [`subnet-identity`](/docs/query/subnet-identity) | The identity metadata of a subnet, or None. | | [`subnet-names`](/docs/query/subnet-names) | Registered name of every subnet that published an identity, keyed by netuid. | | [`subnet-registration-cost`](/docs/query/subnet-registration-cost) | Current cost to register a new subnet. | | [`subnet-start-schedule`](/docs/query/subnet-start-schedule) | When a registered subnet can call `start_call`. | | [`subnets`](/docs/query/subnets) | Info for every subnet, fetched in four batched map queries rather than | | [`token-symbols`](/docs/query/token-symbols) | Chain-registered token symbol of every subnet, keyed by netuid. | ## Weights & bonds [#weights--bonds] | Query | Summary | | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | [`bonds`](/docs/query/bonds) | Validator bond rows as `{validator_uid: {miner_uid: bond}}`, scaled to 0..1. | | [`timelocked-weight-commits`](/docs/query/timelocked-weight-commits) | Pending (still-encrypted) commit-reveal weight commits, grouped by epoch. | | [`weights`](/docs/query/weights) | Validator weight rows as `{validator_uid: {miner_uid: fraction}}`, each row summing to 1. | # is-delegate (/docs/query/is-delegate) **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------ | ------------------------------------ | | `hotkey_ss58` | string | Hotkey to check for delegate status. | ## CLI [#cli] ```bash btcli query is-delegate --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("is_delegate", hotkey_ss58="5F...") ``` # is-fast-blocks (/docs/query/is-fast-blocks) **Category:** Chain ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query is-fast-blocks --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("is_fast_blocks") ``` # lease (/docs/query/lease) `emissions_share` is a percentage from 0 to 100; `end_block` is None for a perpetual lease. **Category:** Leases & crowdloans ## Parameters [#parameters] | Parameter | Type | Description | | ---------- | ------- | ---------------------------------- | | `lease_id` | integer | Id of the subnet lease to look up. | ## CLI [#cli] ```bash btcli query lease --lease-id --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("lease", lease_id=1) ``` # leases (/docs/query/leases) **Category:** Leases & crowdloans ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query leases --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("leases") ``` # locks-for-coldkey (/docs/query/locks-for-coldkey) **Category:** Locks & conviction ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ---------------------------- | | `coldkey_ss58` | string | Coldkey whose locks to list. | ## CLI [#cli] ```bash btcli query locks-for-coldkey --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("locks_for_coldkey", coldkey_ss58="5F...") ``` # max-weight-limit (/docs/query/max-weight-limit) **Category:** Hyperparameters ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query max-weight-limit --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("max_weight_limit", netuid=1) ``` # mechanism-count (/docs/query/mechanism-count) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query mechanism-count --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("mechanism_count", netuid=1) ``` # mechanism-emission-split (/docs/query/mechanism-emission-split) Returns the raw on-chain per-mechanism share values, in mechanism order; an empty list means no explicit split is set. **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query mechanism-emission-split --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("mechanism_emission_split", netuid=1) ``` # metagraph (/docs/query/metagraph) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | -------------------------------- | | `netuid` | integer | Subnet whose metagraph to fetch. | ## CLI [#cli] ```bash btcli query metagraph --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("metagraph", netuid=1) ``` # mev-shield-next-key (/docs/query/mev-shield-next-key) **Category:** Chain ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query mev-shield-next-key --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("mev_shield_next_key") ``` # min-allowed-weights (/docs/query/min-allowed-weights) **Category:** Hyperparameters ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query min-allowed-weights --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("min_allowed_weights", netuid=1) ``` # most-convicted-hotkey (/docs/query/most-convicted-hotkey) **Category:** Locks & conviction ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query most-convicted-hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("most_convicted_hotkey", netuid=1) ``` # multisig (/docs/query/multisig) **Category:** Accounts & keys ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ------------------------------------------------------ | | `account_ss58` | string | Multisig account the pending operation belongs to. | | `call_hash` | string | Hash of the multisig call, as 0x-prefixed or bare hex. | ## CLI [#cli] ```bash btcli query multisig --account --call-hash --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("multisig", account_ss58="5F...", call_hash="...") ``` # netuids-for-hotkey (/docs/query/netuids-for-hotkey) **Category:** Neurons & registration ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------ | ------------------------------------------ | | `hotkey_ss58` | string | Hotkey whose subnet registrations to list. | ## CLI [#cli] ```bash btcli query netuids-for-hotkey --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("netuids_for_hotkey", hotkey_ss58="5F...") ``` # neurons (/docs/query/neurons) `lite=true` (the default) omits per-neuron weights/bonds, which is far smaller and what almost every caller wants. **Category:** Neurons & registration ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | --------------------------------------------------------------------- | | `netuid` | integer | Subnet whose neurons to list. | | `lite` | boolean | Omit per-neuron weights/bonds (much smaller; what most callers want). | ## CLI [#cli] ```bash btcli query neurons --netuid --lite --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("neurons", netuid=1, lite=True) ``` # next-epoch-start-block (/docs/query/next-epoch-start-block) Chain-computed: `LastEpochBlock + tempo`, pulled earlier by any pending owner-triggered epoch. This is the source of truth — the epoch schedule is stateful, so the legacy client-side modular formula is wrong on subnets whose tempo changed or whose owner triggered an epoch. **Category:** Epochs & timing ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query next-epoch-start-block --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("next_epoch_start_block", netuid=1) ``` # owned-hotkeys (/docs/query/owned-hotkeys) **Category:** Neurons & registration ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ------------------------------ | | `coldkey_ss58` | string | Coldkey whose hotkeys to list. | ## CLI [#cli] ```bash btcli query owned-hotkeys --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("owned_hotkeys", coldkey_ss58="5F...") ``` # parents (/docs/query/parents) Proportions are u64-normalized fractions of the parent's stake, where u64::MAX means 100%. **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | ----------------------------------- | | `hotkey_ss58` | string | Child hotkey whose parents to list. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query parents --hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("parents", hotkey_ss58="5F...", netuid=1) ``` # pending-children (/docs/query/pending-children) `set_children` normally does not take effect immediately: the proposal is parked here until `cooldown_block`, then promoted to the finalized set that the `children` read returns. On subnets whose subtoken is not yet enabled the cooldown is skipped and children apply immediately, so nothing lingers here. `children` is (proportion, child\_ss58) pairs with u64-normalized proportions, matching the `children` read. **Category:** Delegation ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | --------------------------------------------- | | `hotkey_ss58` | string | Parent hotkey whose pending children to list. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query pending-children --hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("pending_children", hotkey_ss58="5F...", netuid=1) ``` # proxies (/docs/query/proxies) **Category:** Accounts & keys ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ---------------------------------------- | | `coldkey_ss58` | string | Account whose proxy delegations to list. | ## CLI [#cli] ```bash btcli query proxies --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("proxies", coldkey_ss58="5F...") ``` # quote-stake (/docs/query/quote-stake) Use this to compute a safe `limit_price_rao` for the `add_stake_limit` intent. **Category:** Prices & swaps ## Parameters [#parameters] | Parameter | Type | Description | | ------------ | ------- | -------------------------------- | | `netuid` | integer | Subnet to simulate staking into. | | `amount_tao` | number | TAO amount to simulate staking. | ## CLI [#cli] ```bash btcli query quote-stake --netuid --amount-tao --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("quote_stake", netuid=1, amount_tao=1.0) ``` # quote-unstake (/docs/query/quote-unstake) **Category:** Prices & swaps ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------- | ----------------------------------- | | `netuid` | integer | Subnet to simulate unstaking from. | | `amount_alpha` | number | Alpha amount to simulate unstaking. | ## CLI [#cli] ```bash btcli query quote-unstake --netuid --amount-alpha --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("quote_unstake", netuid=1, amount_alpha=1.0) ``` # reveal-period (/docs/query/reveal-period) **Category:** Hyperparameters ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query reveal-period --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("reveal_period", netuid=1) ``` # revealed-commitment (/docs/query/revealed-commitment) **Category:** Identity & commitments ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | --------------------------------------- | | `netuid` | integer | Subnet the commitment was published on. | | `hotkey_ss58` | string | Hotkey that published the commitment. | ## CLI [#cli] ```bash btcli query revealed-commitment --netuid --hotkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("revealed_commitment", netuid=1, hotkey_ss58="5F...") ``` # root-claim-type (/docs/query/root-claim-type) **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ----------------------------------------- | | `coldkey_ss58` | string | Coldkey whose root-claim setting to read. | ## CLI [#cli] ```bash btcli query root-claim-type --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("root_claim_type", coldkey_ss58="5F...") ``` # stake-for-coldkey (/docs/query/stake-for-coldkey) Each position's `stake` is denominated in the subnet's own currency: subnet alpha, or TAO when netuid is 0. Use `stake_value_for_coldkey` to mark alpha positions to TAO. **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | -------------------------------------- | | `coldkey_ss58` | string | Coldkey whose stake positions to list. | ## CLI [#cli] ```bash btcli query stake-for-coldkey --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("stake_for_coldkey", coldkey_ss58="5F...") ``` # stake-for-coldkeys (/docs/query/stake-for-coldkeys) Each position's `stake` is denominated in the subnet's own currency: subnet alpha, or TAO when netuid is 0. **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | --------------- | ----- | --------------------------------------- | | `coldkey_ss58s` | array | Coldkeys whose stake positions to list. | ## CLI [#cli] ```bash btcli query stake-for-coldkeys --coldkey-ss58s --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("stake_for_coldkeys", coldkey_ss58s=[...]) ``` # stake-value-for-coldkey (/docs/query/stake-value-for-coldkey) **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ----------------------------- | | `coldkey_ss58` | string | Coldkey whose stake to value. | ## CLI [#cli] ```bash btcli query stake-value-for-coldkey --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("stake_value_for_coldkey", coldkey_ss58="5F...") ``` # stake-value-for-coldkeys (/docs/query/stake-value-for-coldkeys) **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | --------------- | ----- | ------------------------------ | | `coldkey_ss58s` | array | Coldkeys whose stake to value. | ## CLI [#cli] ```bash btcli query stake-value-for-coldkeys --coldkey-ss58s --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("stake_value_for_coldkeys", coldkey_ss58s=[...]) ``` # stake (/docs/query/stake) **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------- | ---------------------------- | | `coldkey_ss58` | string | Coldkey that owns the stake. | | `hotkey_ss58` | string | Hotkey the stake is held on. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query stake --coldkey --hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("stake", coldkey_ss58="5F...", hotkey_ss58="5F...", netuid=1) ``` # staking-hotkeys (/docs/query/staking-hotkeys) Unlike `owned_hotkeys` this includes delegates the coldkey has nominated. Use `stake_for_coldkey` for the per-subnet amounts behind each entry. **Category:** Staking ## Parameters [#parameters] | Parameter | Type | Description | | -------------- | ------ | ---------------------------------------- | | `coldkey_ss58` | string | Coldkey whose staked-to hotkeys to list. | ## CLI [#cli] ```bash btcli query staking-hotkeys --coldkey --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("staking_hotkeys", coldkey_ss58="5F...") ``` # subnet-convictions (/docs/query/subnet-convictions) Per hotkey: locked mass, conviction, and the estimated blocks until its conviction reaches 10% of the subnet's outstanding alpha. That per-hotkey figure is a projection heuristic, not a takeover trigger: the ownership takeover in `change_subnet_owner_if_needed` requires the subnet to be at least \~1 year old (2,629,800 blocks) and the total aggregate conviction across all lockers to reach 10% of `SubnetAlphaOut`, at which point the highest-conviction hotkey's coldkey becomes the subnet owner. Projections assume the lock rates and alpha out stay constant. **Category:** Locks & conviction ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query subnet-convictions --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet_convictions", netuid=1) ``` # subnet-hyperparameters (/docs/query/subnet-hyperparameters) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query subnet-hyperparameters --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet_hyperparameters", netuid=1) ``` # subnet-identity (/docs/query/subnet-identity) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ------------------------------ | | `netuid` | integer | Subnet whose identity to read. | ## CLI [#cli] ```bash btcli query subnet-identity --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet_identity", netuid=1) ``` # subnet-names (/docs/query/subnet-names) **Category:** Subnets ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query subnet-names --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet_names") ``` # subnet-registration-cost (/docs/query/subnet-registration-cost) Denominated in TAO. The cost is dynamic: it rises when subnets register and decays back down over time. **Category:** Subnets ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query subnet-registration-cost --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet_registration_cost") ``` # subnet-start-schedule (/docs/query/subnet-start-schedule) All values are block numbers: the subnet can start once the current block reaches `earliest_start_block` (registration block plus the chain's delay). **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query subnet-start-schedule --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet_start_schedule", netuid=1) ``` # subnet (/docs/query/subnet) **Category:** Subnets ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query subnet --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnet", netuid=1) ``` # subnets (/docs/query/subnets) one-query-per-subnet. This is what listing should use. **Category:** Subnets ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query subnets --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("subnets") ``` # timelocked-weight-commits (/docs/query/timelocked-weight-commits) Each entry carries the committing `hotkey`, the `commit_block`, the drand `reveal_round` at which the chain auto-decrypts and applies it, `reveals_at` (that round's UTC time, computed locally), and the `ciphertext`. Entries disappear from this map once revealed — revealed weights show up in the `weights` read. **Category:** Weights & bonds ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ----------------------------------------------------------------------- | | `netuid` | integer | Subnet whose pending weight commits to list. | | `mechid` | integer | Mechanism index within the subnet; 0 (the default) on ordinary subnets. | ## CLI [#cli] ```bash btcli query timelocked-weight-commits --netuid --mechid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("timelocked_weight_commits", netuid=1, mechid=1) ``` # timestamp (/docs/query/timestamp) **Category:** Chain ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query timestamp --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("timestamp") ``` # token-symbols (/docs/query/token-symbols) `Client.connect` runs this automatically (through a disk cache) and installs the result as the connection's display symbols, so balances decoded by that client render with each subnet's real symbol. **Category:** Subnets ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query token-symbols --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("token_symbols") ``` # tx-rate-limit (/docs/query/tx-rate-limit) **Category:** Chain ## Parameters [#parameters] This read takes no parameters. ## CLI [#cli] ```bash btcli query tx-rate-limit --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("tx_rate_limit") ``` # uid (/docs/query/uid) **Category:** Neurons & registration ## Parameters [#parameters] | Parameter | Type | Description | | ------------- | ------- | ---------------------------- | | `hotkey_ss58` | string | Hotkey whose UID to look up. | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query uid --hotkey --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("uid", hotkey_ss58="5F...", netuid=1) ``` # weights-rate-limit (/docs/query/weights-rate-limit) **Category:** Hyperparameters ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ---------------- | | `netuid` | integer | Subnet to query. | ## CLI [#cli] ```bash btcli query weights-rate-limit --netuid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("weights_rate_limit", netuid=1) ``` # weights (/docs/query/weights) The chain stores max-upscaled u16 values whose absolute scale carries no meaning — consensus only uses within-row proportions — so rows are normalized to fractions here. On commit-reveal subnets a validator's row updates only when its timelocked commit reveals (see `timelocked_weight_commits` for what is pending). **Category:** Weights & bonds ## Parameters [#parameters] | Parameter | Type | Description | | --------- | ------- | ----------------------------------------------------------------------- | | `netuid` | integer | Subnet whose weight matrix to fetch. | | `mechid` | integer | Mechanism index within the subnet; 0 (the default) on ordinary subnets. | ## CLI [#cli] ```bash btcli query weights --netuid --mechid --json ``` ## Python [#python] ```python import bittensor as sub async with sub.Client("finney") as client: result = await client.read("weights", netuid=1, mechid=1) ``` # add-proxy (/docs/tx/add-proxy) The foundation of the keep-the-coldkey-offline setup: sign this once from the coldkey, then let the delegate submit day-to-day calls with `proxy_for=`. The chain reserves a small deposit from the signer per delegation (returned on removal). The delegate can act within `proxy_type` immediately (or after announcing, if `delay` > 0) — grant only to keys you control or fully trust. | Signer | Pallet | Wraps | | --------- | ------ | ----------------- | | `coldkey` | Proxy | `Proxy.add_proxy` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `delegate_ss58` | string | yes | Key that will be allowed to sign for this account. | | `proxy_type` | string | no | Scope of calls the delegation covers. One of: Any, Owner, NonCritical, NonTransfer, Senate, NonFungible, Triumvirate, Governance, Staking, Registration, Transfer, SmallTransfer, RootWeights, ChildKeys, SudoUncheckedSetCode, SwapHotkey, SubnetLeaseBeneficiary, RootClaim. Triumvirate, Senate, Governance, and RootWeights are deprecated on the current runtime: they deny all calls, so a proxy of those types can dispatch nothing. Prefer the narrowest type that covers your use; Any can do everything the account can, including transfers. | | `delay` | integer | no | Announcement delay in blocks: the delegate must announce each call and wait this long before executing it, giving you time to veto. 0 executes immediately. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx add-proxy \ --delegate --dry-run btcli tx add-proxy \ --delegate -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AddProxy(delegate_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("add_proxy", {...}, wallet) ``` # add-stake-limit (/docs/tx/add-stake-limit) Same as `add_stake` except the TAO-to-alpha swap only executes while the pool price stays within the limit. With `allow_partial` the call stakes as much as fits under the limit and leaves the rest in the free balance; without it the whole call fails once the limit would be breached. Like `add_stake`, the amount must be at least the chain minimum of 0.002 TAO plus the swap fee (`AmountTooLow`). Prefer this over plain `add_stake` for large amounts or thin pools, where the swap itself moves the price. | Signer | Pallet | Wraps | | --------- | --------------- | --------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.add_stake_limit` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `hotkey_ss58` | string | yes | Hotkey the stake is added to (the validator you are backing). | | `netuid` | integer | yes | Subnet the stake lives on (netuid 0 is the root network). | | `amount_tao` | number \| `"all"` | yes | How much of the coldkey's free balance to stake. | | `limit_price_rao` | integer | yes | Worst pool price you will accept for the swap. The call fails (or fills partially when allow-partial is set) instead of executing beyond this price. | | `allow_partial` | boolean | no | Execute whatever portion fits within the limit price and drop the remainder, instead of failing the whole call when the limit would be breached. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx add-stake-limit \ --hotkey \ --netuid \ --amount-tao \ --limit-price-rao --dry-run btcli tx add-stake-limit \ --hotkey \ --netuid \ --amount-tao \ --limit-price-rao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AddStakeLimit(hotkey_ss58="5F...", netuid=1, amount_tao=1.0, limit_price_rao=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("add_stake_limit", {...}, wallet) ``` # add-stake (/docs/tx/add-stake) Swaps TAO from the coldkey's free balance into the subnet's alpha at the current pool price and credits the result to your stake on the hotkey; on netuid 0 (root) the stake stays TAO-denominated. The swap moves the pool, so large amounts incur slippage — use `add_stake_limit` to bound the price. The position's value then follows the pool price and the validator's performance, and can be exited later with `remove_stake`. Fails if the coldkey's free balance cannot cover the amount plus the transaction fee, and with `AmountTooLow` when the amount is below the chain minimum of 0.002 TAO plus the swap fee. Dynamic subnets also reject a single swap larger than 1000x the pool's TAO reserve (`InsufficientLiquidity`). | Signer | Pallet | Wraps | | --------- | --------------- | --------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.add_stake` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ----------------- | -------- | ------------------------------------------------------------- | | `hotkey_ss58` | string | yes | Hotkey the stake is added to (the validator you are backing). | | `netuid` | integer | yes | Subnet the stake lives on (netuid 0 is the root network). | | `amount_tao` | number \| `"all"` | yes | How much of the coldkey's free balance to stake. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx add-stake \ --hotkey \ --netuid \ --amount-tao --dry-run btcli tx add-stake \ --hotkey \ --netuid \ --amount-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AddStake(hotkey_ss58="5F...", netuid=1, amount_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("add_stake", {...}, wallet) ``` # announce-coldkey-swap (/docs/tx/announce-coldkey-swap) Step one of the two-step coldkey migration: publishes only the BlakeTwo256 hash of `new_coldkey_ss58` — committing to the new key without revealing it — and starts the chain's announcement delay. After the delay, run the `swap_coldkey_announced` intent to move EVERYTHING this coldkey owns (balance, stake, subnets) to the new key; check timing with the `coldkey_swap_announcement` read. The first announcement charges the key-swap cost (0.1 TAO, recycled); re-announcing after the chain's reannouncement delay is free. While an announcement is pending, the chain blocks every other signed extrinsic from this coldkey — only the swap-related calls and shielded (encrypted) submission go through — so the account is operationally locked for the full delay. Before announcing, be certain you control the new coldkey and have its mnemonic backed up. A pending announcement can be cancelled with `clear_coldkey_swap_announcement`, and the legitimate holder can freeze an unauthorized one with `dispute_coldkey_swap`. | Signer | Pallet | Wraps | | --------- | --------------- | --------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.announce_coldkey_swap` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------------- | | `new_coldkey_ss58` | string | yes | Coldkey that will take over everything this coldkey owns once the swap executes; only its hash is published now. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx announce-coldkey-swap \ --new-coldkey --dry-run btcli tx announce-coldkey-swap \ --new-coldkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AnnounceColdkeySwap(new_coldkey_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("announce_coldkey_swap", {...}, wallet) ``` # associate-evm-key (/docs/tx/associate-evm-key) Links an Ethereum-style (H160) address to the signing hotkey on one subnet, letting EVM-side activity be attributed to that neuron. Signed by the hotkey, and additionally proven by the EVM key itself: `signature` must be an EIP-191 personal-sign signature by the EVM key over the message `hotkey_pubkey (32 bytes) ++ keccak_256(scale(block_number))`, where the block number is SCALE-encoded (u64 little-endian). Use `bittensor.evm.transactions.association_proof` to produce it (it needs the EVM private key); a wrong message, block number, or key makes the chain reject the call. Prerequisites: the hotkey must be registered on `netuid` (else HotKeyNotRegisteredInSubNet) and have an owning coldkey, and re-association is rate-limited to once per 7,200 blocks (\~1 day) per neuron. | Signer | Pallet | Wraps | | -------- | --------------- | ----------------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.associate_evm_key` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet on which the EVM key association is recorded. | | `evm_key` | string | yes | EVM address to link to the hotkey, as 0x-prefixed H160 hex. | | `block_number` | integer | yes | Block number the signature was produced for; part of the signed message. | | `signature` | string | yes | The EVM key's ownership proof, as 0x-prefixed hex: an EIP-191 personal-sign signature over the hotkey public key concatenated with keccak\_256 of the SCALE-encoded block number. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx associate-evm-key \ --netuid \ --evm-key \ --block-number \ --signature --dry-run btcli tx associate-evm-key \ --netuid \ --evm-key \ --block-number \ --signature -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AssociateEvmKey(netuid=1, evm_key="...", block_number=0, signature="...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("associate_evm_key", {...}, wallet) ``` # associate-hotkey (/docs/tx/associate-hotkey) Records on chain that the signing coldkey owns the hotkey, without registering it on any subnet or staking anything. Useful when the pairing should be visible before the hotkey's first registration (registration and staking establish it as a side effect). The chain only associates hotkeys that are not already owned: if the hotkey already belongs to a coldkey the call succeeds but is silently a no-op — it does not error, and it does not take over the hotkey. | Signer | Pallet | Wraps | | --------- | --------------- | -------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.try_associate_hotkey` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------- | | `hotkey_ss58` | string | no | Hotkey to record as owned by the signing coldkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx associate-hotkey --dry-run btcli tx associate-hotkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.AssociateHotkey() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("associate_hotkey", {...}, wallet) ``` # batch (/docs/tx/batch) Wraps the child calls in `Utility.batch_all`: they run in order and if any one fails the whole extrinsic reverts, so there is never a partially-applied result. Use it for multi-step operations that must land together (e.g. move funds then act on them) instead of submitting the steps separately and risking a half-done state. All children must share one signer — an extrinsic has a single signature — and batches cannot contain other batches. Spend limits and policy checks aggregate across every child, and the transaction plan lists each child's effects. | Signer | Pallet | Wraps | | --------- | ------- | ------------------- | | `coldkey` | Utility | `Utility.batch_all` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ----- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `intents` | array | yes | The calls to execute, in order, as a JSON list of objects \{"op": \, ...args}. At least one; all must share a signer; batches cannot nest. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx batch \ --intents --dry-run btcli tx batch \ --intents -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.Batch(intents=[...]) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("batch", {...}, wallet) ``` # burned-register (/docs/tx/burned-register) Burns the subnet's current registration cost from the signing coldkey and assigns the hotkey a UID on that subnet. The burned TAO is recycled, not staked — it cannot be recovered by deregistering. The cost floats with registration demand and is only known at execution time, so a configured spend cap blocks this call until raised. Fails with `SubNetRegistrationDisabled` while the subnet's `registration_allowed` toggle is off. On a full subnet, registering evicts the non-immune neuron with the lowest emission (ties broken by older registration block, then lower UID), and the new UID can itself be evicted once its immunity period ends. Use `root_register` instead for the root network (netuid 0). | Signer | Pallet | Wraps | | --------- | --------------- | --------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.burned_register` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | -------------------------------------------------------------- | | `netuid` | integer | yes | Subnet to register on. | | `hotkey_ss58` | string | no | Hotkey that receives the UID; defaults to the wallet's hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx burned-register \ --netuid --dry-run btcli tx burned-register \ --netuid -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.BurnedRegister(netuid=1) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("burned_register", {...}, wallet) ``` # claim-root (/docs/tx/claim-root) Pays out the signing coldkey's accrued root-stake dividends from the listed subnets. What the payout looks like depends on the coldkey's root claim type (see `set_root_claim_type`): swapped to TAO, kept as subnet alpha, or a per-subnet mix. At most 5 subnets per call — an empty or longer list fails with `InvalidSubnetNumber`. Subnets whose accrued dividends are below the per-subnet claim threshold (default 500,000 rao; adjustable by the subnet owner or root) are silently skipped while the transaction still succeeds. Unclaimed dividends simply keep accruing — there is no deadline — but each call pays out only the subnets listed. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.claim_root` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ---------------- | -------- | --------------------------------------------------------------------- | | `subnets` | array of integer | yes | Netuids to claim accumulated root dividends from; at most 5 per call. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx claim-root \ --subnets --dry-run btcli tx claim-root \ --subnets -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ClaimRoot(subnets=[...]) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("claim_root", {...}, wallet) ``` # clear-coldkey-swap-announcement (/docs/tx/clear-coldkey-swap-announcement) Withdraws this coldkey's own pending swap announcement so the swap can no longer be executed — use it if you announced to the wrong key or changed your mind. Only clearable once the current block reaches the swap's execute block plus the reannouncement delay — i.e. roughly the announcement delay (\~5 days) PLUS the reannouncement delay (\~1 day) after announcing — so it cannot be used to rapidly cycle announcements. Nothing moves; a fresh `announce_coldkey_swap` can be made afterwards. If the announcement was made by an attacker, `dispute_coldkey_swap` is the right call instead. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.clear_coldkey_swap_announcement` | ## Parameters [#parameters] This operation takes no parameters. Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx clear-coldkey-swap-announcement --dry-run btcli tx clear-coldkey-swap-announcement -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ClearColdkeySwapAnnouncement() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("clear_coldkey_swap_announcement", {...}, wallet) ``` # commit-weights (/docs/tx/commit-weights) Encrypts the weights with drand timelock encryption and submits the ciphertext; the weights stay hidden until the chain auto-decrypts and applies them at the reveal round (no separate reveal call is needed). Hiding weights until the round closes prevents other validators from copying them. Signed by the hotkey, with the same preflight, conforming, and rate-limit behavior as `set_weights`. Use `set_weights` instead unless you need to force the commit path regardless of the subnet's commit-reveal setting. | Signer | Pallet | Wraps | | -------- | --------------- | ----------------------------------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.commit_timelocked_mechanism_weights` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose miners the weights score. | | `uids` | array of integer | no | Miner UIDs being weighted, as a list parallel to weights. Omit when weights is given as a uid-to-weight mapping. | | `weights` | array of number | no | Relative weight per miner: either a JSON object mapping uid to weight, or a list parallel to uids. Values are relative, not absolute; they are clipped to the subnet's max-weight limit, normalized, and quantized before submission. | | `mechid` | integer | no | Mechanism index within the subnet. 0 is the default (and for most subnets the only) mechanism. | | `version_key` | integer | no | Weights version key checked against the subnet's required version. Leave 0 unless the subnet owner requires a specific value. | | `commit_reveal_version` | integer | no | Commit-reveal protocol version to tag the commit with. Keep the default unless the subnet requires an older protocol. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx commit-weights \ --netuid --dry-run btcli tx commit-weights \ --netuid -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.CommitWeights(netuid=1) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("commit_weights", {...}, wallet) ``` # contribute-crowdloan (/docs/tx/contribute-crowdloan) Moves the amount from the signer into the crowdloan's pot. The contribution must meet the loan's minimum (and per-contributor maximum, if set) and the loan must still be open — contributing after `end` or past the cap fails. Contributions are not refunded automatically if the loan fails: they come back when the creator calls `refund_crowdloan`, or the contributor can `withdraw_crowdloan` themselves any time before finalization. On success, the funds go to the loan's target or fund its inner call. | Signer | Pallet | Wraps | | --------- | --------- | ---------------------- | | `coldkey` | Crowdloan | `Crowdloan.contribute` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | | `amount_tao` | number \| `"all"` | yes | Amount to contribute; moved into the crowdloan's pot. Recoverable via refund or withdraw while the loan is not finalized. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx contribute-crowdloan \ --crowdloan-id \ --amount-tao --dry-run btcli tx contribute-crowdloan \ --crowdloan-id \ --amount-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ContributeCrowdloan(crowdloan_id=0, amount_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("contribute_crowdloan", {...}, wallet) ``` # create-crowdloan (/docs/tx/create-crowdloan) Locks `deposit_tao` from the creator (it counts toward the raise and comes back when the loan is dissolved) and opens the loan for contributions until `end`. Provide exactly one of `target_ss58` (the address that receives the raised funds on finalize) or `call` (an inner intent, `{"op": ..., ...args}`, dispatched with the creator's origin on finalize) — supplying both or neither is rejected. If the cap is reached the creator finalizes with `finalize_crowdloan`. Passing `end` only stops new contributions — nothing is refunded automatically: the creator returns contributions with `refund_crowdloan` (contributors may also `withdraw_crowdloan` themselves) and then `dissolve_crowdloan` to recover the deposit. | Signer | Pallet | Wraps | | --------- | --------- | ------------------ | | `coldkey` | Crowdloan | `Crowdloan.create` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ---------------------- | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `deposit_tao` | number \| `"all"` | yes | Creator's initial deposit, locked when the crowdloan opens. It counts toward the raise and is returned when the crowdloan is dissolved. | | `min_contribution_tao` | number \| `"all"` | yes | Smallest contribution the crowdloan will accept from a contributor. | | `cap_tao` | number \| `"all"` | yes | Maximum total to raise. Reaching the cap lets the creator finalize. | | `end` | integer | yes | Block number at which the crowdloan stops accepting contributions. Passing it refunds nothing by itself; refunds happen via explicit refund or withdraw calls. | | `target_ss58` | string | no | Account that receives the raised funds on finalize. Provide exactly one of this or call. | | `call` | object | no | Inner intent dispatched with the creator's origin on finalize, as a JSON object \{"op": \, ...args}. Provide exactly one of this or target\_ss58. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx create-crowdloan \ --deposit-tao \ --min-contribution-tao \ --cap-tao \ --end --dry-run btcli tx create-crowdloan \ --deposit-tao \ --min-contribution-tao \ --cap-tao \ --end -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.CreateCrowdloan(deposit_tao=1.0, min_contribution_tao=1.0, cap_tao=1.0, end=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("create_crowdloan", {...}, wallet) ``` # create-pure-proxy (/docs/tx/create-pure-proxy) The chain derives a new address from the signer, `proxy_type`, `index`, and the creation block. Nobody holds its private key — the spawner controls it purely through the proxy relationship, which makes it useful as a disposable or role-scoped account (e.g. a Staking-only treasury). Record the creation block and extrinsic index (shown in the result): `kill_pure_proxy` needs them to close the account later. Losing the spawner key means losing the pure proxy and anything it holds. | Signer | Pallet | Wraps | | --------- | ------ | ------------------- | | `coldkey` | Proxy | `Proxy.create_pure` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------ | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `proxy_type` | string | no | Scope of calls the delegation covers. One of: Any, Owner, NonCritical, NonTransfer, Senate, NonFungible, Triumvirate, Governance, Staking, Registration, Transfer, SmallTransfer, RootWeights, ChildKeys, SudoUncheckedSetCode, SwapHotkey, SubnetLeaseBeneficiary, RootClaim. Triumvirate, Senate, Governance, and RootWeights are deprecated on the current runtime: they deny all calls, so a proxy of those types can dispatch nothing. Prefer the narrowest type that covers your use; Any can do everything the account can, including transfers. | | `delay` | integer | no | Announcement delay in blocks: the delegate must announce each call and wait this long before executing it, giving you time to veto. 0 executes immediately. | | `index` | integer | no | Disambiguator so one signer can create several pure proxies in one block; also part of the derived address. Keep 0 unless batching. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx create-pure-proxy --dry-run btcli tx create-pure-proxy -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.CreatePureProxy() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("create_pure_proxy", {...}, wallet) ``` # decrease-take (/docs/tx/decrease-take) The delegate take is the fraction of staking emissions a delegate hotkey keeps for itself before distributing the rest to its nominators. This call only moves the take downward — the chain rejects values at or above the current take — and unlike increases it is not rate limited, so lowering your take is always available. Signed by the coldkey that owns the hotkey. Use `set_take` to land on an absolute value without tracking direction. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.decrease_take` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | `take` | integer | yes | New take as a u16 proportion (fraction of 65535, e.g. 5898 is about 9 percent). The chain enforces its configured take bounds. | | `hotkey_ss58` | string | no | Hotkey the operation applies to. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx decrease-take \ --take --dry-run btcli tx decrease-take \ --take -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.DecreaseTake(take=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("decrease_take", {...}, wallet) ``` # dispute-coldkey-swap (/docs/tx/dispute-coldkey-swap) The recovery path for a compromised coldkey: if a swap was announced on your coldkey that you did not initiate, disputing freezes the account entirely — the chain rejects ALL signed extrinsics from it, including executing the swap, clearing the announcement, or disputing again — until root clears the state via `reset_coldkey_swap`. Sign it from the affected coldkey itself. It does not cancel the announcement or move anything — it locks the situation so an attacker cannot complete the takeover while governance investigates; only root can unfreeze. Use `clear_coldkey_swap_announcement` instead to withdraw an announcement you made yourself. | Signer | Pallet | Wraps | | --------- | --------------- | -------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.dispute_coldkey_swap` | ## Parameters [#parameters] This operation takes no parameters. Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx dispute-coldkey-swap --dry-run btcli tx dispute-coldkey-swap -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.DisputeColdkeySwap() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("dispute_coldkey_swap", {...}, wallet) ``` # dissolve-crowdloan (/docs/tx/dissolve-crowdloan) Removes a non-finalized crowdloan from chain state once every non-creator contribution has been returned (run `refund_crowdloan` until no other contributors remain). Dissolving automatically transfers the creator's remaining contribution — including the deposit — back to them. Only the creator may dissolve. Dissolving while other contributions are still outstanding fails — refund first. | Signer | Pallet | Wraps | | --------- | --------- | -------------------- | | `coldkey` | Crowdloan | `Crowdloan.dissolve` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ---------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx dissolve-crowdloan \ --crowdloan-id --dry-run btcli tx dissolve-crowdloan \ --crowdloan-id -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.DissolveCrowdloan(crowdloan_id=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("dissolve_crowdloan", {...}, wallet) ``` # evm-withdraw (/docs/tx/evm-withdraw) Every native account controls one EVM address: the first 20 bytes of its public key (the *truncated* mapping). TAO sent from MetaMask to that address's mirror can be pulled into the native account with this call — the EVM-to-substrate path that needs no EVM gas. The flow: send TAO from the EVM wallet to the address shown by `btcli evm deposit-address`, then claim it with `btcli evm claim-deposit` (or `btcli tx evm-withdraw`). This is not `btcli evm send-to-ss58`, which spends from a stored EVM key via the balance-transfer precompile. Fails if the mirror holds less than the amount. | Signer | Pallet | Wraps | | --------- | ------ | -------------- | | `coldkey` | EVM | `EVM.withdraw` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------ | ----------------- | -------- | ------------------------------------- | | `amount_tao` | number \| `"all"` | yes | How much TAO to pull from the mirror. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx evm-withdraw \ --amount-tao --dry-run btcli tx evm-withdraw \ --amount-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.EvmWithdraw(amount_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("evm_withdraw", {...}, wallet) ``` # execute-proxy-announced (/docs/tx/execute-proxy-announced) The delayed-proxy flow: a delegate added with `delay` > 0 first announces the call's hash, waits out the delay (during which the real account can veto), then anyone may dispatch the announced call with this intent. The inner call is given as an intent op name plus its arguments and must hash to exactly what was announced. Fails if the delay has not elapsed or no matching announcement exists. | Signer | Pallet | Wraps | | --------- | ------ | ----------------------- | | `coldkey` | Proxy | `Proxy.proxy_announced` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------- | | `delegate_ss58` | string | yes | Delegate that made the announcement. | | `real_ss58` | string | yes | Account the call executes as (the delegation's grantor). | | `inner_op` | string | yes | Intent op name of the announced call (see btcli tools for the catalog). | | `inner_args` | object | yes | Arguments of the announced call, as a JSON object. | | `force_proxy_type` | string | no | Require the delegation to be exactly this proxy type instead of accepting any type that covers the call. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx execute-proxy-announced \ --delegate \ --real \ --inner-op \ --inner-args '' --dry-run btcli tx execute-proxy-announced \ --delegate \ --real \ --inner-op \ --inner-args '' -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ExecuteProxyAnnounced(delegate_ss58="5F...", real_ss58="5F...", inner_op="...", inner_args={...}) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("execute_proxy_announced", {...}, wallet) ``` # finalize-crowdloan (/docs/tx/finalize-crowdloan) Settles a successful raise: the pot is transferred to the loan's target address, or its inner call is dispatched with the creator's origin. Only the creator may finalize, and only once the cap has been reached. After finalization the crowdloan can no longer be updated, refunded, or dissolved. | Signer | Pallet | Wraps | | --------- | --------- | -------------------- | | `coldkey` | Crowdloan | `Crowdloan.finalize` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ---------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx finalize-crowdloan \ --crowdloan-id --dry-run btcli tx finalize-crowdloan \ --crowdloan-id -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.FinalizeCrowdloan(crowdloan_id=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("finalize_crowdloan", {...}, wallet) ``` # fund-evm-key (/docs/tx/fund-evm-key) An EVM account's native balance lives at its ss58 *mirror* address (`blake2_256("evm:" ++ h160)`). This intent computes the mirror and transfers TAO to it; the funds then appear as the EVM account's balance in MetaMask or any Ethereum tool (displayed with 18 decimals there: 1 TAO = 1e18). Like any transfer this is irreversible — and only the holder of the EVM private key can move the funds afterwards, so double-check the address. | Signer | Pallet | Wraps | | --------- | -------- | ------------------------------ | | `coldkey` | Balances | `Balances.transfer_keep_alive` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ----------------- | -------- | --------------------------------------------- | | `evm_address` | string | yes | EVM address to fund, as 0x-prefixed h160 hex. | | `amount_tao` | number \| `"all"` | yes | How much TAO to send. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx fund-evm-key \ --evm-address \ --amount-tao --dry-run btcli tx fund-evm-key \ --evm-address \ --amount-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.FundEvmKey(evm_address="...", amount_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("fund_evm_key", {...}, wallet) ``` # increase-take (/docs/tx/increase-take) The delegate take is the fraction of staking emissions a delegate hotkey keeps for itself before distributing the rest to its nominators. This call only moves the take upward: the chain rejects values at or below the current take, values above the configured maximum, and increases made sooner than the take rate limit allows. Signed by the coldkey that owns the hotkey. Use `set_take` if you just want to land on an absolute value without tracking the direction yourself. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.increase_take` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | `take` | integer | yes | New take as a u16 proportion (fraction of 65535, e.g. 5898 is about 9 percent). The chain enforces its configured take bounds. | | `hotkey_ss58` | string | no | Hotkey the operation applies to. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx increase-take \ --take --dry-run btcli tx increase-take \ --take -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.IncreaseTake(take=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("increase_take", {...}, wallet) ``` # Transactions (/docs/tx) Every mutation is an **intent**: preview it with `plan` / `--dry-run`, submit it with `execute`. Each CLI command below is `btcli tx `; each Python class is `sub.`. The machine-readable catalog of all of these (with JSON schemas) is at [`/catalog/intents.json`](/catalog/intents.json) or via `btcli tools` / `sub.intents.list_tools()`. ## AdminUtils [#adminutils] | Operation | Signer | Summary | | ----------------------------------------------------------------------- | ------- | --------------------------------------------------------------- | | [`set-hyperparameter`](/docs/tx/set-hyperparameter) | coldkey | Set an owner-settable subnet hyperparameter (btcli `sudo set`). | | [`set-mechanism-count`](/docs/tx/set-mechanism-count) | coldkey | Set the number of mechanisms on a subnet. | | [`set-mechanism-emission-split`](/docs/tx/set-mechanism-emission-split) | coldkey | Set emission split between mechanisms on a subnet. | | [`trim-subnet`](/docs/tx/trim-subnet) | coldkey | Trim a subnet to at most `max_n` UIDs (subnet owner). | ## Balances [#balances] | Operation | Signer | Summary | | --------------------------------------- | ------- | ------------------------------------------------------------------ | | [`fund-evm-key`](/docs/tx/fund-evm-key) | coldkey | Fund an EVM (h160) address with TAO from the signing coldkey. | | [`transfer`](/docs/tx/transfer) | coldkey | Transfer TAO from the coldkey to a destination address. | | [`transfer-all`](/docs/tx/transfer-all) | coldkey | Transfer the entire transferable balance to a destination address. | ## Crowdloan [#crowdloan] | Operation | Signer | Summary | | --------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------- | | [`contribute-crowdloan`](/docs/tx/contribute-crowdloan) | coldkey | Contribute TAO to an active crowdloan. | | [`create-crowdloan`](/docs/tx/create-crowdloan) | coldkey | Open a crowdloan raising up to `cap_tao` by `end` block. | | [`dissolve-crowdloan`](/docs/tx/dissolve-crowdloan) | coldkey | Dissolve a fully-refunded crowdloan (creator only). | | [`finalize-crowdloan`](/docs/tx/finalize-crowdloan) | coldkey | Finalize a crowdloan that reached its cap (creator only). | | [`refund-crowdloan`](/docs/tx/refund-crowdloan) | coldkey | Refund contributors of a non-finalized crowdloan (creator only). | | [`set-crowdloan-max-contribution`](/docs/tx/set-crowdloan-max-contribution) | coldkey | Set or clear the per-contributor max for a non-finalized crowdloan (creator only). | | [`update-crowdloan-cap`](/docs/tx/update-crowdloan-cap) | coldkey | Update the cap of a non-finalized crowdloan (creator only). | | [`update-crowdloan-end`](/docs/tx/update-crowdloan-end) | coldkey | Update the end block of a non-finalized crowdloan (creator only). | | [`update-crowdloan-min-contribution`](/docs/tx/update-crowdloan-min-contribution) | coldkey | Update the minimum contribution of a non-finalized crowdloan (creator only). | | [`withdraw-crowdloan`](/docs/tx/withdraw-crowdloan) | coldkey | Withdraw the signer's contribution from a non-finalized crowdloan. | ## EVM [#evm] | Operation | Signer | Summary | | --------------------------------------- | ------- | ---------------------------------------------------------- | | [`evm-withdraw`](/docs/tx/evm-withdraw) | coldkey | Claim TAO deposited to the coldkey's truncated EVM mirror. | ## Multisig [#multisig] | Operation | Signer | Summary | | ------------------------------------------------------- | ------- | ------------------------------------------------------------------------------- | | [`multisig-approve`](/docs/tx/multisig-approve) | coldkey | Register approval for a multisig call (non-final approvals). | | [`multisig-cancel`](/docs/tx/multisig-cancel) | coldkey | Cancel an ongoing multisig operation (only the original depositor may). | | [`multisig-execute`](/docs/tx/multisig-execute) | coldkey | Approve and, if the threshold is met, execute a multisig call (final approval). | | [`multisig-threshold-1`](/docs/tx/multisig-threshold-1) | coldkey | Dispatch a 1-of-N multisig call immediately (single approval). | ## Proxy [#proxy] | Operation | Signer | Summary | | ------------------------------------------------------------- | ------- | ----------------------------------------------------------------------- | | [`add-proxy`](/docs/tx/add-proxy) | coldkey | Authorize a delegate key to sign calls on this account's behalf. | | [`create-pure-proxy`](/docs/tx/create-pure-proxy) | coldkey | Create a pure proxy: a fresh keyless account controlled via delegation. | | [`execute-proxy-announced`](/docs/tx/execute-proxy-announced) | coldkey | Execute a proxy call that was announced and has passed its delay. | | [`kill-pure-proxy`](/docs/tx/kill-pure-proxy) | coldkey | Close a pure proxy account and return its reserved deposit. | | [`remove-proxies`](/docs/tx/remove-proxies) | coldkey | Revoke every proxy delegation for the signing account at once. | | [`remove-proxy`](/docs/tx/remove-proxy) | coldkey | Revoke one proxy delegation. | ## SubtensorModule [#subtensormodule] | Operation | Signer | Summary | | ----------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------- | | [`add-stake`](/docs/tx/add-stake) | coldkey | Stake TAO from the coldkey onto a hotkey. | | [`add-stake-limit`](/docs/tx/add-stake-limit) | coldkey | Stake TAO with a limit price (slippage protection). | | [`announce-coldkey-swap`](/docs/tx/announce-coldkey-swap) | coldkey | Announce (commit to) a coldkey swap; executable after the chain's delay. | | [`associate-evm-key`](/docs/tx/associate-evm-key) | hotkey | Associate an EVM key with a hotkey on a subnet. | | [`associate-hotkey`](/docs/tx/associate-hotkey) | coldkey | Associate a hotkey with the signing coldkey. | | [`burned-register`](/docs/tx/burned-register) | coldkey | Register a hotkey on a subnet by recycling TAO. | | [`claim-root`](/docs/tx/claim-root) | coldkey | Claim accumulated root dividends from one or more subnets. | | [`clear-coldkey-swap-announcement`](/docs/tx/clear-coldkey-swap-announcement) | coldkey | Cancel a pending coldkey swap announcement (after the reannouncement delay). | | [`commit-weights`](/docs/tx/commit-weights) | hotkey | Timelock-encrypt and commit weights, forcing the commit-reveal path. | | [`decrease-take`](/docs/tx/decrease-take) | coldkey | Decrease the delegate take of a hotkey. | | [`dispute-coldkey-swap`](/docs/tx/dispute-coldkey-swap) | coldkey | Freeze this coldkey entirely until root resolves the dispute. | | [`increase-take`](/docs/tx/increase-take) | coldkey | Increase the delegate take of a hotkey. | | [`lock-stake`](/docs/tx/lock-stake) | coldkey | Lock alpha stake on a subnet, building conviction toward a hotkey. | | [`move-lock`](/docs/tx/move-lock) | coldkey | Move an existing lock from one hotkey to another on a subnet. | | [`move-stake`](/docs/tx/move-stake) | coldkey | Move alpha between hotkeys and/or subnets. | | [`register-leased-network`](/docs/tx/register-leased-network) | coldkey | Register a new crowdloan-funded leased subnet. | | [`register-subnet`](/docs/tx/register-subnet) | coldkey | Create a new subnet owned by the signing coldkey. | | [`remove-stake`](/docs/tx/remove-stake) | coldkey | Unstake alpha from a hotkey back to the coldkey. | | [`remove-stake-limit`](/docs/tx/remove-stake-limit) | coldkey | Unstake alpha with a limit price (slippage protection). | | [`reset-axon`](/docs/tx/reset-axon) | hotkey | Reset (stop serving) this hotkey's axon endpoint on a subnet. | | [`reveal-weights`](/docs/tx/reveal-weights) | hotkey | Reveal previously committed weights (legacy salt-based commit-reveal). | | [`root-register`](/docs/tx/root-register) | coldkey | Register a hotkey on the root network (netuid 0). | | [`serve-axon`](/docs/tx/serve-axon) | hotkey | Publish this hotkey's axon endpoint (ip:port) for a subnet. | | [`serve-axon-tls`](/docs/tx/serve-axon-tls) | hotkey | Publish this hotkey's axon endpoint with a neuron certificate. | | [`serve-prometheus`](/docs/tx/serve-prometheus) | hotkey | Publish this hotkey's prometheus metrics endpoint (ip:port) for a subnet. | | [`set-auto-stake`](/docs/tx/set-auto-stake) | coldkey | Auto-stake future mining rewards on a subnet to a chosen hotkey. | | [`set-childkey-take`](/docs/tx/set-childkey-take) | coldkey | Set the childkey take for a hotkey on a subnet. | | [`set-children`](/docs/tx/set-children) | coldkey | Assign child hotkeys with stake-weight proportions on a subnet. | | [`set-identity`](/docs/tx/set-identity) | coldkey | Publish an on-chain identity (name, links, description) for the coldkey. | | [`set-perpetual-lock`](/docs/tx/set-perpetual-lock) | coldkey | Enable or disable perpetual lock mode for a coldkey on a subnet. | | [`set-root-claim-type`](/docs/tx/set-root-claim-type) | coldkey | Set how a coldkey's root alpha emission is claimed. | | [`set-subnet-identity`](/docs/tx/set-subnet-identity) | coldkey | Publish identity metadata for a subnet (signer must be the subnet owner). | | [`set-take`](/docs/tx/set-take) | coldkey | Set the delegate take to an absolute value. | | [`set-weights`](/docs/tx/set-weights) | hotkey | Set validator weights, auto-selecting plaintext or commit-reveal. | | [`stake-burn`](/docs/tx/stake-burn) | coldkey | Buy back / burn stake via the stake-burn extrinsic. | | [`start-call`](/docs/tx/start-call) | coldkey | Activate a subnet (subtoken trading, epochs) as its owner. | | [`swap-coldkey-announced`](/docs/tx/swap-coldkey-announced) | coldkey | Execute a previously announced coldkey swap (after the delay has passed). | | [`swap-hotkey`](/docs/tx/swap-hotkey) | coldkey | Swap a hotkey for a new one (all subnets, or one netuid). | | [`swap-stake`](/docs/tx/swap-stake) | coldkey | Swap stake on one hotkey between two subnets. | | [`terminate-lease`](/docs/tx/terminate-lease) | coldkey | Terminate an ended lease and take subnet ownership (beneficiary only). | | [`transfer-stake`](/docs/tx/transfer-stake) | coldkey | Transfer stake ownership to another coldkey. | | [`unstake-all`](/docs/tx/unstake-all) | coldkey | Unstake everything from a hotkey across all subnets. | | [`unstake-all-alpha`](/docs/tx/unstake-all-alpha) | coldkey | Unstake all alpha from a hotkey across subnets (moves it to root). | | [`update-symbol`](/docs/tx/update-symbol) | coldkey | Update a subnet's symbol (from the chain's fixed catalog). | ## Utility [#utility] | Operation | Signer | Summary | | ------------------------- | ------- | --------------------------------------------------------------------- | | [`batch`](/docs/tx/batch) | coldkey | Execute several intents atomically in one extrinsic (all-or-nothing). | # kill-pure-proxy (/docs/tx/kill-pure-proxy) Must be signed *by the pure proxy itself* (i.e. dispatched through it with `proxy_for=`), and the parameters must reproduce the exact creation: spawner, type, index, plus the block height and extrinsic index of the `create_pure_proxy` call. Irreversible — any funds left in the account become permanently inaccessible, so empty it first. | Signer | Pallet | Wraps | | --------- | ------ | ----------------- | | `coldkey` | Proxy | `Proxy.kill_pure` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ----------------------------------------------------------- | | `spawner_ss58` | string | yes | Account that originally created the pure proxy. | | `proxy_type` | string | no | Type the pure proxy was created with (must match exactly). | | `index` | integer | no | Index the pure proxy was created with (must match exactly). | | `height` | integer | no | Block number of the creating create\_pure\_proxy call. | | `ext_index` | integer | no | Extrinsic index of the creating call within that block. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx kill-pure-proxy \ --spawner --dry-run btcli tx kill-pure-proxy \ --spawner -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.KillPureProxy(spawner_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("kill_pure_proxy", {...}, wallet) ``` # lock-stake (/docs/tx/lock-stake) Commits part of the signing coldkey's alpha on the subnet as locked stake: the locked amount builds conviction the longer it stays locked. The lock acts as a subnet-wide floor on unstaking, not a hold on a specific position — the coldkey can freely unstake anything above the locked mass, and the locked amount itself keeps earning normally. The coldkey's total alpha on the subnet, summed across all hotkeys, must cover the locked amount; conviction can be pointed at one hotkey while the stake sits on another. If a lock already exists on the subnet, the hotkey must match the existing lock's hotkey or the call fails with `LockHotkeyMismatch` — repeat calls only top up the lock; use `move_lock` to change hotkeys. Whether the lock decays over time or persists is controlled per coldkey per subnet with `set_perpetual_lock`. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.lock_stake` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ----------------- | -------- | --------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet the locked stake lives on. | | `amount_alpha` | number \| `"all"` | yes | How much of the existing stake to lock. | | `hotkey_ss58` | string | no | Hotkey the lock's conviction is credited to. Defaults to the wallet hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx lock-stake \ --netuid \ --amount-alpha --dry-run btcli tx lock-stake \ --netuid \ --amount-alpha -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.LockStake(netuid=1, amount_alpha=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("lock_stake", {...}, wallet) ``` # move-lock (/docs/tx/move-lock) Re-points the signing coldkey's entire stake lock on the subnet at the destination hotkey, carrying the locked mass with it. Accrued conviction is preserved only when the origin and destination hotkeys are owned by the same coldkey (e.g. rotating your own validator hotkeys); if the destination hotkey belongs to a different coldkey, conviction resets to zero and matures again from scratch. The destination hotkey must exist on chain (fails with `HotKeyAccountNotExists`). Fails if there is no existing lock on the subnet to move. | Signer | Pallet | Wraps | | --------- | --------------- | --------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.move_lock` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------------- | ------- | -------- | ---------------------------- | | `netuid` | integer | yes | Subnet the lock lives on. | | `destination_hotkey_ss58` | string | yes | Hotkey the lock is moved to. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx move-lock \ --netuid \ --destination-hotkey --dry-run btcli tx move-lock \ --netuid \ --destination-hotkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.MoveLock(netuid=1, destination_hotkey_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("move_lock", {...}, wallet) ``` # move-stake (/docs/tx/move-stake) Re-delegates an existing position without passing through the coldkey's free balance: the stake leaves the origin hotkey on the origin subnet and lands on the destination hotkey at the destination subnet. Moving within one subnet just changes which validator backs the stake; moving across subnets swaps through both pools and can incur slippage on each leg. Ownership stays with the signing coldkey — use `transfer_stake` to hand the position to another coldkey, or `swap_stake` when only the subnet changes. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.move_stake` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `origin_hotkey_ss58` | string | yes | Hotkey the stake moves away from. | | `origin_netuid` | integer | yes | Subnet the stake currently sits on. | | `dest_hotkey_ss58` | string | yes | Hotkey the stake moves to. | | `dest_netuid` | integer | yes | Subnet the stake ends up on. When it differs from the origin, the position is swapped through both subnet pools, which can incur slippage on each leg. | | `amount_alpha` | number \| `"all"` | yes | How much of the origin position to move (an explicit amount; `all` is not accepted). | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx move-stake \ --origin-hotkey \ --origin-netuid \ --dest-hotkey \ --dest-netuid \ --amount-alpha --dry-run btcli tx move-stake \ --origin-hotkey \ --origin-netuid \ --dest-hotkey \ --dest-netuid \ --amount-alpha -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.MoveStake(origin_hotkey_ss58="5F...", origin_netuid=0, dest_hotkey_ss58="5F...", dest_netuid=0, amount_alpha=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("move_stake", {...}, wallet) ``` # multisig-approve (/docs/tx/multisig-approve) Records the signer's approval for a pending multisig operation without dispatching anything. The *opening* approval (omit `timepoint`; reserves a deposit from the signer) embeds the full call in the extrinsic, so every co-signer can recover the call — and a ready-to-run command — straight from `multisig pending`, with no out-of-band call data. Intermediate approvals (pass the opening `timepoint`) go up hash-only, which stays cheap even for huge calls like a runtime-upgrade blob. It never executes — once `threshold - 1` approvals exist, the last signatory must send `multisig_execute` with the full call. Approving twice from the same signer, or with a mismatched timepoint, threshold, or signatory set, fails. | Signer | Pallet | Wraps | | --------- | -------- | ------------------------------------------------ | | `coldkey` | Multisig | `Multisig.as_multi`, `Multisig.approve_as_multi` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `threshold` | integer | yes | Number of approvals required to execute, counting the signer. Together with the full signatory set it identifies the multisig account, so it must match on every approval. | | `other_signatories` | array | yes | The other members of the multisig (every signatory except the signer), as a JSON list of addresses. The same set must be given on every approval; order does not matter (sorted automatically). | | `call` | object | yes | The inner call to dispatch from the multisig account, as a JSON object \{"op": \, ...args}. All approvals must describe the identical call — it is matched by hash. Its arguments must be fully explicit, since it runs as the multisig account, not the signer's wallet. | | `timepoint` | object | no | Block height and extrinsic index of the approval that opened the operation, as a JSON object \{"height": ..., "index": ...}. Omit on the first approval; required on every later one (read it with the multisig query). | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx multisig-approve \ --threshold \ --other-signatories \ --call '' --dry-run btcli tx multisig-approve \ --threshold \ --other-signatories \ --call '' -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.MultisigApprove(threshold=0, other_signatories=[...], call={...}) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("multisig_approve", {...}, wallet) ``` # multisig-cancel (/docs/tx/multisig-cancel) Abandons a pending operation before it collects enough approvals: the stored approvals are discarded and the deposit reserved at opening is returned. Only the signatory who opened the operation (and paid the deposit) may cancel it. The threshold, signatory set, call, and opening `timepoint` must all match the pending operation exactly. | Signer | Pallet | Wraps | | --------- | -------- | -------------------------- | | `coldkey` | Multisig | `Multisig.cancel_as_multi` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `threshold` | integer | yes | Number of approvals required to execute, counting the signer. Together with the full signatory set it identifies the multisig account, so it must match on every approval. | | `other_signatories` | array | yes | The other members of the multisig (every signatory except the signer), as a JSON list of addresses. The same set must be given on every approval; order does not matter (sorted automatically). | | `call` | object | yes | The inner call to dispatch from the multisig account, as a JSON object \{"op": \, ...args}. All approvals must describe the identical call — it is matched by hash. Its arguments must be fully explicit, since it runs as the multisig account, not the signer's wallet. | | `timepoint` | object | yes | Block height and extrinsic index of the approval that opened the operation, as a JSON object \{"height": ..., "index": ...}. Required — it identifies which pending operation to cancel. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx multisig-cancel \ --threshold \ --other-signatories \ --call '' \ --timepoint '' --dry-run btcli tx multisig-cancel \ --threshold \ --other-signatories \ --call '' \ --timepoint '' -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.MultisigCancel(threshold=0, other_signatories=[...], call={...}, timepoint={...}) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("multisig_cancel", {...}, wallet) ``` # multisig-execute (/docs/tx/multisig-execute) Sends the full inner call along with an approval. If this is the first approval (omit `timepoint`), it opens the operation and reserves a deposit from the signer, returned when the operation completes or is cancelled. If it is the final approval — bringing the count to `threshold` — the inner call executes as the multisig account in the same extrinsic. Intermediate signers can use the cheaper `multisig_approve` (hash only), but whoever approves last must use this intent so the chain has the call to run. Every approval must repeat the same threshold, signatory set, and call; later approvals must also pass the opening `timepoint` or they will not match the pending operation. | Signer | Pallet | Wraps | | --------- | -------- | ------------------- | | `coldkey` | Multisig | `Multisig.as_multi` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `threshold` | integer | yes | Number of approvals required to execute, counting the signer. Together with the full signatory set it identifies the multisig account, so it must match on every approval. | | `other_signatories` | array | yes | The other members of the multisig (every signatory except the signer), as a JSON list of addresses. The same set must be given on every approval; order does not matter (sorted automatically). | | `call` | object | yes | The inner call to dispatch from the multisig account, as a JSON object \{"op": \, ...args}. All approvals must describe the identical call — it is matched by hash. Its arguments must be fully explicit, since it runs as the multisig account, not the signer's wallet. | | `timepoint` | object | no | Block height and extrinsic index of the approval that opened the operation, as a JSON object \{"height": ..., "index": ...}. Omit on the first approval; required on every later one (read it with the multisig query). | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx multisig-execute \ --threshold \ --other-signatories \ --call '' --dry-run btcli tx multisig-execute \ --threshold \ --other-signatories \ --call '' -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.MultisigExecute(threshold=0, other_signatories=[...], call={...}) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("multisig_execute", {...}, wallet) ``` # multisig-threshold-1 (/docs/tx/multisig-threshold-1) For multisig accounts with threshold 1, where any single member may act alone: the call executes in this same extrinsic, with no approval round, no timepoint, and no deposit. The multisig account is derived from the signer plus `other_signatories`, so the full member set must still be supplied even though nobody else signs. For thresholds above 1 use `multisig_execute` / `multisig_approve` instead. | Signer | Pallet | Wraps | | --------- | -------- | ------------------------------- | | `coldkey` | Multisig | `Multisig.as_multi_threshold_1` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `other_signatories` | array | yes | The other members of the multisig (every signatory except the signer), as a JSON list of addresses. The same set must be given on every approval; order does not matter (sorted automatically). | | `call` | object | yes | The inner call to dispatch from the multisig account, as a JSON object \{"op": \, ...args}. All approvals must describe the identical call — it is matched by hash. Its arguments must be fully explicit, since it runs as the multisig account, not the signer's wallet. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx multisig-threshold-1 \ --other-signatories \ --call '' --dry-run btcli tx multisig-threshold-1 \ --other-signatories \ --call '' -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.MultisigThreshold1(other_signatories=[...], call={...}) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("multisig_threshold_1", {...}, wallet) ``` # refund-crowdloan (/docs/tx/refund-crowdloan) Returns contributions (excluding the creator's) to their contributors. Only the creator may call it. Each call refunds at most 50 contributors, so large loans may need several `refund_crowdloan` calls before everyone is paid back. Once all contributors are refunded, the creator runs `dissolve_crowdloan`, which returns the creator's remaining contribution (including the deposit) and removes the loan. | Signer | Pallet | Wraps | | --------- | --------- | ------------------ | | `coldkey` | Crowdloan | `Crowdloan.refund` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ---------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx refund-crowdloan \ --crowdloan-id --dry-run btcli tx refund-crowdloan \ --crowdloan-id -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RefundCrowdloan(crowdloan_id=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("refund_crowdloan", {...}, wallet) ``` # register-leased-network (/docs/tx/register-leased-network) Creates a subnet paid for by a crowdloan rather than a single coldkey: the crowdloan's funds cover the network registration cost (leftover cap is refunded to contributors pro-rata, with any rounding remainder going to the beneficiary), contributors earn `emissions_share` percent of the subnet owner's emission cut (not of total subnet emissions) as dividends, and the beneficiary operates the subnet through a scoped proxy. Must be dispatched in a crowdloan context — it fails as a standalone call. The cost is not cheaply boundable up front, so a configured spend cap blocks this until raised. With an `end_block` the beneficiary can later take full ownership via `terminate_lease`; without one the lease is perpetual and ownership never transfers. | Signer | Pallet | Wraps | | --------- | --------------- | ----------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.register_leased_network` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------- | | `emissions_share` | integer | yes | Percent (0-100) of the subnet owner's emission cut paid to crowdloan contributors as dividends. | | `end_block` | integer | no | Block at which the lease ends and the beneficiary may take ownership; omit for a perpetual lease. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx register-leased-network \ --emissions-share --dry-run btcli tx register-leased-network \ --emissions-share -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RegisterLeasedNetwork(emissions_share=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("register_leased_network", {...}, wallet) ``` # register-subnet (/docs/tx/register-subnet) Registers a brand-new subnet with the signing coldkey as its owner and the wallet's hotkey as the subnet-owner hotkey. The network registration cost — potentially thousands of TAO — is taken from the coldkey; it doubles after each new subnet registration and decays linearly back over the lock reduction interval, and is only known at execution time, so a configured spend cap blocks this call until raised. The full cost becomes the new subnet's initial TAO pool reserve — a sunk cost, not a refundable deposit. Network registrations are rate-limited per coldkey. If the chain is at its subnet limit, registering dissolves the non-immune subnet with the lowest EMA price to free the slot (the new subnet reuses its netuid). The new subnet starts inactive: call `start_call` once the chain's activation delay has passed to activate it; the subnet's share of TAO emission additionally stays off until root enables the subnet's emission-enabled flag. This is a major, expensive commitment — check the current cost before sending. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.register_network` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------------ | | `hotkey_ss58` | string | no | Subnet-owner hotkey for the new subnet; defaults to the wallet's hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx register-subnet --dry-run btcli tx register-subnet -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RegisterSubnet() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("register_subnet", {...}, wallet) ``` # remove-proxies (/docs/tx/remove-proxies) A cleanup/panic switch: all delegates lose access in one call and all proxy deposits are returned. Careful if this account spawned pure proxies — they are controlled *through* delegations, so removing everything can strand them permanently (there is no key to recover a pure proxy with). | Signer | Pallet | Wraps | | --------- | ------ | ---------------------- | | `coldkey` | Proxy | `Proxy.remove_proxies` | ## Parameters [#parameters] This operation takes no parameters. Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx remove-proxies --dry-run btcli tx remove-proxies -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RemoveProxies() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("remove_proxies", {...}, wallet) ``` # remove-proxy (/docs/tx/remove-proxy) The (delegate, proxy\_type, delay) triple must match the original `add_proxy` exactly — a mismatch fails with NotFound rather than removing a different delegation. The proxy deposit reserved at add time is returned to the signer. Check current delegations with `btcli query proxies`. | Signer | Pallet | Wraps | | --------- | ------ | -------------------- | | `coldkey` | Proxy | `Proxy.remove_proxy` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------------- | ------- | -------- | ----------------------------------------------------------- | | `delegate_ss58` | string | yes | Delegate whose authorization to revoke. | | `proxy_type` | string | no | Type the delegation was granted with (must match exactly). | | `delay` | integer | no | Delay the delegation was granted with (must match exactly). | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx remove-proxy \ --delegate --dry-run btcli tx remove-proxy \ --delegate -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RemoveProxy(delegate_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("remove_proxy", {...}, wallet) ``` # remove-stake-limit (/docs/tx/remove-stake-limit) Same as `remove_stake` except the alpha-to-TAO swap only executes while the pool price stays within the limit. With `allow_partial` it unstakes what it can within the limit and leaves the rest staked; without it the whole call fails once the limit would be breached. Pass `all` to target the entire position (the build fails if nothing is staked there). Like `remove_stake`, a partial unstake must leave a remainder worth at least 0.002 TAO at the simulated pool price (`AmountTooLow`). Prefer this over plain `remove_stake` when exiting large positions. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------------ | | `coldkey` | SubtensorModule | `SubtensorModule.remove_stake_limit` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `hotkey_ss58` | string | yes | Hotkey the stake is held on (the validator backing the position). | | `netuid` | integer | yes | Subnet the stake lives on (netuid 0 is the root network). | | `amount_alpha` | number \| `"all"` | yes | How much to unstake from this position, or `all`. | | `limit_price_rao` | integer | yes | Worst pool price you will accept for the swap. The call fails (or fills partially when allow-partial is set) instead of executing beyond this price. | | `allow_partial` | boolean | no | Execute whatever portion fits within the limit price and drop the remainder, instead of failing the whole call when the limit would be breached. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx remove-stake-limit \ --hotkey \ --netuid \ --amount-alpha \ --limit-price-rao --dry-run btcli tx remove-stake-limit \ --hotkey \ --netuid \ --amount-alpha \ --limit-price-rao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RemoveStakeLimit(hotkey_ss58="5F...", netuid=1, amount_alpha=1.0, limit_price_rao=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("remove_stake_limit", {...}, wallet) ``` # remove-stake (/docs/tx/remove-stake) Swaps the alpha position back to TAO at the current pool price and credits it to the signing coldkey's free balance. Pass `all` to exit the entire position on that hotkey and subnet (the build fails if nothing is staked there). Like staking, the swap moves the pool, so large amounts incur slippage — use `remove_stake_limit` to bound the price. The hotkey and netuid must match where the stake is actually held, and the subnet must have subtoken trading enabled. The requested amount is capped to the stake currently available. A partial unstake must leave a remainder worth at least 0.002 TAO at the simulated pool price — exit the full position instead of leaving dust (`AmountTooLow`). | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------ | | `coldkey` | SubtensorModule | `SubtensorModule.remove_stake` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ----------------- | -------- | ----------------------------------------------------------------- | | `hotkey_ss58` | string | yes | Hotkey the stake is held on (the validator backing the position). | | `netuid` | integer | yes | Subnet the stake lives on (netuid 0 is the root network). | | `amount_alpha` | number \| `"all"` | yes | How much to unstake from this position, or `all`. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx remove-stake \ --hotkey \ --netuid \ --amount-alpha --dry-run btcli tx remove-stake \ --hotkey \ --netuid \ --amount-alpha -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RemoveStake(hotkey_ss58="5F...", netuid=1, amount_alpha=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("remove_stake", {...}, wallet) ``` # reset-axon (/docs/tx/reset-axon) Republishes the axon record as a placeholder (ip 0, port 1, protocol 4) so validators stop routing traffic to the old address; the storage entry is not removed. Signed by the hotkey. Use this when taking a miner offline or before moving it, then publish the new address with `serve_axon` when it is back up. | Signer | Pallet | Wraps | | -------- | --------------- | ---------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.serve_axon` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ------- | -------- | ---------------------------------------------- | | `netuid` | integer | yes | Subnet whose published axon endpoint to clear. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx reset-axon \ --netuid --dry-run btcli tx reset-axon \ --netuid -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ResetAxon(netuid=1) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("reset_axon", {...}, wallet) ``` # reveal-weights (/docs/tx/reveal-weights) Completes the old two-step commit-reveal flow: the uids, weights, salt, and version key must hash to exactly what was committed earlier. The reveal is valid on exactly one epoch — the commit's epoch plus the subnet's reveal period; revealing earlier fails, and once that epoch has passed the commit is expired and dropped. The chain hashes the quantized u16 values, so the inputs here must be the same values passed at commit time (`build()` re-normalizes floats: identical proportions produce identical u16s). Signed by the hotkey that made the commit. Only needed for legacy salt-based commits — the timelocked path used by `set_weights` and `commit_weights` is auto-revealed by the chain and never needs this call. | Signer | Pallet | Wraps | | -------- | --------------- | -------------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.reveal_weights` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ---------------- | -------- | --------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose miners the weights score. | | `uids` | array of integer | yes | Miner UIDs exactly as committed, as a list parallel to weights. | | `weights` | array of number | yes | Weights exactly as committed, as a list parallel to uids. | | `salt` | array of integer | yes | Salt used when the commit was made; must match to reveal. | | `version_key` | integer | no | Version key used when the commit was made. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx reveal-weights \ --netuid \ --uids \ --weights \ --salt --dry-run btcli tx reveal-weights \ --netuid \ --uids \ --weights \ --salt -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RevealWeights(netuid=1, uids=[...], weights=[...], salt=[...]) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("reveal_weights", {...}, wallet) ``` # root-register (/docs/tx/root-register) Joins the hotkey to the root network, the TAO staking pool (netuid 0 has no miners and no alpha; validators register here to receive root stake). Placement is stake-based rather than burn-based: root slots are limited, so joining a full root network evicts the member with the least stake, and a hotkey without enough stake behind it will not hold a seat. Root registrations are also capped per block (`max_registrations_per_block`) and per interval (three times `target_registrations_per_interval`); hitting either cap fails until the window passes. Use `burned_register` for ordinary subnets. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.root_register` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------------------------ | | `hotkey_ss58` | string | no | Hotkey to register on the root network; defaults to the wallet's hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx root-register --dry-run btcli tx root-register -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.RootRegister() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("root_register", {...}, wallet) ``` # serve-axon-tls (/docs/tx/serve-axon-tls) Same as `serve_axon` plus a compact neuron certificate stored on chain: one algorithm byte followed by up to 64 bytes of public key — not an X.509 TLS certificate blob (anything else fails to decode). The chain only publishes the key for peers to fetch; there is no chain-side TLS handshake, and running any TLS endpoint is up to the caller. Signed by the hotkey, which must be registered on the subnet. Use plain `serve_axon` when peers do not need a published key. | Signer | Pallet | Wraps | | -------- | --------------- | -------------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.serve_axon_tls` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `netuid` | integer | yes | Subnet on which to publish the endpoint. | | `ip` | string | yes | Public IPv4 or IPv6 address of the endpoint, in standard dotted or colon notation. | | `port` | integer | yes | TCP port the endpoint listens on. | | `certificate` | string | yes | Neuron certificate as 0x-prefixed hex: 1 algorithm byte followed by up to 64 bytes of public key. Not an X.509 certificate; other formats fail to decode on chain. | | `protocol` | integer | no | Application protocol tag stored alongside the endpoint; its meaning is subnet-defined. | | `version` | integer | no | Version number of the serving neuron's software, stored with the endpoint. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx serve-axon-tls \ --netuid \ --ip \ --port \ --certificate --dry-run btcli tx serve-axon-tls \ --netuid \ --ip \ --port \ --certificate -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ServeAxonTls(netuid=1, ip="...", port=0, certificate="...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("serve_axon_tls", {...}, wallet) ``` # serve-axon (/docs/tx/serve-axon) Writes the connection info to chain storage so validators on the subnet know where to reach this miner or validator; it does not start any server — running the actual axon service is up to the caller. Signed by the hotkey, which must be registered on the subnet, and subject to the chain's serving rate limit (re-publishing too soon fails). Use `serve_axon_tls` if peers should verify a TLS certificate, and `reset_axon` to stop advertising the endpoint. | Signer | Pallet | Wraps | | -------- | --------------- | ---------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.serve_axon` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ---------- | ------- | -------- | -------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet on which to publish the endpoint. | | `ip` | string | yes | Public IPv4 or IPv6 address of the endpoint, in standard dotted or colon notation. | | `port` | integer | yes | TCP port the endpoint listens on. | | `protocol` | integer | no | Application protocol tag stored alongside the endpoint; its meaning is subnet-defined. | | `version` | integer | no | Version number of the serving neuron's software, stored with the endpoint. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx serve-axon \ --netuid \ --ip \ --port --dry-run btcli tx serve-axon \ --netuid \ --ip \ --port -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ServeAxon(netuid=1, ip="...", port=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("serve_axon", {...}, wallet) ``` # serve-prometheus (/docs/tx/serve-prometheus) Advertises where this neuron's prometheus metrics can be scraped, separate from the axon endpoint used for inter-neuron traffic. Signed by the hotkey, which must be registered on the subnet, and subject to the same serving rate limit as `serve_axon`. Like the axon calls, this only writes chain data — running the metrics server is up to the caller. | Signer | Pallet | Wraps | | -------- | --------------- | ---------------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.serve_prometheus` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ------- | -------- | ---------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet on which to publish the endpoint. | | `ip` | string | yes | Public IPv4 or IPv6 address of the endpoint, in standard dotted or colon notation. | | `port` | integer | yes | TCP port the endpoint listens on. | | `version` | integer | no | Version number of the serving neuron's software, stored with the endpoint. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx serve-prometheus \ --netuid \ --ip \ --port --dry-run btcli tx serve-prometheus \ --netuid \ --ip \ --port -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.ServePrometheus(netuid=1, ip="...", port=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("serve_prometheus", {...}, wallet) ``` # set-auto-stake (/docs/tx/set-auto-stake) Sets the coldkey's autostake destination for the subnet: all future rewards earned there are automatically staked to the chosen hotkey (defaulting to the wallet's own hotkey) instead of accumulating unstaked. A configuration change only — it moves no funds by itself and applies just to that subnet. The hotkey must be registered on the subnet (`HotKeyNotRegisteredInSubNet`), and setting the hotkey that is already the destination fails (`SameAutoStakeHotkeyAlreadySet`). Call it again with a different hotkey to redirect; read the current setting back with the `auto_stake` read. | Signer | Pallet | Wraps | | --------- | --------------- | ----------------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.set_coldkey_auto_stake_hotkey` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ---------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose future rewards are auto-staked. | | `hotkey_ss58` | string | no | Hotkey the rewards are staked to. Defaults to the wallet's own hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-auto-stake \ --netuid --dry-run btcli tx set-auto-stake \ --netuid -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetAutoStake(netuid=1) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_auto_stake", {...}, wallet) ``` # set-childkey-take (/docs/tx/set-childkey-take) The childkey take is the fraction of emissions a child hotkey keeps from the stake weight its parents delegate to it, before passing the remainder through. It is set per subnet, unlike the global delegate take. Signed by the coldkey that owns the child hotkey. The chain enforces both its minimum and maximum childkey take bounds, and rate-limits only increases — decreases apply immediately, so lowering the take is always available. | Signer | Pallet | Wraps | | --------- | --------------- | ----------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.set_childkey_take` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | `netuid` | integer | yes | Subnet the childkey take applies to. | | `take` | integer | yes | New take as a u16 proportion (fraction of 65535, e.g. 5898 is about 9 percent). The chain enforces its configured take bounds. | | `hotkey_ss58` | string | no | Hotkey the operation applies to. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-childkey-take \ --netuid \ --take --dry-run btcli tx set-childkey-take \ --netuid \ --take -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetChildkeyTake(netuid=1, take=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_childkey_take", {...}, wallet) ``` # set-children (/docs/tx/set-children) Childkeys let a parent hotkey delegate a fraction of its stake weight to other hotkeys on one subnet — commonly used to split validation duties or point stake at a separate validating key without moving the stake itself. Each entry in `children` is a pair of proportion and hotkey ss58, where proportion is a u64 share of u64::MAX; the proportions must not sum past the whole. The call replaces the full child set, so pass an empty list to revoke all children. Signed by the coldkey that owns the parent hotkey, and subject to the chain's childkey rate limit. Chain guards: not allowed on the root subnet; at most 5 children per hotkey per subnet; duplicate children are rejected; a hotkey that is a parent of this hotkey cannot be added as its child (relations stay bipartite); and the parent hotkey needs a minimum own stake (StakeThreshold) unless it is the subnet-owner hotkey. Changes take effect after a chain-defined cooldown, except on subnets whose subtoken is not yet enabled, where they apply immediately. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------ | | `coldkey` | SubtensorModule | `SubtensorModule.set_children` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `netuid` | integer | yes | Subnet on which the child relationships apply. | | `children` | array | yes | JSON list of proportion-and-hotkey pairs; each proportion is a u64 share of u64::MAX of the parent's stake weight delegated to that child. An empty list revokes all children. | | `hotkey_ss58` | string | no | Hotkey the operation applies to. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-children \ --netuid \ --children --dry-run btcli tx set-children \ --netuid \ --children -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetChildren(netuid=1, children=[...]) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_children", {...}, wallet) ``` # set-crowdloan-max-contribution (/docs/tx/set-crowdloan-max-contribution) Caps how much any single contributor can put in, useful to keep a raise broadly distributed. Omitting the amount clears the limit. Only the creator may change it, and only while the loan has not been finalized. | Signer | Pallet | Wraps | | --------- | --------- | -------------------------------- | | `coldkey` | Crowdloan | `Crowdloan.set_max_contribution` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------------------- | ----------------- | -------- | ------------------------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | | `new_max_contribution_tao` | number \| `"all"` | no | Largest total any single contributor may put in. Omit to clear the limit. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-crowdloan-max-contribution \ --crowdloan-id --dry-run btcli tx set-crowdloan-max-contribution \ --crowdloan-id -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetCrowdloanMaxContribution(crowdloan_id=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_crowdloan_max_contribution", {...}, wallet) ``` # set-hyperparameter (/docs/tx/set-hyperparameter) Dispatches the matching AdminUtils `sudo_set_*` call for the named parameter. The signer must be the subnet's owner coldkey; root-only parameters are not available here and must go through the raw-call escape hatch. Changes take effect on chain immediately and shape subnet economics and consensus (registration costs, weight rules, immunity, transfers), so verify the raw value before sending — `value` accepts either the raw on-chain integer or a human form that is converted for you. Some parameters are rate-limited by the chain, so a quick follow-up change can fail. Read current values back with the `subnet_hyperparameters` read. | Signer | Pallet | Wraps | | --------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `coldkey` | AdminUtils | `AdminUtils.sudo_set_immunity_period`, `AdminUtils.sudo_set_min_allowed_weights`, `AdminUtils.sudo_set_weights_version_key`, `AdminUtils.sudo_set_activity_cutoff`, `AdminUtils.sudo_set_min_burn`, `AdminUtils.sudo_set_bonds_moving_average`, `AdminUtils.sudo_set_serving_rate_limit`, `AdminUtils.sudo_set_commit_reveal_weights_interval`, `AdminUtils.sudo_set_max_allowed_uids`, `AdminUtils.sudo_set_burn_increase_mult`, `AdminUtils.sudo_set_burn_half_life`, `AdminUtils.sudo_set_commit_reveal_weights_enabled`, `AdminUtils.sudo_set_liquid_alpha_enabled`, `AdminUtils.sudo_set_network_pow_registration_allowed`, `AdminUtils.sudo_set_yuma3_enabled`, `AdminUtils.sudo_set_bonds_reset_enabled`, `AdminUtils.sudo_set_toggle_transfer`, `AdminUtils.sudo_set_owner_cut_enabled`, `AdminUtils.sudo_set_owner_cut_auto_lock_enabled` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | --------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet to configure; the signer must be its owner. | | `name` | string | yes | Hyperparameter to set. One of: activity\_cutoff, bonds\_moving\_avg, bonds\_reset\_enabled, burn\_half\_life, burn\_increase\_mult, commit\_reveal\_period, commit\_reveal\_weights\_enabled, immunity\_period, liquid\_alpha\_enabled, max\_allowed\_uids, min\_allowed\_weights, min\_burn, network\_pow\_registration\_allowed, owner\_cut\_auto\_lock\_enabled, owner\_cut\_enabled, serving\_rate\_limit, transfers\_enabled, weights\_version, yuma3\_enabled. | | `value` | integer \| number \| string | yes | New value. Give the raw on-chain integer, or the human form as a float or a string with a decimal point (a 0..1 fraction for normalized parameters, a TAO amount for rao parameters). Boolean parameters take true/false or 0/1. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-hyperparameter \ --netuid \ --name \ --value --dry-run btcli tx set-hyperparameter \ --netuid \ --name \ --value -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetHyperparameter(netuid=1, name="...", value="...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_hyperparameter", {...}, wallet) ``` # set-identity (/docs/tx/set-identity) Stores public, human-readable metadata against the signing coldkey so explorers, wallets, and delegators can recognize it — useful for validator operators and subnet owners who want a public face. The signing coldkey must own at least one hotkey registered on some subnet, else the call fails with `HotKeyNotRegisteredInNetwork`. Everything submitted is public and permanent history on chain, so include nothing sensitive. Calling again overwrites the whole identity (empty fields clear their previous values); the chain enforces length limits on each field. Purely cosmetic: no effect on balances, stake, or permissions. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------ | | `coldkey` | SubtensorModule | `SubtensorModule.set_identity` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------------- | | `name` | string | yes | Display name shown for this coldkey. | | `url` | string | no | Website associated with this identity. | | `github_repo` | string | no | GitHub repository URL. | | `image` | string | no | Avatar or logo image URL. | | `discord` | string | no | Discord handle or server invite. | | `description` | string | no | Short free-text description of who this key belongs to. | | `additional` | string | no | Any extra free-text information to publish. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-identity \ --name --dry-run btcli tx set-identity \ --name -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetIdentity(name="...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_identity", {...}, wallet) ``` # set-mechanism-count (/docs/tx/set-mechanism-count) Mechanisms are independent incentive sub-markets within one subnet, each running its own weights and consensus; this owner-only call sets how many the subnet runs. The count must be greater than zero, and the chain caps how many mechanisms a subnet may have. Increasing the count opens new mechanisms; decreasing it removes the highest-numbered ones and the miner state in them. Any change to the count clears the emission split back to an even division — reapply `set_mechanism_emission_split` afterwards if you want an uneven split. Rate-limited and blocked during the end-of-epoch admin freeze window. | Signer | Pallet | Wraps | | --------- | ---------- | ------------------------------------- | | `coldkey` | AdminUtils | `AdminUtils.sudo_set_mechanism_count` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ------- | -------- | -------------------------------------------------- | | `netuid` | integer | yes | Subnet to configure; the signer must be its owner. | | `mechanism_count` | integer | yes | Number of mechanisms the subnet should run. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-mechanism-count \ --netuid \ --mechanism-count --dry-run btcli tx set-mechanism-count \ --netuid \ --mechanism-count -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetMechanismCount(netuid=1, mechanism_count=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_mechanism_count", {...}, wallet) ``` # set-mechanism-emission-split (/docs/tx/set-mechanism-emission-split) Owner-only: divides the subnet's emission between its mechanisms. The list gives one u16 weight per mechanism, in order, and the entries must sum to exactly 65,535; each mechanism receives that fraction of emission. The list may be at most as long as the subnet's current mechanism count (see `set_mechanism_count`) — a shorter list leaves the trailing mechanisms with zero. Changing the split reallocates future emission only — nothing already emitted moves. | Signer | Pallet | Wraps | | --------- | ---------- | ---------------------------------------------- | | `coldkey` | AdminUtils | `AdminUtils.sudo_set_mechanism_emission_split` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ---------------- | -------- | ---------------------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet to configure; the signer must be its owner. | | `split` | array of integer | yes | u16 emission weights in mechanism order, summing to exactly 65,535; at most one entry per mechanism. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-mechanism-emission-split \ --netuid \ --split --dry-run btcli tx set-mechanism-emission-split \ --netuid \ --split -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetMechanismEmissionSplit(netuid=1, split=[...]) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_mechanism_emission_split", {...}, wallet) ``` # set-perpetual-lock (/docs/tx/set-perpetual-lock) Switches how the signing coldkey's stake lock on the subnet behaves over time: perpetual mode keeps the lock (and its conviction) in force indefinitely, while decaying mode lets it wind down over time so the stake eventually becomes liquid again. A per-coldkey, per-subnet setting that moves no funds by itself — it changes the behavior of locks created with `lock_stake`. Enabling perpetual mode means the locked stake stays illiquid until you switch back to decaying and the lock runs off. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------------ | | `coldkey` | SubtensorModule | `SubtensorModule.set_perpetual_lock` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ------- | -------- | --------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose lock mode is changed. | | `enabled` | boolean | yes | True for perpetual mode (lock never decays), false for decaying mode. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-perpetual-lock \ --netuid \ --enabled --dry-run btcli tx set-perpetual-lock \ --netuid \ --enabled -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetPerpetualLock(netuid=1, enabled=True) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_perpetual_lock", {...}, wallet) ``` # set-root-claim-type (/docs/tx/set-root-claim-type) Controls what happens to root dividends when they are claimed (see `claim_root`): `Swap` converts all alpha emission to TAO (the chain default), `Keep` keeps everything as subnet alpha, and `KeepSubnets` keeps alpha on the listed `subnets` while swapping the rest. The setting is per-coldkey and persists until changed again; it does not move anything already claimed. Read it back with the `root_claim_type` read. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.set_root_claim_type` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------ | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `claim_type` | string | no | How root alpha emission is claimed. One of: Swap, Keep, KeepSubnets. Swap converts all alpha emission to TAO, Keep keeps everything as alpha, KeepSubnets keeps alpha only on the subnets given via --subnets and swaps the rest. | | `subnets` | array | no | Netuids to keep alpha on; required for KeepSubnets, invalid otherwise. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-root-claim-type --dry-run btcli tx set-root-claim-type -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetRootClaimType() async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_root_claim_type", {...}, wallet) ``` # set-subnet-identity (/docs/tx/set-subnet-identity) Stores the subnet's public profile — name, links, contact, logo — so explorers and participants can identify it. Owner-only: the signing coldkey must own the subnet. Everything submitted is public and permanent history on chain. Calling again overwrites the whole record (empty fields clear their previous values); the chain enforces length limits on each field. Purely cosmetic — for the token ticker use `update_symbol`, and for economics use `set_hyperparameter`. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.set_subnet_identity` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ---------------- | ------- | -------- | --------------------------------------------------------- | | `netuid` | integer | yes | Subnet the identity is for; the signer must be its owner. | | `subnet_name` | string | yes | Display name shown for the subnet. | | `github_repo` | string | no | GitHub repository URL for the subnet's code. | | `subnet_contact` | string | no | Contact address for the subnet operators. | | `subnet_url` | string | no | Website for the subnet. | | `discord` | string | no | Discord handle or server invite. | | `description` | string | no | Short free-text description of what the subnet does. | | `logo_url` | string | no | Logo image URL for the subnet. | | `additional` | string | no | Any extra free-text information to publish. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-subnet-identity \ --netuid \ --subnet-name --dry-run btcli tx set-subnet-identity \ --netuid \ --subnet-name -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetSubnetIdentity(netuid=1, subnet_name="...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_subnet_identity", {...}, wallet) ``` # set-take (/docs/tx/set-take) The delegate take is the fraction of staking emissions a delegate hotkey keeps before distributing the rest to its nominators. This is sugar over the chain's directional `increase_take` / `decrease_take`: it reads the current take and dispatches whichever call moves it to `take`, so you do not need to know the current value. Signed by the coldkey that owns the hotkey. If the move is upward it inherits the increase path's constraints (take maximum and rate limit on increases). | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.increase_take`, `SubtensorModule.decrease_take` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | `take` | integer | yes | New take as a u16 proportion (fraction of 65535, e.g. 5898 is about 9 percent). The chain enforces its configured take bounds. | | `hotkey_ss58` | string | no | Hotkey the operation applies to. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-take \ --take --dry-run btcli tx set-take \ --take -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetTake(take=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_take", {...}, wallet) ``` # set-weights (/docs/tx/set-weights) The one entry point validators need for scoring miners: it conforms the weights to the subnet's hyperparameters (max-weight clip, u16 quantization, minimum weight count) and submits via whichever path the subnet runs — a plain `set_weights` when commit-reveal is off, or a timelock-encrypted commit (auto-revealed by the chain at the drand reveal round) when it is on. Signed by the hotkey, which must be registered on the subnet. Before signing it preflights registration and the rate limit, so those failures are caught fast with the same error the chain would return; the rate-limit error says how many blocks to wait. The chain additionally enforces checks that are not preflighted: the hotkey must hold the minimum stake to set weights, must hold a validator permit to set non-self weights (the subnet owner is exempt), and `version_key` must not be older than the subnet's required version. Prefer this over `commit_weights`/`reveal_weights` unless you specifically need to force one path. | Signer | Pallet | Wraps | | -------- | --------------- | ---------------------------------------------------------------------------------------------- | | `hotkey` | SubtensorModule | `SubtensorModule.set_mechanism_weights`, `SubtensorModule.commit_timelocked_mechanism_weights` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose miners the weights score. | | `uids` | array of integer | no | Miner UIDs being weighted, as a list parallel to weights. Omit when weights is given as a uid-to-weight mapping. | | `weights` | array of number | no | Relative weight per miner: either a JSON object mapping uid to weight, or a list parallel to uids. Values are relative, not absolute; they are clipped to the subnet's max-weight limit, normalized, and quantized before submission. | | `mechid` | integer | no | Mechanism index within the subnet. 0 is the default (and for most subnets the only) mechanism. | | `version_key` | integer | no | Weights version key checked against the subnet's required version. Leave 0 unless the subnet owner requires a specific value. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx set-weights \ --netuid --dry-run btcli tx set-weights \ --netuid -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SetWeights(netuid=1) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("set_weights", {...}, wallet) ``` # stake-burn (/docs/tx/stake-burn) Spends TAO from the signing coldkey to buy the subnet's alpha and burn it, reducing alpha supply (a buyback-and-burn) rather than adding to the signer's stake. The TAO is spent permanently — nothing lands in your stake, so this is not an investment call; use a regular add-stake intent to acquire a position. Fails on the root subnet (`CannotBurnOrRecycleOnRootSubnet`). The chain accepts an optional limit (omitted = market order), but this intent always requires `limit_price` and executes all-or-nothing: the swap fails instead of partially filling at a worse rate. Counts against a configured spend cap. | Signer | Pallet | Wraps | | --------- | --------------- | -------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.add_stake_burn` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ----------------- | -------- | -------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose alpha is bought and burned. | | `amount_tao` | number \| `"all"` | yes | Spent from the coldkey to buy alpha that is then burned. | | `limit_price` | integer | yes | Worst acceptable price in rao per alpha; the call fails rather than filling beyond it. | | `hotkey_ss58` | string | no | Hotkey the burn is routed through; defaults to the wallet's hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx stake-burn \ --netuid \ --amount-tao \ --limit-price --dry-run btcli tx stake-burn \ --netuid \ --amount-tao \ --limit-price -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.StakeBurn(netuid=1, amount_tao=1.0, limit_price=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("stake_burn", {...}, wallet) ``` # start-call (/docs/tx/start-call) Flips a freshly registered subnet from inactive to active: the subnet token becomes tradable and alpha emission into the subnet's epochs begins. It does not enable the subnet's share of TAO emission — that additionally requires the root-gated emission-enabled flag, which only root can set. Owner-only, callable once per subnet, and only after the chain's minimum delay since the subnet was registered — calling too early fails. Until this is called the subnet earns nothing, so run it as soon as the delay allows. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.start_call` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ------- | -------- | ------------------------------------------------- | | `netuid` | integer | yes | Subnet to activate; the signer must be its owner. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx start-call \ --netuid --dry-run btcli tx start-call \ --netuid -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.StartCall(netuid=1) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("start_call", {...}, wallet) ``` # swap-coldkey-announced (/docs/tx/swap-coldkey-announced) Step two of the two-step migration: reveals the new coldkey and moves everything the signing coldkey owns — balance, stake, and subnet ownership — to it. Irreversible once included. The revealed key must hash to exactly what `announce_coldkey_swap` committed to, and the call fails if the announcement delay has not elapsed, no announcement exists, or the swap is frozen by a dispute. After it succeeds, the old coldkey is empty; all future operations sign with the new coldkey. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.swap_coldkey_announced` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------ | ------ | -------- | ------------------------------------------------------------------------------- | | `new_coldkey_ss58` | string | yes | Coldkey receiving everything; must match the previously announced hash exactly. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx swap-coldkey-announced \ --new-coldkey --dry-run btcli tx swap-coldkey-announced \ --new-coldkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SwapColdkeyAnnounced(new_coldkey_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("swap_coldkey_announced", {...}, wallet) ``` # swap-hotkey (/docs/tx/swap-hotkey) Re-keys the neuron identity: the old hotkey's registrations, stake, and history move to `new_hotkey_ss58`, either everywhere (`netuid` omitted) or on a single subnet. The all-subnets swap recycles 0.1 TAO from the coldkey; the per-subnet swap recycles 0.001 TAO. Both respect a 7,200-block (one day) per-(subnet, coldkey) cooldown — the all-subnets swap checks and records it on every subnet the old hotkey participates in. The old hotkey stops earning immediately, so update running miners/validators to sign with the new key at the same time. The new hotkey must not already be registered where the swap applies, so plan the change rather than iterating. This wraps the legacy `swap_hotkey` extrinsic, deprecated on chain in favor of `swap_hotkey_v2`; behavior is identical to `swap_hotkey_v2` with `keep_stake=false` (stake moves to the new hotkey). This rotates a leaked hotkey without touching the coldkey; a compromised coldkey needs a coldkey swap instead. | Signer | Pallet | Wraps | | --------- | --------------- | ----------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.swap_hotkey` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ----------------- | ------- | -------- | ---------------------------------------------------------------------------- | | `new_hotkey_ss58` | string | yes | Replacement hotkey that takes over the old hotkey's registrations and stake. | | `hotkey_ss58` | string | no | Hotkey being replaced; defaults to the wallet's hotkey. | | `netuid` | integer | no | Limit the swap to this subnet; omit to swap across all subnets. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx swap-hotkey \ --new-hotkey --dry-run btcli tx swap-hotkey \ --new-hotkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SwapHotkey(new_hotkey_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("swap_hotkey", {...}, wallet) ``` # swap-stake (/docs/tx/swap-stake) Moves part of a position from the origin subnet to the destination subnet while staying on the same hotkey: the alpha is swapped to TAO in the origin pool and then to alpha in the destination pool, so both legs can incur slippage. The two netuids must differ (`SameNetuid`). Use `move_stake` when the hotkey should change too, and `remove_stake` plus `add_stake` only if you want to control each leg separately. | Signer | Pallet | Wraps | | --------- | --------------- | ---------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.swap_stake` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `hotkey_ss58` | string | yes | Hotkey the stake is held on (the validator backing the position). | | `origin_netuid` | integer | yes | Subnet the stake currently sits on. | | `dest_netuid` | integer | yes | Subnet the stake ends up on. When it differs from the origin, the position is swapped through both subnet pools, which can incur slippage on each leg. | | `amount_alpha` | number \| `"all"` | yes | How much of the origin position to swap across (an explicit amount; `all` is not accepted). | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx swap-stake \ --hotkey \ --origin-netuid \ --dest-netuid \ --amount-alpha --dry-run btcli tx swap-stake \ --hotkey \ --origin-netuid \ --dest-netuid \ --amount-alpha -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.SwapStake(hotkey_ss58="5F...", origin_netuid=0, dest_netuid=0, amount_alpha=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("swap_stake", {...}, wallet) ``` # terminate-lease (/docs/tx/terminate-lease) Ends the lease and transfers full subnet ownership to the beneficiary: contributor dividends stop and the subnet becomes an ordinary owned subnet. Only the lease's beneficiary can call it, and only after the lease's end block has passed — earlier attempts fail, and perpetual leases (no end block) can never be terminated this way. Check the lease's end block with the `lease` read before calling. | Signer | Pallet | Wraps | | --------- | --------------- | --------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.terminate_lease` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------- | -------- | ------------------------------------------------------------------------------------------ | | `lease_id` | integer | yes | Lease to terminate (see the leases read). | | `hotkey_ss58` | string | no | Beneficiary hotkey recorded as the subnet's owner hotkey; defaults to the wallet's hotkey. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx terminate-lease \ --lease-id --dry-run btcli tx terminate-lease \ --lease-id -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.TerminateLease(lease_id=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("terminate_lease", {...}, wallet) ``` # transfer-all (/docs/tx/transfer-all) Drains the signing coldkey's transferable balance to the destination in one irreversible call — equivalent to `transfer` with `all` as the amount. Staked or otherwise reserved funds are not included; unstake first to move those. A spend-cap policy treats this as an unbounded spend and blocks it until the cap is raised. With `keep_alive` (the default) the existential deposit stays behind; disable it to empty and reap the account. | Signer | Pallet | Wraps | | --------- | -------- | ----------------------- | | `coldkey` | Balances | `Balances.transfer_all` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------ | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `dest_ss58` | string | yes | Account the funds are sent to. | | `keep_alive` | boolean | no | Refuse to drop the sender below the existential deposit. Disable it only when you intend to empty the account, which lets the chain reap it. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx transfer-all \ --dest --dry-run btcli tx transfer-all \ --dest -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.TransferAll(dest_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("transfer_all", {...}, wallet) ``` # transfer-stake (/docs/tx/transfer-stake) Hands the position itself to the destination coldkey: after this call that coldkey — not you — controls and can unstake those funds, so this is a transfer of value and is irreversible. Double-check the destination address. The stake stays on the same hotkey but can land on a different subnet, swapping through both pools (with slippage) when the netuids differ. Fails with `TransferDisallowed` when the subnet owner has disabled stake transfers on the origin or destination subnet. A spend-cap policy treats this as an unbounded spend and blocks it until the cap is raised. Use `move_stake` to re-delegate without changing owners. | Signer | Pallet | Wraps | | --------- | --------------- | -------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.transfer_stake` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------------- | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `dest_coldkey_ss58` | string | yes | Coldkey that becomes the new owner of the stake. | | `hotkey_ss58` | string | yes | Hotkey the stake is held on (the validator backing the position). | | `origin_netuid` | integer | yes | Subnet the stake currently sits on. | | `dest_netuid` | integer | yes | Subnet the stake ends up on. When it differs from the origin, the position is swapped through both subnet pools, which can incur slippage on each leg. | | `amount_alpha` | number \| `"all"` | yes | How much of the position to hand over (an explicit amount; `all` is not accepted). | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx transfer-stake \ --dest-coldkey \ --hotkey \ --origin-netuid \ --dest-netuid \ --amount-alpha --dry-run btcli tx transfer-stake \ --dest-coldkey \ --hotkey \ --origin-netuid \ --dest-netuid \ --amount-alpha -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.TransferStake(dest_coldkey_ss58="5F...", hotkey_ss58="5F...", origin_netuid=0, dest_netuid=0, amount_alpha=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("transfer_stake", {...}, wallet) ``` # transfer (/docs/tx/transfer) Sends free balance from the signing coldkey to the destination. On-chain transfers are irreversible — funds sent to a wrong address cannot be recovered, so double-check the destination. A transaction fee is deducted from the sender on top of the amount, and the call fails if the free balance cannot cover both. Pass `all` to sweep the entire transferable balance. With `keep_alive` (the default) the transfer refuses to drop the sender below the existential deposit; disable it only when intentionally emptying the account. | Signer | Pallet | Wraps | | --------- | -------- | ---------------------------------------------------------------------------------------- | | `coldkey` | Balances | `Balances.transfer_keep_alive`, `Balances.transfer_allow_death`, `Balances.transfer_all` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------ | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `dest_ss58` | string | yes | Account the funds are sent to. | | `amount_tao` | number \| `"all"` | yes | How much to send. | | `keep_alive` | boolean | no | Refuse to drop the sender below the existential deposit. Disable it only when you intend to empty the account, which lets the chain reap it. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx transfer \ --dest \ --amount-tao --dry-run btcli tx transfer \ --dest \ --amount-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.Transfer(dest_ss58="5F...", amount_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("transfer", {...}, wallet) ``` # trim-subnet (/docs/tx/trim-subnet) Lowers the subnet's UID capacity and immediately deregisters the lowest-emission neurons above the new limit — those miners lose their slots and would have to re-register (paying the burn cost) to return. `max_n` must be between the chain's minimum allowed UIDs (64) and the subnet's current `max_allowed_uids`. Owner-immune and temporally immune UIDs are skipped, and the call fails if immune UIDs would exceed 80% of `max_n`. Surviving UIDs are renumbered consecutively from zero, so UID values change. Rate-limited to once per 216,000 blocks (30 days) and blocked during the end-of-epoch admin freeze window. Owner-only and disruptive to affected participants, so announce it before shrinking a live subnet. To simply cap future growth without evicting anyone, set the `max_allowed_uids` hyperparameter to a value at or above the current UID count instead. | Signer | Pallet | Wraps | | --------- | ---------- | ------------------------------------------ | | `coldkey` | AdminUtils | `AdminUtils.sudo_trim_to_max_allowed_uids` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ------- | -------- | --------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet to trim; the signer must be its owner. | | `max_n` | integer | yes | New maximum number of UIDs; lowest-emission neurons above this are removed. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx trim-subnet \ --netuid \ --max-n --dry-run btcli tx trim-subnet \ --netuid \ --max-n -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.TrimSubnet(netuid=1, max_n=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("trim_subnet", {...}, wallet) ``` # unstake-all-alpha (/docs/tx/unstake-all-alpha) Sells every alpha position the signing coldkey holds on this hotkey and restakes the proceeds as TAO on the root network (netuid 0), instead of releasing them to the free balance. Subnets where subtoken trading is disabled or where the position fails validation are silently skipped. Use it to consolidate onto root while keeping funds staked; use `unstake_all` to exit to free balance instead. Each pool swap happens at the current price with no limit protection, so large positions can incur slippage. | Signer | Pallet | Wraps | | --------- | --------------- | ----------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.unstake_all_alpha` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------ | | `hotkey_ss58` | string | yes | Hotkey whose alpha stake is moved to root. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx unstake-all-alpha \ --hotkey --dry-run btcli tx unstake-all-alpha \ --hotkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.UnstakeAllAlpha(hotkey_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("unstake_all_alpha", {...}, wallet) ``` # unstake-all (/docs/tx/unstake-all) Sweeps the signing coldkey's entire stake held on this hotkey across every subnet (root included) back to TAO in the coldkey's free balance. Subnets where subtoken trading is disabled or where the position fails validation (e.g. dust below the chain minimum) are silently skipped, so the call can succeed while leaving some positions untouched. Alpha positions are sold at each pool's current price with no limit protection, so large positions can incur significant slippage. Use `remove_stake` to exit a single subnet, or `unstake_all_alpha` to consolidate onto root while staying staked. | Signer | Pallet | Wraps | | --------- | --------------- | ----------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.unstake_all` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------- | | `hotkey_ss58` | string | yes | Hotkey whose entire stake is removed. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx unstake-all \ --hotkey --dry-run btcli tx unstake-all \ --hotkey -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.UnstakeAll(hotkey_ss58="5F...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("unstake_all", {...}, wallet) ``` # update-crowdloan-cap (/docs/tx/update-crowdloan-cap) Raises or lowers the total the crowdloan can collect. Only the creator may change it, and only while the loan has not been finalized. Reaching the (new) cap is what allows the creator to finalize. | Signer | Pallet | Wraps | | --------- | --------- | ---------------------- | | `coldkey` | Crowdloan | `Crowdloan.update_cap` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ----------------- | -------- | ---------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | | `new_cap_tao` | number \| `"all"` | yes | New maximum total the crowdloan can raise. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx update-crowdloan-cap \ --crowdloan-id \ --new-cap-tao --dry-run btcli tx update-crowdloan-cap \ --crowdloan-id \ --new-cap-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.UpdateCrowdloanCap(crowdloan_id=0, new_cap_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("update_crowdloan_cap", {...}, wallet) ``` # update-crowdloan-end (/docs/tx/update-crowdloan-end) Extends or shortens the contribution window. The new end is re-validated against the current block: it must be in the future and fall within the chain's minimum/maximum crowdloan duration (roughly 7 to 60 days) measured from now. Only the creator may change it, and only while the loan has not been finalized. | Signer | Pallet | Wraps | | --------- | --------- | ---------------------- | | `coldkey` | Crowdloan | `Crowdloan.update_end` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | | `new_end` | integer | yes | New block number at which the crowdloan stops accepting contributions. Must be in the future and roughly 7-60 days from the current block. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx update-crowdloan-end \ --crowdloan-id \ --new-end --dry-run btcli tx update-crowdloan-end \ --crowdloan-id \ --new-end -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.UpdateCrowdloanEnd(crowdloan_id=0, new_end=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("update_crowdloan_end", {...}, wallet) ``` # update-crowdloan-min-contribution (/docs/tx/update-crowdloan-min-contribution) Changes the smallest contribution the crowdloan will accept from that point on; contributions already made are unaffected. Only the creator may change it, and only while the loan has not been finalized. | Signer | Pallet | Wraps | | --------- | --------- | ----------------------------------- | | `coldkey` | Crowdloan | `Crowdloan.update_min_contribution` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------------------- | ----------------- | -------- | ---------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | | `new_min_contribution_tao` | number \| `"all"` | yes | New smallest contribution the crowdloan will accept. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx update-crowdloan-min-contribution \ --crowdloan-id \ --new-min-contribution-tao --dry-run btcli tx update-crowdloan-min-contribution \ --crowdloan-id \ --new-min-contribution-tao -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.UpdateCrowdloanMinContribution(crowdloan_id=0, new_min_contribution_tao=1.0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("update_crowdloan_min_contribution", {...}, wallet) ``` # update-symbol (/docs/tx/update-symbol) Cosmetic call for the subnet owner (or root): changes the short ticker shown for the subnet's alpha token in wallets, explorers, and CLIs. Symbols are not arbitrary strings — the chain keeps a fixed catalog of roughly 439 predefined symbols, and anything outside it is rejected with `SymbolDoesNotExist`. A symbol already taken by another subnet is rejected with `SymbolAlreadyInUse`. No economic effect — balances, stake, and emissions are untouched. | Signer | Pallet | Wraps | | --------- | --------------- | ------------------------------- | | `coldkey` | SubtensorModule | `SubtensorModule.update_symbol` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | --------- | ------- | -------- | ------------------------------------------------------------------------------------------------- | | `netuid` | integer | yes | Subnet whose symbol to change; the signer must be its owner. | | `symbol` | string | yes | New token symbol; must be one of the chain's predefined symbols and not in use by another subnet. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx update-symbol \ --netuid \ --symbol --dry-run btcli tx update-symbol \ --netuid \ --symbol -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.UpdateSymbol(netuid=1, symbol="...") async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("update_symbol", {...}, wallet) ``` # withdraw-crowdloan (/docs/tx/withdraw-crowdloan) Contributors can pull their contribution back out of any crowdloan that has not finalized, including while it is still raising. The creator may only withdraw the part of their contribution above the initial deposit; the deposit itself stays locked until `dissolve_crowdloan` returns it. Fails once the crowdloan has finalized or if the signer has nothing left to withdraw. | Signer | Pallet | Wraps | | --------- | --------- | -------------------- | | `coldkey` | Crowdloan | `Crowdloan.withdraw` | ## Parameters [#parameters] | Parameter | Type | Required | Description | | -------------- | ------- | -------- | ---------------------------------------------------------- | | `crowdloan_id` | integer | yes | Identifier of the crowdloan, assigned when it was created. | Address parameters (`--hotkey`, `--coldkey`, `--dest`, ...) accept a raw ss58 address, an address-book or proxy-book name, or a local wallet/hotkey name. ## CLI [#cli] Preview with `--dry-run` (shows fee, effects, and policy result without submitting), then submit: ```bash btcli tx withdraw-crowdloan \ --crowdloan-id --dry-run btcli tx withdraw-crowdloan \ --crowdloan-id -w my_coldkey ``` ## Python [#python] ```python import bittensor as sub from bittensor.wallet import Wallet wallet = Wallet(name="my_coldkey", hotkey="my_hotkey") intent = sub.WithdrawCrowdloan(crowdloan_id=0) async with sub.Client("finney") as client: plan = await client.plan(intent, wallet) # fee, effects, policy — no submission result = await client.execute(intent, wallet) if not result.success: print(result.error.code, result.error.remediation) ``` Or build it by op name, as an agent would: ```python await client.execute_tool("withdraw_crowdloan", {...}, wallet) ```