The V438 Upgrade
Interfaces & Reliability · July 2026
Introduction
Spec 438 is a focused compatibility and correctness release after v437. It gives smart contracts a bounded way to inspect staking positions, lets Ledger-style signatures authorize limit orders, closes two small but important gaps in mechanism and epoch accounting, repairs testnet warp sync, and makes the release pipeline recover cleanly from a partial Python publication. There is no new token model or migration for ordinary wallets: v438 tightens the surfaces that applications, operators, and automation already use.
Staking data, directly from EVM
Contracts can now read a coldkey's non-zero alpha positions on one subnet through the staking precompile at 0x…0805. getStakeInfoForColdkeyAndNetuid accepts a coldkey, netuid, and up to 64 distinct candidate hotkeys, then returns only the candidates with stake. The caller supplies the search set deliberately: the precompile never walks the coldkey's unbounded historical hotkey index, so its work and gas remain proportional to the request.
IStaking.StakeInfo[] memory positions =
staking.getStakeInfoForColdkeyAndNetuid(coldkey, netuid, hotkeys);
uint256 baseMinimum = staking.getDefaultMinStake();The contract chooses at most 64 distinct candidate hotkeys. The precompile charges for the bounded reads and returns only non-zero (hotkey, stake) pairs.
The companion getDefaultMinStake()view exposes the runtime's base staking minimum. It is intentionally a primitive, not a quote: operation fees, subnet price conversion, and full-unstake rules can still change what a particular transaction accepts. Both selectors are added to the Solidity interface, JSON ABI, Rust precompile, and gas-accounting tests, so Solidity and SDK callers share one contract.
Limit orders meet hardware wallets
A signed limit order can now use either sr25519 or ed25519 and can cover either the raw SCALE-encoded order or the Ledger-compatible <Bytes>blake2_256(order)</Bytes> payload produced bysignRaw. The order ID is unchanged: it remains the blake2-256 hash of the versioned order, so cancellation, replay protection, partial fills, and relayer restrictions behave exactly as before. ECDSA remains rejected because its account recovery model does not map to the 32-byte signer identity used by the pallet.
Software keys can continue signing the order bytes directly. Ledger and compatible signers can sign the wrapped order hash; both paths verify against the same signer and derive the same on-chain order ID.
raw = SCALE(versioned_order)
order_id = blake2_256(raw)
wrapped = "<Bytes>" ++ order_id ++ "</Bytes>"
accepted = sr25519(raw | wrapped) or ed25519(raw | wrapped)
rejected = ecdsaMechanism emission splits are exact
A custom mechanism split must now contain one entry for every active mechanism, and the entries must still sum to 65,535. Previously, a shorter vector passed validation and was padded with zeroes, which could silently starve trailing mechanisms while rounding residue was assigned to mechanism zero. Owners that want an even split can continue clearing the custom value; owners that set one explicitly must describe the full mechanism set.
| Configuration | Before v438 | v438 |
|---|---|---|
| No custom split | Even distribution | Even distribution |
| Full vector, sum = 65,535 | Accepted | Accepted |
| Short vector | Zero-padded | Rejected |
| Wrong sum | Rejected | Rejected |
Epoch observability stays bounded
BlocksSinceLastStep is now capped at the subnet's tempo + 1. The scheduler's safety check uses that same subnet-specific tempo instead of a global maximum. Deferred epochs can still report the one-block-overdue state that triggers the fallback, but dashboards and clients no longer see a counter grow indefinitely during inconsistent or delayed state. The related epoch-status and next-epoch queries use the same model.
Testnet warp sync, repaired
Testnet nodes now start GRANDPA warp sync from two trusted, genesis-scoped authority checkpoints. The checkpoints are enabled only when the node sees the testnet genesis hash; mainnet and development chains retain their existing initial set-ID behavior. This repairs fast synchronization across historical authority-set changes without weakening checkpoint selection on any other network. The release also aligns the Polkadot SDK revision needed to verify those proofs and corrects dependency features that affected node log decoding.
Release train, recoverable
The mainnet watcher now tracks GitHub release completion and stable Python publication independently. A release can exist while a failed or partial PyPI upload is retried on the next watcher run; a provenance-bound completion marker is written only after every expected distribution is accepted. That removes the old failure mode where a successful GitHub release prevented the SDK publication from being retried.
Every merge to main also publishes the moving development image ghcr.io/raofoundation/subtensor:main. The deployed :latest tag remains tied to an executed mainnet release, while:devnet and :testnet continue following their network mirrors. Developers can therefore test merged code without mistaking it for the version running on mainnet. The complete promotion sequence is documented in the release process.
What to do
spec_version to move to 438, then update to the matching release. Testnet operators should update promptly to pick up the warp-sync checkpoints.stakingV2.abi and use a bounded, duplicate-free hotkey list when calling the new stake-position view.BlocksSinceLastStep as a bounded status counter, not an elapsed-time source beyond tempo + 1.Signers: after the release train proposes, use btcli upgrade sign --url <v438 release URL> -w <wallet>.