diff --git a/docs/guides/evm/precompile-design.mdx b/docs/guides/evm/precompile-design.mdx
new file mode 100644
index 0000000000..a2a233c57f
--- /dev/null
+++ b/docs/guides/evm/precompile-design.mdx
@@ -0,0 +1,458 @@
+---
+title: Precompile design and lifecycle
+description: How Bittensor precompiles preserve compatibility and how projects relay selected Substrate events to EVM contracts.
+---
+
+Bittensor precompiles are fixed-address EVM contracts implemented by the
+Subtensor runtime. They give Solidity callers typed access to chain operations
+and durable chain values without requiring contracts to understand Substrate
+storage.
+
+This page defines the compatibility model that new and existing precompiles
+should follow. It also describes the target lifecycle registry. The registry
+interface shown below is a design contract; it is not yet available on chain.
+
+
+ A deployed contract may be immutable. Treat every released precompile address,
+ function signature, and selector as a permanent public API.
+
+
+## Design goals
+
+The precompile layer is designed around five goals:
+
+1. **Contracts at rest keep working.** Runtime upgrades must not silently break
+ deployed contracts.
+2. **Interfaces evolve additively.** Existing selectors remain reserved, and
+ richer behavior is introduced through new function versions.
+3. **Deprecation is normally soft.** An old function continues to preserve its
+ original behavior whenever that behavior can still be represented safely.
+4. **Status is discoverable.** Solidity interfaces and a registry should tell
+ developers when a function is deprecated, replaced, or temporarily disabled.
+5. **The signed, deterministic Substrate API has typed parity.** Storage and
+ runtime API results have bounded typed views, and extrinsics that accept a
+ non-Root signed origin have typed operations.
+
+## Fixed addresses and function selectors
+
+A precompile has a fixed EVM address for a domain such as staking, metagraph
+data, or subnet operations. Solidity dispatches a call using the first four
+bytes of the Keccak-256 hash of its canonical function signature.
+
+For example:
+
+```solidity
+function getStake(uint16 netuid, uint16 uid) external view returns (uint64);
+```
+
+The selector belongs to that signature permanently once released. It must not
+later be assigned different semantics, even if the original function is
+hard-deprecated. Reusing a selector could make an old contract decode a
+successful but unrelated result.
+
+The source-of-truth Solidity interfaces and generated ABIs live in
+[`precompiles/src/solidity/`](https://github.com/RaoFoundation/subtensor/tree/main/precompiles/src/solidity).
+
+## Compatibility rules
+
+### Preserve released interfaces
+
+Do not remove or change a released function signature. A runtime implementation
+may change internally to follow a new storage layout or computation, but the
+observable result must retain the function's documented meaning.
+
+Changing any of these creates a different EVM interface:
+
+- function name or version suffix;
+- parameter types or order;
+- return types or order;
+- mutability where it affects permitted calls;
+- precompile address.
+
+### Version functions, not whole domains
+
+When a breaking return-type or parameter change is necessary, add a versioned
+function at the same precompile address:
+
+```solidity
+interface IMetagraph {
+ // Original selector remains supported.
+ function getStake(
+ uint16 netuid,
+ uint16 uid
+ ) external view returns (uint64);
+
+ // New selector exposes the richer representation.
+ function getStakeV2(
+ uint16 netuid,
+ uint16 uid
+ ) external view returns (StakeInfo memory);
+}
+```
+
+Use `functionName` for the initial version, followed by `functionNameV2`,
+`functionNameV3`, and so on. Both selectors route independently, so adding a
+version does not alter calls made by existing contracts.
+
+Creating a new domain address may still be appropriate when the functionality
+is genuinely a different precompile, but it should not be the default
+versioning mechanism.
+
+New Bittensor domain addresses are assigned sequentially from the next unused
+Bittensor address. The latest deployed domains are:
+
+| Address | Domain |
+|---|---|
+| `0x000000000000000000000000000000000000080f` | Scheduler |
+| `0x0000000000000000000000000000000000000810` | Drand |
+| `0x0000000000000000000000000000000000000811` | Timestamp |
+| `0x0000000000000000000000000000000000000812` | Runtime configuration |
+| `0x0000000000000000000000000000000000000813` | Precompile registry |
+
+Documenting an address reservation does not make a proposed precompile
+callable. When an implementation is added, routing and tests must lock the
+address and every implemented selector before release.
+
+### Keep old semantics when possible
+
+Suppose `getStake` originally returned total stake, while a later runtime stores
+self-stake and delegated stake separately. The original function can continue
+returning their sum, while `getStakeV2` returns the breakdown.
+
+This is a soft deprecation: the old selector remains correct for callers that
+depend on its original meaning.
+
+## Replace raw storage access with typed views
+
+Raw storage access couples a contract to pallet names, storage item names,
+hashers, key shapes, and SCALE encodings. Any internal refactor can then make
+the contract read an empty value or decode the wrong bytes without a useful
+error.
+
+A typed view instead owns the encoding and decoding:
+
+```solidity
+uint64 weight = IMetagraph(METAGRAPH_ADDRESS).getWeight(netuid, uid);
+```
+
+If the underlying storage map, key format, hasher, or value encoding changes,
+the precompile implementation adapts while the Solidity interface remains
+stable. A resulting Rust compilation failure provides a safety net that raw
+storage queries do not.
+
+### Bound collection views
+
+Runtime APIs and storage collections that grow with chain state must not be
+copied into a single Solidity function returning an unbounded array. Expose an
+indexed item with a bounded count, or use a cursor and a caller-supplied limit
+that is capped by a fixed runtime maximum. A fixed-size batch of explicit keys
+is also suitable when callers already know which records they need.
+
+The bound must apply before storage is scanned or results are constructed.
+Calling an unbounded runtime helper and truncating its result afterward does
+not make the precompile bounded. Paginated views should return a next cursor or
+completion indicator and define stable ordering, missing-item behavior, and
+the maximum page size.
+
+### Preserve runtime authorization
+
+A state-changing precompile dispatches the highest-level pallet extrinsic with
+the mapped EVM caller as a signed origin. The pallet then enforces the same
+ownership, role, rate-limit, freeze-window, and validation checks that apply to
+an ordinary signed Substrate transaction.
+
+An extrinsic that permits either Root or a non-Root signer, such as a subnet
+owner, may expose its signed path. Root-only and `None`-only extrinsics are not
+exposed through typed EVM functions. A precompile must never substitute Root,
+invoke an internal state-changing helper, or reproduce the extrinsic logic to
+bypass the top-level checks.
+
+### Read-only infrastructure views
+
+Read-only precompiles let contracts inspect deterministic consensus state
+without receiving any authority to change it:
+
+- Scheduler views expose bounded task metadata so contracts can verify whether
+ and when runtime work is scheduled.
+- Drand views expose beacon configuration, stored pulses, and round ranges for
+ contract logic that depends on the runtime's randomness state.
+- Timestamp views replace raw reads of timestamp storage; `getTimestamp`
+ corresponds to the same underlying time represented by `block.timestamp`.
+- Lifecycle views let contracts and tooling discover whether a selector is
+ deprecated, replaced, or currently unavailable.
+
+These views replace raw storage decoding or off-chain RPC composition. They do
+not execute privileged extrinsics and do not provide a path to Root.
+
+### Phasing out raw storage reads
+
+`StorageQueryPrecompile` at `0x…0807` exposes raw Substrate storage and is
+inherently brittle. The intended migration is:
+
+1. Add a bounded typed view for every storage item in the currently authorized pallets:
+ SubtensorModule, Balances, Proxy, Scheduler, Drand, Crowdloan, Sudo,
+ Multisig, Timestamp, and Swap.
+2. Soft-deprecate raw storage access after that typed coverage exists.
+3. Hard-deprecate it after a documented migration window.
+4. Eventually disable it through an explicit root decision.
+
+Whether this 1:1 coverage should extend beyond the authorized pallets remains
+an open design question.
+
+## Project-scoped event relays
+
+Substrate events are already recorded in chain data. Reproducing the complete
+event stream through protocol-level EVM callbacks would add another on-chain
+copy together with subscription storage, delivery queues, and callback
+execution. It would also force the runtime to support broad event delivery even
+when an application needs only a small, highly filtered set of signals.
+
+Bittensor therefore does not propose event-reporting precompiles. A project
+that needs proactive notifications in its EVM contracts should run an
+off-chain relay tailored to that project's use cases. The relay watches
+finalized Substrate events, performs application-specific filtering,
+aggregation, and enrichment off chain, and submits only the reports that the
+project's contracts can act on.
+
+Typed precompile views remain the authoritative way for contracts to read
+current runtime state. Relayed reports are notifications under the trust and
+availability model chosen by the project.
+
+### Relay flow
+
+A typical relay operates as follows:
+
+1. Relay nodes read finalized blocks and events from Substrate RPC endpoints or
+ an indexer.
+2. Each node applies the project's filters and derives a canonical typed
+ report.
+3. A configured signer quorum attests to the report.
+4. A relayer submits the report and its authorization proof in an ordinary EVM
+ transaction.
+5. The reporting contract verifies the report, rejects duplicates, and either
+ emits a typed EVM log, invokes a bounded set of subscribed receivers, or
+ records data for receivers to pull.
+
+A report should identify at least the source chain, finalized block hash and
+number, source event position or another unique event identifier, schema
+version, payload, and relay sequence or nonce. The signed message must be
+domain-separated by chain ID, reporting-contract address, and schema version so
+that it cannot be replayed on another chain, contract, or report type.
+
+Filtering belongs primarily in the relay. A subnet application might publish
+only completed tempo summaries, material configuration changes, or aggregate
+emission results instead of reproducing every underlying pallet event.
+
+### Subscription-capable reporting contracts
+
+A project can deploy a reporting contract that lets users or other contracts
+register subscriptions and lets authorized relayers submit observed reports.
+A subscription can select typed report kinds, project-specific filters, a
+receiver, and a callback gas limit. The contract should make its payment,
+retry, ordering, and removal rules explicit.
+
+Neither report submission nor callback delivery should iterate an unbounded
+subscriber set. Limit each transaction to a fixed-size batch, let relayers
+target matching subscribers explicitly, or let subscribers pull verified
+reports. Catch callback failures so one receiver cannot revert delivery to
+others, and require receiver callbacks to be idempotent.
+
+Every successful relay submission has an EVM transaction and receipt. Projects
+must decide whether relayers fund these transactions, subscribers prepay for
+delivery, or another project account subsidizes them.
+
+### Relayer trust and security
+
+A single relay signer is the simplest design but makes that signer a trusted
+oracle. Projects that need stronger guarantees can use an independently
+operated committee with an explicit `M-of-N` multisignature, a threshold
+signature scheme, or another auditable quorum mechanism. The reporting
+contract must define signer enrollment, quorum, key rotation, emergency
+revocation, and version upgrades.
+
+Relay implementations should also:
+
+- wait for the documented source-chain finality condition;
+- use deterministic report encoding and reject duplicate event identifiers;
+- expose sequences or source positions so receivers can detect gaps;
+- tolerate delayed, reordered, and repeated submissions;
+- bound report size, callback gas, batch size, and retained on-chain history;
+- separate observation from submission so any permitted party can submit a
+ valid quorum-authorized report; and
+- provide a reconciliation path through typed precompile views when a report is
+ missing or disputed.
+
+Contracts must not treat relayed events as consensus-authenticated merely
+because they describe on-chain activity. Their integrity depends on the relay
+committee and verification rules, while their availability depends on relay
+operators continuing to observe and submit reports.
+
+## Function lifecycle
+
+Deprecation and disablement are different dimensions:
+
+- **Deprecation** communicates API evolution. It normally points callers toward
+ a replacement and is expected to remain part of the function's history.
+- **Disablement** is an operational switch for an entire precompile. Root can
+ disable and later re-enable it through
+ `AdminUtils.sudo_toggle_evm_precompile`.
+
+| Lifecycle condition | Call behavior |
+|---|---|
+| Active and enabled | Executes normally |
+| Soft-deprecated and enabled | Preserves its documented behavior |
+| Hard-deprecated and enabled | Returns a descriptive precompile error |
+| Disabled | Returns a precompile-disabled error regardless of function lifecycle |
+
+Soft deprecation is the default. Hard deprecation is reserved for cases where
+the original behavior cannot be represented honestly or safely—for example,
+when the underlying concept has been removed without a replacement.
+
+Disablement does not erase deprecation metadata. A soft-deprecated function can
+also be disabled, and re-enabling its precompile restores its soft-deprecated
+behavior.
+
+## Discovering status
+
+The standalone registry precompile gives tooling and contracts one
+place to inspect both API lifecycle and operational availability.
+
+Because the result covers both lifecycle and operational availability, it is
+called `PrecompileStatus`:
+
+```solidity
+interface IPrecompileRegistry {
+ struct PrecompileStatus {
+ bool isDeprecated;
+ bool isDisabled;
+ address newPrecompile;
+ bytes4 newSelector;
+ string message;
+ }
+
+ function getPrecompileStatus(
+ address precompile,
+ bytes4 selector
+ ) external view returns (PrecompileStatus memory);
+}
+```
+
+The fields have the following meaning:
+
+| Field | Meaning |
+|---|---|
+| `isDeprecated` | The function is soft- or hard-deprecated. |
+| `isDisabled` | The containing precompile is currently disabled by Root; Root can re-enable it. |
+| `newPrecompile` | Address of the recommended replacement, often the same address. |
+| `newSelector` | Selector of the recommended replacement function. |
+| `message` | Human-readable status or migration guidance. |
+
+Zero replacement fields mean that no replacement is available. Tooling should
+not infer that `isDisabled` implies deprecation, or that re-enabling a
+precompile clears `isDeprecated`.
+
+The registry avoids adding overhead to every deprecated call. Deployment tools,
+frontends, and upgradeable contracts can query it when evaluating dependencies.
+
+Solidity interfaces should also carry NatSpec annotations:
+
+```solidity
+interface IMetagraph {
+ /// @deprecated Use getStakeV2 instead.
+ function getStake(
+ uint16 netuid,
+ uint16 uid
+ ) external view returns (uint64);
+
+ function getStakeV2(
+ uint16 netuid,
+ uint16 uid
+ ) external view returns (StakeInfo memory);
+}
+```
+
+## Handling runtime changes
+
+### Additive representation changes
+
+Keep the original function returning the original subset, add a versioned
+function for the extended result, and soft-deprecate the original if callers
+should migrate.
+
+### Semantic refinements
+
+Adapt the original implementation to preserve its documented meaning. Add a new
+version only when callers need a representation that the original return type
+cannot express.
+
+### Storage and computation changes
+
+Change the precompile implementation without changing its interface. This
+includes changing:
+
+- storage names, key shapes, or hashers;
+- the number of storage items used;
+- intermediate representations;
+- the computation used to produce the exposed value.
+
+### Complete removal
+
+Keep the selector reserved and make the function return a descriptive error.
+Mark it hard-deprecated and explain whether an alternative exists. Do not
+delete the signature and do not reuse its selector.
+
+### Emergency disablement
+
+Root may disable a precompile with:
+
+```text
+AdminUtils.sudo_toggle_evm_precompile(precompile_id, false)
+```
+
+and re-enable it with:
+
+```text
+AdminUtils.sudo_toggle_evm_precompile(precompile_id, true)
+```
+
+This switch is reversible and applies to the precompile as a whole. It is not a
+substitute for function-level lifecycle metadata or a normal deprecation
+process.
+
+## Maintenance and testing requirements
+
+Every precompile change should verify:
+
+- all previously released selectors remain routed;
+- existing function signatures and return encodings are unchanged;
+- old semantics are preserved or explicitly hard-deprecated;
+- new behavior uses a new versioned selector when necessary;
+- Solidity interfaces, generated ABIs, SDK copies, and runtime implementations
+ agree;
+- lifecycle registry metadata and NatSpec annotations agree;
+- disable and re-enable behavior is covered for the affected precompile;
+- state-changing functions dispatch the highest-level extrinsic as the mapped
+ signed caller and do not bypass its authorization checks;
+- bulk views are bounded before they read or construct results;
+- new domain addresses follow the documented sequential reservation and are
+ locked by routing tests;
+- no selector is reused.
+
+Typed views provide a compile-time safety advantage: when runtime types or
+storage APIs change, the Rust implementation is more likely to stop compiling,
+forcing maintainers to make an explicit compatibility decision. Coverage checks
+should ensure that every storage item in the authorized pallets has a
+corresponding view.
+
+Macro or code-generation support may eventually reduce boilerplate and validate
+selector coverage, ABI synchronization, and registry entries. The compatibility
+rules should remain explicit even if their enforcement becomes automated.
+
+## Summary
+
+Precompiles are a long-lived contract between Subtensor and deployed EVM code.
+Keep addresses and released selectors stable, version functions additively,
+preserve old semantics whenever possible, and replace raw storage access with
+typed views that insulate callers from storage layouts. Use deprecation to guide
+migration and reversible disablement to handle operational risk; report both
+through a common status model without treating them as the same condition.
diff --git a/docs/guides/evm/precompiles/account-balance.mdx b/docs/guides/evm/precompiles/account-balance.mdx
new file mode 100644
index 0000000000..478cbcf9f9
--- /dev/null
+++ b/docs/guides/evm/precompiles/account-balance.mdx
@@ -0,0 +1,33 @@
+---
+title: Account balance
+description: Reference for the deployed BalancePrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `BalancePrecompile` |
+| Solidity interface | `IBalance` |
+| Address | `0x000000000000000000000000000000000000080e` |
+| Status | Deployed |
+
+## Functions
+
+| Function | Mutability |
+|---|---|
+| `getFreeBalance(bytes32)` | `view` |
+| `getTotalIssuance()` | `view` |
+
+## Added operations
+
+| Function | Source extrinsic |
+|---|---|
+| `burnBalance` | `Balances.burn` |
+| `upgradeAccounts` | `Balances.upgrade_accounts` |
+
+`upgradeAccounts` has an explicit input bound of 64 accounts. Both operations
+dispatch the highest-level Balances call as the mapped signer and preserve all
+runtime authorization and issuance invariants. Force operations require Root
+and are not exposed.
+
+
+Source: [`balance.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/balance.sol)
diff --git a/docs/guides/evm/precompiles/alpha.mdx b/docs/guides/evm/precompiles/alpha.mdx
new file mode 100644
index 0000000000..ae4cf01904
--- /dev/null
+++ b/docs/guides/evm/precompiles/alpha.mdx
@@ -0,0 +1,65 @@
+---
+title: Alpha
+description: Reference for the deployed AlphaPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `AlphaPrecompile` |
+| Solidity interface | `IAlpha` |
+| Address | `0x0000000000000000000000000000000000000808` |
+| Status | Deployed |
+
+Provides typed views of subnet pools, prices, issuance, emissions, and simulated
+swaps. All functions are `view`.
+
+## Functions
+
+```text
+getAlphaPrice(uint16)
+getMovingAlphaPrice(uint16)
+getTaoInPool(uint16)
+getAlphaInPool(uint16)
+getAlphaOutPool(uint16)
+getAlphaIssuance(uint16)
+getTaoWeight()
+simSwapTaoForAlpha(uint16,uint64)
+simSwapAlphaForTao(uint16,uint64)
+getSubnetMechanism(uint16)
+getRootNetuid()
+getEMAPriceHalvingBlocks(uint16)
+getSubnetVolume(uint16)
+getTaoInEmission(uint16)
+getAlphaInEmission(uint16)
+getAlphaOutEmission(uint16)
+getSumAlphaPrice()
+getCKBurn()
+getEmissionAccounting(uint16,bytes32)
+getSubnetEconomicState(uint16)
+getSubnetFlowState(uint16)
+getEmissionGateConfig()
+getSwapState(uint16)
+hasSwapMigrationRun(bytes)
+```
+
+Flow values use signed Solidity integers. Fixed-point economic values are
+returned as their raw runtime bits. `getSwapState` includes the fee,
+initialization status, balancer quote weight, and both protocol reservoirs;
+the initialization flag is the generic swap-initialization view.
+
+## Added operations
+
+| Function | Source extrinsic |
+|---|---|
+| `setRecycleOrBurn` | `AdminUtils.sudo_set_recycle_or_burn` |
+| `setBurnHalfLife` | `AdminUtils.sudo_set_burn_half_life` |
+| `setBurnIncreaseMultiplier` | `AdminUtils.sudo_set_burn_increase_mult` |
+
+The five deprecated `Swap` liquidity extrinsics are intentionally not proposed;
+they always return the pallet's `Deprecated` error. `Swap.set_fee_rate` and the
+remaining Alpha-related AdminUtils calls require Root and are not exposed.
+The three listed AdminUtils calls accept a signed subnet owner; only that
+signed path is exposed and runtime authorization remains in force.
+
+
+Source: [`alpha.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/alpha.sol)
diff --git a/docs/guides/evm/precompiles/balance-transfer.mdx b/docs/guides/evm/precompiles/balance-transfer.mdx
new file mode 100644
index 0000000000..b94532ccf9
--- /dev/null
+++ b/docs/guides/evm/precompiles/balance-transfer.mdx
@@ -0,0 +1,37 @@
+---
+title: Balance transfer
+description: Reference for the deployed BalanceTransferPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `BalanceTransferPrecompile` |
+| Solidity interface | `ISubtensorBalanceTransfer` |
+| Address | `0x0000000000000000000000000000000000000800` |
+| Status | Deployed |
+
+Transfers the EVM call value to the Substrate account supplied as a 32-byte
+public key.
+
+## Functions
+
+| Function | Mutability |
+|---|---|
+| `transfer(bytes32)` | `payable` |
+
+## Added operations
+
+| Function | Source extrinsic |
+|---|---|
+| `transferKeepAlive` | `Balances.transfer_keep_alive` |
+| `transferAll` | `Balances.transfer_all` |
+
+The existing `transfer(bytes32)` semantically covers
+`Balances.transfer_allow_death` by taking the amount from attached EVM value.
+The added functions use explicit typed arguments where attached value does
+not express the complete source operation. They dispatch the highest-level
+Balances call as the mapped signer. `force_transfer` requires Root and the
+feature-gated development faucet is not part of the production interface.
+
+
+Source: [`balanceTransfer.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/balanceTransfer.sol)
diff --git a/docs/guides/evm/precompiles/crowdloan.mdx b/docs/guides/evm/precompiles/crowdloan.mdx
new file mode 100644
index 0000000000..5684ff5a3f
--- /dev/null
+++ b/docs/guides/evm/precompiles/crowdloan.mdx
@@ -0,0 +1,46 @@
+---
+title: Crowdloan
+description: Reference for the deployed CrowdloanPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `CrowdloanPrecompile` |
+| Solidity interface | `ICrowdloan` |
+| Address | `0x0000000000000000000000000000000000000809` |
+| Status | Deployed |
+
+## Views
+
+```text
+getCrowdloan(uint32)
+getContribution(uint32,bytes32)
+```
+
+## Operations
+
+All operations are `payable`:
+
+```text
+create(uint64,uint64,uint64,uint32,address)
+contribute(uint32,uint64)
+withdraw(uint32)
+finalize(uint32)
+refund(uint32)
+dissolve(uint32)
+updateMinContribution(uint32,uint64)
+updateEnd(uint32,uint32)
+updateCap(uint32,uint64)
+```
+
+## Added operation
+
+| Function | Source extrinsic |
+|---|---|
+| `setMaxContribution` | `Crowdloan.set_max_contribution` |
+
+The typed interface preserves the source call's optional value so the creator
+can either set or clear the per-contributor maximum.
+
+
+Source: [`crowdloan.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/crowdloan.sol)
diff --git a/docs/guides/evm/precompiles/drand.mdx b/docs/guides/evm/precompiles/drand.mdx
new file mode 100644
index 0000000000..ebfbf778af
--- /dev/null
+++ b/docs/guides/evm/precompiles/drand.mdx
@@ -0,0 +1,38 @@
+---
+title: Drand
+description: Typed EVM interface for stored Drand randomness.
+---
+
+| Property | Value |
+|---|---|
+| Implementation | `DrandPrecompile` |
+| Solidity interface | `IDrand` |
+| Address | `0x0000000000000000000000000000000000000810` |
+| Status | Deployed |
+
+This precompile exposes typed beacon configuration and pulse data instead
+of requiring callers to construct Drand storage keys and decode SCALE values.
+Contracts can use the runtime's stored randomness state deterministically
+without receiving permission to configure the beacon or submit pulses.
+
+## Views
+
+| Function | Replaces |
+|---|---|
+| `getBeaconConfig()` | `Drand.BeaconConfig` |
+| `getPulse(uint64 round)` | `Drand.Pulses` |
+| `getStoredRoundRange()` | `Drand.OldestStoredRound` and `Drand.LastStoredRound` |
+| `getNextUnsignedAt()` | `Drand.NextUnsignedAt` |
+| `hasMigrationRun(bytes key)` | `Drand.HasMigrationRun` |
+
+## State-changing operations
+
+`Drand.set_beacon_config` and `Drand.set_oldest_stored_round` require Root, and
+`Drand.write_pulse` requires `None` origin as an unsigned offchain-worker
+submission. None is exposed as a typed EVM operation because doing so would
+bypass the pallet's top-level origin checks.
+
+`hasMigrationRun(bytes)` bounds the supplied key to 128 bytes before reading
+storage.
+
+Source: [`drand.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/drand.sol)
diff --git a/docs/guides/evm/precompiles/extrinsic-coverage.mdx b/docs/guides/evm/precompiles/extrinsic-coverage.mdx
new file mode 100644
index 0000000000..ce41488e86
--- /dev/null
+++ b/docs/guides/evm/precompiles/extrinsic-coverage.mdx
@@ -0,0 +1,86 @@
+---
+title: Extrinsic coverage
+description: Audit of typed EVM coverage for every extrinsic in the authorized runtime pallets.
+---
+
+This audit covers the runtime's `SubtensorModule`, `AdminUtils`, `Balances`,
+`Proxy`, `Scheduler`, `Drand`, `Crowdloan`, `Timestamp`, and `Swap` pallets.
+Sudo and Multisig extrinsics are intentionally outside typed precompile
+coverage.
+
+Generic SCALE dispatch through the Frontier `Dispatch` precompile does not
+count as typed coverage. A covered operation must have a stable Solidity
+interface or an explicit proposed typed replacement.
+
+## Coverage summary
+
+| Pallet | Runtime extrinsics | Typed today | Proposed signed additions | Not exposed |
+|---|---:|---:|---:|---:|
+| `SubtensorModule` | 82 | 68 | 0 | 14 |
+| `AdminUtils` | 86 | 40 | 0 | 46 |
+| `Balances` | 9 | 5 | 0 | 4 |
+| `Proxy` | 12 | 11 | 1 | 0 |
+| `Scheduler` | 10 | 0 | 0 | 10 |
+| `Drand` | 3 | 0 | 0 | 3 |
+| `Crowdloan` | 10 | 10 | 0 | 0 |
+| `Timestamp` | 1 | 0 | 0 | 1 |
+| `Swap` | 6 | 0 | 0 | 6 |
+| **Total** | **219** | **134** | **1** | **84** |
+
+`Typed today` counts semantic coverage, not only direct dispatch to the same
+Rust call. For example, `registerNetwork(bytes32)` covers basic subnet
+registration by dispatching `register_network_with_identity` with empty
+identity fields.
+
+`Proposed signed additions` includes extrinsics whose highest-level pallet call
+accepts a non-Root signed origin. If a call also accepts Root, only its signed
+path is exposed: the mapped EVM caller is dispatched as `Signed`, and the
+pallet performs its normal authorization checks.
+
+## Signed additions
+
+Each implemented operation is listed on the page of its target precompile:
+
+| Target precompile | Missing extrinsics assigned |
+|---|---:|
+| [Subnet](/docs/guides/evm/precompiles/subnet) | 13 |
+| [Staking V2](/docs/guides/evm/precompiles/staking-v2) | 20 |
+| [Neuron](/docs/guides/evm/precompiles/neuron) | 21 |
+| [Alpha](/docs/guides/evm/precompiles/alpha) | 3 |
+| [Account balance](/docs/guides/evm/precompiles/account-balance) | 2 |
+| [Proxy](/docs/guides/evm/precompiles/proxy) | 4 |
+| [Balance transfer](/docs/guides/evm/precompiles/balance-transfer) | 2 |
+| [Voting power](/docs/guides/evm/precompiles/voting-power) | 2 |
+| [Leasing](/docs/guides/evm/precompiles/leasing) | 1 |
+| [Crowdloan](/docs/guides/evm/precompiles/crowdloan) | 1 |
+
+The only remaining proposed signed operation is `Proxy.proxy_announced`. It
+requires a stable, versioned EVM description of the proxied runtime call and
+must not add another SCALE-encoded `RuntimeCall` interface.
+
+## Extrinsics not exposed as EVM calls
+
+| Pallet extrinsic | Reason |
+|---|---|
+| Root-only `SubtensorModule` extrinsics | `dissolve_network`, `root_dissolve_network`, `swap_coldkey`, `sudo_set_tx_childkey_take_rate_limit`, `sudo_set_min_childkey_take`, `sudo_set_max_childkey_take`, `set_pending_childkey_cooldown`, `reset_coldkey_swap`, `sudo_set_num_root_claims`, and `sudo_set_voting_power_ema_alpha` require Root. |
+| `SubtensorModule.schedule_swap_coldkey` | Deprecated compatibility call that always returns `Deprecated`. |
+| `SubtensorModule.faucet` | Build-feature-only development call; it is not part of the production runtime interface. |
+| `SubtensorModule.set_tempo` | Retained call-index compatibility entry point that succeeds without changing state. The real setting is exposed as `SubnetPrecompile.setTempo` through `AdminUtils.sudo_set_tempo`. |
+| `SubtensorModule.set_activity_cutoff_factor` | Retained call-index compatibility entry point that succeeds without changing state. The active AdminUtils operation is already covered by `SubnetPrecompile.setActivityCutoffFactor`. |
+| Root-only `AdminUtils` extrinsics | Root-only administration is not delegated to EVM callers. For calls that also accept a signed subnet owner, the domain precompile dispatches the highest-level call as the mapped EVM signer and preserves its authorization checks. |
+| `AdminUtils.sudo_set_total_issuance` | Deprecated call that always returns `Deprecated`. |
+| Root-only `Balances` extrinsics | `force_unreserve`, `force_transfer`, `force_set_balance`, and `force_adjust_total_issuance` require Root. |
+| All `Scheduler` extrinsics | `Scheduler.ScheduleOrigin` is configured as Root in the runtime. |
+| `Drand.write_pulse` | Unsigned offchain-worker submission requiring `None` origin. An EVM caller cannot satisfy that origin without changing its security model. |
+| Drand configuration extrinsics | `set_beacon_config` and `set_oldest_stored_round` require Root. |
+| `Timestamp.set` | Block-production inherent requiring `None` origin. Contracts already receive the same time through `block.timestamp`. |
+| `Swap.set_fee_rate` | Requires Root. |
+| `Swap.add_liquidity` | Permanently disabled pallet call that always returns `Deprecated`. |
+| `Swap.remove_liquidity` | Permanently disabled pallet call that always returns `Deprecated`. |
+| `Swap.modify_position` | Permanently disabled pallet call that always returns `Deprecated`. |
+| `Swap.toggle_user_liquidity` | Permanently disabled pallet call that always returns `Deprecated`. |
+| `Swap.disable_lp` | Permanently disabled pallet call that always returns `Deprecated`. |
+
+These exclusions preserve the existing runtime origin and lifecycle semantics.
+A typed precompile must not manufacture Root or `None`, call an internal helper,
+or write storage directly to make one of these operations callable.
diff --git a/docs/guides/evm/precompiles/index.mdx b/docs/guides/evm/precompiles/index.mdx
new file mode 100644
index 0000000000..6a264d9b98
--- /dev/null
+++ b/docs/guides/evm/precompiles/index.mdx
@@ -0,0 +1,66 @@
+---
+title: Precompiles
+description: Addresses, implementations, and reference pages for Bittensor EVM precompiles.
+---
+
+Bittensor precompiles are fixed-address contracts implemented by the Subtensor
+runtime. `Deployed` means that the address is registered in the current runtime;
+it does not imply complete coverage of the underlying runtime domain.
+`Proposed` precompiles are not callable. Their documented addresses are
+reserved for those domains, while their function selectors remain provisional
+until the interfaces are implemented and released.
+
+The [extrinsic coverage audit](/docs/guides/evm/precompiles/extrinsic-coverage)
+tracks every runtime extrinsic in scope and identifies its deployed, proposed,
+or intentionally non-callable EVM treatment.
+
+## Ethereum and Frontier precompiles
+
+| Precompile | Address | Status |
+|---|---|---|
+| `ECRecover` | | Deployed |
+| `Sha256` | | Deployed |
+| `Ripemd160` | | Deployed |
+| `Identity` | | Deployed |
+| `Modexp` | | Deployed |
+| `Dispatch` | | Deployed |
+| `Bn128Mul` | | Deployed |
+| `Bn128Pairing` | | Deployed |
+| `Bn128Add` | | Deployed |
+| `Sha3FIPS256` | | Deployed |
+| `ECRecoverPublicKey` | | Deployed |
+| `Ed25519Verify` | | Deployed |
+| `Sr25519Verify` | | Deployed |
+
+## Bittensor precompiles
+
+| Precompile | Solidity interface | Details |
+|---|---|---|
+| [`BalanceTransferPrecompile`](/docs/guides/evm/precompiles/balance-transfer) | `ISubtensorBalanceTransfer` |
Deployed |
+| [`StakingPrecompile`](/docs/guides/evm/precompiles/staking-v1) | `IStaking` V1 |
Deployed |
+| [`MetagraphPrecompile`](/docs/guides/evm/precompiles/metagraph) | `IMetagraph` |
Deployed |
+| [`SubnetPrecompile`](/docs/guides/evm/precompiles/subnet) | `ISubnet` |
Deployed |
+| [`NeuronPrecompile`](/docs/guides/evm/precompiles/neuron) | `INeuron` |
Deployed |
+| [`StakingPrecompileV2`](/docs/guides/evm/precompiles/staking-v2) | `IStaking` V2 |
Deployed |
+| [`UidLookupPrecompile`](/docs/guides/evm/precompiles/uid-lookup) | `IUidLookup` |
Deployed |
+| [`StorageQueryPrecompile`](/docs/guides/evm/precompiles/storage-query) | Selectorless |
Deployed · deprecation planned |
+| [`AlphaPrecompile`](/docs/guides/evm/precompiles/alpha) | `IAlpha` |
Deployed |
+| [`CrowdloanPrecompile`](/docs/guides/evm/precompiles/crowdloan) | `ICrowdloan` |
Deployed |
+| [`LeasingPrecompile`](/docs/guides/evm/precompiles/leasing) | `ILeasing` |
Deployed |
+| [`ProxyPrecompile`](/docs/guides/evm/precompiles/proxy) | `IProxy` |
Deployed |
+| [`AddressMappingPrecompile`](/docs/guides/evm/precompiles/address-mapping) | `IAddressMapping` |
Deployed |
+| [`VotingPowerPrecompile`](/docs/guides/evm/precompiles/voting-power) | `IVotingPower` |
Deployed |
+| [`BalancePrecompile`](/docs/guides/evm/precompiles/account-balance) | `IBalance` |
Deployed |
+| [`SchedulerPrecompile`](/docs/guides/evm/precompiles/scheduler) | `IScheduler` |
Deployed |
+| [`DrandPrecompile`](/docs/guides/evm/precompiles/drand) | `IDrand` |
Deployed |
+| [`TimestampPrecompile`](/docs/guides/evm/precompiles/timestamp) | `ITimestamp` |
Deployed |
+| [`RuntimeConfigurationPrecompile`](/docs/guides/evm/precompiles/runtime-configuration) | `IRuntimeConfiguration` |
Deployed |
+| [`PrecompileRegistry`](/docs/guides/evm/precompiles/registry) | `IPrecompileRegistry` |
Deployed |
+
+Projects that need proactive event delivery should use
+[project-scoped event relays](/docs/guides/evm/precompile-design#project-scoped-event-relays)
+instead of protocol-level event-reporting precompiles.
+
+Released addresses and selectors remain reserved permanently. The compatibility
+and lifecycle rules are documented in
+[Precompile design and lifecycle](/docs/guides/evm/precompile-design).
diff --git a/docs/guides/evm/precompiles/leasing.mdx b/docs/guides/evm/precompiles/leasing.mdx
new file mode 100644
index 0000000000..52694ee71f
--- /dev/null
+++ b/docs/guides/evm/precompiles/leasing.mdx
@@ -0,0 +1,41 @@
+---
+title: Leasing
+description: Reference for the deployed LeasingPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `LeasingPrecompile` |
+| Solidity interface | `ILeasing` |
+| Address | `0x000000000000000000000000000000000000080a` |
+| Status | Deployed |
+
+## Views
+
+```text
+getLease(uint32)
+getContributorShare(uint32,bytes32)
+getLeaseIdForSubnet(uint16)
+getNextLeaseId()
+getAccumulatedLeaseDividends(uint32)
+```
+
+## Operations
+
+```text
+createLeaseCrowdloan(uint64,uint64,uint64,uint32,uint8,bool,uint32)
+terminateLease(uint32,bytes32)
+```
+
+Both operations are `payable`.
+
+## Added operation
+
+| Function | Source extrinsic |
+|---|---|
+| `startCall` | `SubtensorModule.start_call` |
+
+`startCall` accepts a signed subnet owner. The Root-only start-call delay
+configuration is not exposed.
+
+Source: [`leasing.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/leasing.sol)
diff --git a/docs/guides/evm/precompiles/metagraph.mdx b/docs/guides/evm/precompiles/metagraph.mdx
new file mode 100644
index 0000000000..f463c35454
--- /dev/null
+++ b/docs/guides/evm/precompiles/metagraph.mdx
@@ -0,0 +1,45 @@
+---
+title: Metagraph
+description: Reference for the deployed MetagraphPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `MetagraphPrecompile` |
+| Solidity interface | `IMetagraph` |
+| Address | `0x0000000000000000000000000000000000000802` |
+| Status | Deployed |
+
+Provides typed views of per-neuron metagraph values.
+
+## Functions
+
+All functions are `view`:
+
+```text
+getUidCount(uint16)
+getStake(uint16,uint16)
+getRank(uint16,uint16)
+getTrust(uint16,uint16)
+getConsensus(uint16,uint16)
+getIncentive(uint16,uint16)
+getDividends(uint16,uint16)
+getEmission(uint16,uint16)
+getVtrust(uint16,uint16)
+getValidatorStatus(uint16,uint16)
+getLastUpdate(uint16,uint16)
+getIsActive(uint16,uint16)
+getAxon(uint16,uint16)
+getHotkey(uint16,uint16)
+getColdkey(uint16,uint16)
+```
+
+## Proposed bulk runtime API
+
+`SubnetInfoRuntimeApi.get_all_metagraphs` remains proposed. Its current result
+can grow with the number and size of subnets, so no Solidity selector is
+assigned in this change. Before implementation, it needs a bounded
+cursor-based or indexed interface with stable typed metadata. The deployed
+per-subnet and per-UID views above are unchanged.
+
+Source: [`metagraph.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/metagraph.sol)
diff --git a/docs/guides/evm/precompiles/neuron.mdx b/docs/guides/evm/precompiles/neuron.mdx
new file mode 100644
index 0000000000..3a7cd165d0
--- /dev/null
+++ b/docs/guides/evm/precompiles/neuron.mdx
@@ -0,0 +1,105 @@
+---
+title: Neuron
+description: Reference for the deployed NeuronPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `NeuronPrecompile` |
+| Solidity interface | `INeuron` |
+| Address | `0x0000000000000000000000000000000000000804` |
+| Status | Deployed |
+
+Registers neurons, publishes serving endpoints, and submits weights. Every
+function is `payable`.
+
+## Functions
+
+```text
+burnedRegister(uint16,bytes32)
+registerLimit(uint16,bytes32,uint64)
+serveAxon(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8)
+serveAxonTls(uint16,uint32,uint128,uint16,uint8,uint8,uint8,uint8,bytes)
+servePrometheus(uint16,uint32,uint128,uint16,uint8)
+setWeights(uint16,uint16[],uint16[],uint64)
+commitWeights(uint16,bytes32)
+revealWeights(uint16,uint16[],uint16[],uint16[],uint64)
+```
+
+## Added weight operations
+
+| Function | Source extrinsic |
+|---|---|
+| `setMechanismWeights` | `SubtensorModule.set_mechanism_weights` |
+| `batchSetWeights` | `SubtensorModule.batch_set_weights` |
+| `commitMechanismWeights` | `SubtensorModule.commit_mechanism_weights` |
+| `batchCommitWeights` | `SubtensorModule.batch_commit_weights` |
+| `revealMechanismWeights` | `SubtensorModule.reveal_mechanism_weights` |
+| `commitCrv3MechanismWeights` | `SubtensorModule.commit_crv3_mechanism_weights` |
+| `batchRevealWeights` | `SubtensorModule.batch_reveal_weights` |
+| `commitTimelockedWeights` | `SubtensorModule.commit_timelocked_weights` |
+| `commitTimelockedMechanismWeights` | `SubtensorModule.commit_timelocked_mechanism_weights` |
+
+Batch calls accept at most 16 outer items and at most 4,096 weight entries per
+inner array. Timelocked commits and registration work are bounded to 5,000 and
+64 bytes respectively; none of these functions accepts SCALE-encoded runtime
+calls.
+
+## Added registration and key operations
+
+| Function | Source extrinsic |
+|---|---|
+| `register` | `SubtensorModule.register` |
+| `rootRegister` | `SubtensorModule.root_register` |
+| `swapHotkey` | `SubtensorModule.swap_hotkey` |
+| `swapHotkeyV2` | `SubtensorModule.swap_hotkey_v2` |
+| `setChildren` | `SubtensorModule.set_children` |
+| `setIdentity` | `SubtensorModule.set_identity` |
+| `tryAssociateHotkey` | `SubtensorModule.try_associate_hotkey` |
+| `associateEvmKey` | `SubtensorModule.associate_evm_key` |
+| `announceColdkeySwap` | `SubtensorModule.announce_coldkey_swap` |
+| `executeAnnouncedColdkeySwap` | `SubtensorModule.swap_coldkey_announced` |
+| `disputeColdkeySwap` | `SubtensorModule.dispute_coldkey_swap` |
+| `clearColdkeySwapAnnouncement` | `SubtensorModule.clear_coldkey_swap_announcement` |
+
+Every added operation accepts a non-Root signed origin. The precompile
+dispatches the highest-level extrinsic as the mapped caller so runtime
+authorization remains in force. Root-only and deprecated compatibility calls
+are classified in the [coverage audit](./extrinsic-coverage).
+
+## Typed state views
+
+```text
+getUid(uint16,bytes32)
+isNetworkMember(bytes32,uint16)
+getWeights(uint16,uint16)
+getBonds(uint16,uint16)
+getBlockAtRegistration(uint16,uint16)
+getNeuronCertificate(uint16,bytes32)
+getPrometheus(uint16,bytes32)
+getChainIdentity(bytes32)
+getSubnetIdentity(uint16)
+getLoadedEmission(uint16)
+getTransactionKeyLastBlock(bytes32,uint16,uint16)
+getLegacyTransactionRateBlocks(bytes32)
+getWeightCommitCount(uint16,bytes32)
+getWeightCommit(uint16,bytes32,uint32)
+getTimelockedWeightCommitCount(uint16,uint64)
+getTimelockedWeightCommit(uint16,uint64,uint32)
+getLegacyTimelockedWeightCommitCount(uint8,uint16,uint64)
+getLegacyTimelockedWeightCommit(uint8,uint16,uint64,uint32)
+```
+
+The indexed commit readers expose stable metadata. Timelocked ciphertext is
+represented by its Keccak-256 hash and byte length instead of returning the
+runtime's encrypted payload type.
+
+## Proposed bulk runtime API
+
+`NeuronInfoRuntimeApi.get_neurons` remains proposed. Its current result can
+grow with subnet size, so no Solidity selector is assigned in this change.
+Before implementation, it needs a bounded cursor-based or indexed interface
+with a stable typed result. Existing single-neuron and per-field views remain
+available through the deployed Neuron and Metagraph precompiles.
+
+Source: [`neuron.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/neuron.sol)
diff --git a/docs/guides/evm/precompiles/proxy.mdx b/docs/guides/evm/precompiles/proxy.mdx
new file mode 100644
index 0000000000..0880a66b39
--- /dev/null
+++ b/docs/guides/evm/precompiles/proxy.mdx
@@ -0,0 +1,59 @@
+---
+title: Proxy
+description: Reference for the deployed ProxyPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `ProxyPrecompile` |
+| Solidity interface | `IProxy` |
+| Address | `0x000000000000000000000000000000000000080b` |
+| Status | Deployed |
+
+## Functions
+
+| Function | Mutability |
+|---|---|
+| `createPureProxy(uint8,uint32,uint16)` | nonpayable |
+| `proxyCall(bytes32,uint8[],uint8[])` | nonpayable |
+| `killPureProxy(bytes32,uint8,uint16,uint32,uint32)` | nonpayable |
+| `addProxy(bytes32,uint8,uint32)` | nonpayable |
+| `removeProxy(bytes32,uint8,uint32)` | nonpayable |
+| `removeProxies()` | nonpayable |
+| `pokeDeposit()` | nonpayable |
+| `getProxies(bytes32)` | `view` |
+| `getProxyDeposit(bytes32)` | `view` |
+| `getAnnouncements(bytes32)` | `view` |
+| `getLastCallResult(bytes32)` | `view` |
+| `isRealPaysFee(bytes32,bytes32)` | `view` |
+
+`getLastCallResult` returns stable success/error metadata. Module errors expose
+the pallet index and four error bytes. Error kinds are `1` Other, `2`
+CannotLookup, `3` BadOrigin, `4` Module, `5` ConsumerRemaining, `6`
+NoProviders, `7` TooManyConsumers, `8` Token, `9` Arithmetic, `10`
+Transactional, `11` Exhausted, `12` Corruption, `13` Unavailable, `14`
+RootNotAllowed, and `15` Trie. This avoids returning SCALE-encoded runtime
+data.
+
+## Added operations
+
+| Function | Source extrinsic |
+|---|---|
+| `announce` | `Proxy.announce` |
+| `removeAnnouncement` | `Proxy.remove_announcement` |
+| `rejectAnnouncement` | `Proxy.reject_announcement` |
+| `setRealPaysFee` | `Proxy.set_real_pays_fee` |
+
+## Proposed operation
+
+| Function | Source extrinsic |
+|---|---|
+| `proxyAnnounced` | `Proxy.proxy_announced` |
+
+`proxyAnnounced` must use the same versioned, stable EVM call description as
+other typed proxy execution. A new interface must not introduce another
+dependency on SCALE-encoded `RuntimeCall`.
+
+No selector is reserved for `proxyAnnounced`.
+
+Source: [`proxy.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/proxy.sol)
diff --git a/docs/guides/evm/precompiles/registry.mdx b/docs/guides/evm/precompiles/registry.mdx
new file mode 100644
index 0000000000..5a0ca38ef0
--- /dev/null
+++ b/docs/guides/evm/precompiles/registry.mdx
@@ -0,0 +1,44 @@
+---
+title: Precompile registry
+description: Registry for precompile lifecycle and availability.
+---
+
+| Property | Value |
+|---|---|
+| Implementation | `PrecompileRegistry` |
+| Solidity interface | `IPrecompileRegistry` |
+| Address | `0x0000000000000000000000000000000000000813` |
+| Status | Deployed |
+
+The registry provides function-level lifecycle metadata and the current
+operational availability of the containing precompile. Contracts, deployment
+tools, and frontends can inspect whether a selector is deprecated, has a
+replacement, or is currently unavailable without attempting the affected call.
+
+## Interface
+
+```solidity
+interface IPrecompileRegistry {
+ struct PrecompileStatus {
+ bool isDeprecated;
+ bool isDisabled;
+ address newPrecompile;
+ bytes4 newSelector;
+ string message;
+ }
+
+ function getPrecompileStatus(
+ address precompile,
+ bytes4 selector
+ ) external view returns (PrecompileStatus memory);
+}
+```
+
+`AdminUtils.sudo_toggle_evm_precompile` is Root-only and is not exposed by this
+precompile. The registry reports availability but does not grant callers
+permission to change it.
+
+The lifecycle model is described in
+[Precompile design and lifecycle](/docs/guides/evm/precompile-design#discovering-status).
+
+Source: [`registry.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/registry.sol)
diff --git a/docs/guides/evm/precompiles/runtime-configuration.mdx b/docs/guides/evm/precompiles/runtime-configuration.mdx
new file mode 100644
index 0000000000..5817de694e
--- /dev/null
+++ b/docs/guides/evm/precompiles/runtime-configuration.mdx
@@ -0,0 +1,66 @@
+---
+title: Runtime configuration
+description: Typed EVM views for global runtime configuration.
+---
+
+| Property | Value |
+|---|---|
+| Implementation | `RuntimeConfigurationPrecompile` |
+| Solidity interface | `IRuntimeConfiguration` |
+| Address | `0x0000000000000000000000000000000000000812` |
+| Status | Deployed |
+
+This domain contains bounded typed views of runtime configuration. The grouped
+constant views read their values from the runtime and therefore reflect the
+runtime version executing the call.
+
+## Views
+
+| Function | Meaning |
+|---|---|
+| `getEvmChainId()` | Current EVM chain identifier |
+| `getTransactionRateLimit()` | Global Subtensor transaction rate limit |
+| `getSubtensorEconomicConstants()` | Initial issuance, burn, stake, transfer, registration-lock, and key-swap balance constants |
+| `getSubtensorSubnetConstants()` | Subnet size, tempo, immunity, activity, owner-cut, and epoch limits |
+| `getSubtensorConsensusConstants()` | Initial weight, emission, Yuma, bonds, pruning, and TAO-weight configuration |
+| `getSubtensorRegistrationConstants()` | Registration difficulty, adjustment, rate-limit, immunity, lock-reduction, and price-EMA configuration |
+| `getSubtensorDelegationConstants()` | Initial delegate and childkey takes plus Liquid Alpha and Yuma feature defaults |
+| `getSubtensorRateLimitConstants()` | Transaction, serving, EVM-association, swap, dissolution, start-call, and lease timing constants |
+| `getSubtensorProtocolConstants()` | Fixed public protocol bounds, flags, voting-power timing, lock timing, and maximum TAO issuance |
+| `getSubtensorSystemAccounts()` | Derived Subtensor pallet and burn accounts as `bytes32` |
+| `getBalancesConstants()` | Existential deposit and lock, reserve, and freeze limits |
+| `getProxyConstants()` | Proxy and announcement deposits and count limits |
+| `getSchedulerConstants()` | Maximum scheduler weight and calls per block |
+| `getDrandConstants()` | Quicknet chain hash, unsigned transaction configuration, and pulse-retention limits |
+| `getCrowdloanConstants()` | Deposit, contribution, duration, contributor, refund, and pallet-account configuration |
+| `getSwapConstants()` | Maximum fee rate, minimum liquidity and reserve, and derived protocol account |
+| `getTimestampConstants()` | Minimum timestamp period |
+| `getAdminConstants()` | Maximum authority count |
+
+## Constant representation
+
+All balance-valued constants are returned as `uint256` values using EVM's
+18-decimal convention. Block numbers, durations, percentages, fixed-point
+parts, and count limits retain the units stated by their Solidity output
+names. `PalletId` configuration is wrapped as the derived 32-byte account that
+contracts can use.
+
+A runtime upgrade may change a constant's returned value. The function
+selector, output type and order, units, and documented meaning remain the
+compatibility contract.
+
+## State-changing operations
+
+The currently identified global configuration extrinsics are Root-only:
+
+```text
+AdminUtils.swap_authorities
+AdminUtils.sudo_set_tx_rate_limit
+AdminUtils.sudo_set_evm_chain_id
+AdminUtils.schedule_grandpa_change
+```
+
+They are not exposed as typed EVM operations. A future view must return a
+typed, bounded representation and must not expose SCALE-encoded runtime values.
+
+Source: [`runtimeConfiguration.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/runtimeConfiguration.sol)
diff --git a/docs/guides/evm/precompiles/scheduler.mdx b/docs/guides/evm/precompiles/scheduler.mdx
new file mode 100644
index 0000000000..662c8c6b95
--- /dev/null
+++ b/docs/guides/evm/precompiles/scheduler.mdx
@@ -0,0 +1,40 @@
+---
+title: Scheduler
+description: Typed EVM interface for Scheduler metadata.
+---
+
+| Property | Value |
+|---|---|
+| Implementation | `SchedulerPrecompile` |
+| Solidity interface | `IScheduler` |
+| Address | `0x000000000000000000000000000000000000080f` |
+| Status | Deployed |
+
+This precompile replaces raw reads of Scheduler storage with a stable EVM
+interface. It lets contracts inspect whether and when runtime work is
+scheduled without decoding Scheduler storage or acquiring permission to modify
+the schedule.
+
+## Views
+
+| Function | Replaces |
+|---|---|
+| `getIncompleteSince()` | `Scheduler.IncompleteSince` |
+| `getScheduledCall(uint64 when,uint32 index)` | One entry of `Scheduler.Agenda` |
+| `getScheduledCallCount(uint64 when)` | The bounded agenda length for a block |
+| `getRetry(uint64 when,uint32 index)` | `Scheduler.Retries` |
+| `getTaskAddress(bytes32 taskId)` | `Scheduler.Lookup` |
+
+Returning one agenda entry at a time keeps execution bounded and avoids an
+unbounded array result.
+
+## State-changing operations
+
+The runtime configures `Scheduler.ScheduleOrigin` as Root. Scheduler extrinsics
+therefore have no typed EVM operation: a precompile must not manufacture Root
+or bypass the top-level Scheduler authorization check.
+
+The returned agenda entry contains stable metadata rather than a SCALE-encoded
+runtime call or origin.
+
+Source: [`scheduler.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/scheduler.sol)
diff --git a/docs/guides/evm/precompiles/staking-v2.mdx b/docs/guides/evm/precompiles/staking-v2.mdx
new file mode 100644
index 0000000000..e57ed90de4
--- /dev/null
+++ b/docs/guides/evm/precompiles/staking-v2.mdx
@@ -0,0 +1,162 @@
+---
+title: Staking V2
+description: Reference for the deployed StakingPrecompileV2.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `StakingPrecompileV2` |
+| Solidity interface | `IStaking` V2 |
+| Address | `0x0000000000000000000000000000000000000805` |
+| Status | Deployed |
+
+This is the current staking interface. The V1 address remains available for
+backward compatibility.
+
+## Stake operations
+
+```text
+addStake(bytes32,uint256,uint256)
+addStakeLimit(bytes32,uint256,uint256,bool,uint256)
+removeStake(bytes32,uint256,uint256)
+removeStakeLimit(bytes32,uint256,uint256,bool,uint256)
+removeStakeFull(bytes32,uint256)
+removeStakeFullLimit(bytes32,uint256,uint256)
+moveStake(bytes32,bytes32,uint256,uint256,uint256)
+transferStake(bytes32,bytes32,uint256,uint256,uint256)
+burnAlpha(bytes32,uint256,uint256)
+```
+
+These functions are `payable`.
+
+## Stake views
+
+```text
+getStake(bytes32,bytes32,uint256)
+getStakeInfoForColdkeyAndNetuid(bytes32,uint256,bytes32[])
+getTotalColdkeyStake(bytes32)
+getTotalColdkeyStakeOnSubnet(bytes32,uint256)
+getTotalHotkeyStake(bytes32)
+getAlphaStakedValidators(bytes32,uint256)
+getTotalAlphaStaked(bytes32,uint256)
+getNominatorMinRequiredStake()
+getDefaultMinStake()
+```
+
+These functions are `view`.
+
+## Relationship and ownership views
+
+```text
+getDelegate(bytes32)
+getChildkeyTake(bytes32,uint16)
+getPendingChildKeys(bytes32,uint16)
+getChildKeys(bytes32,uint16)
+getParentKeys(bytes32,uint16)
+getPendingChildKeyCooldown()
+getTakeLimits()
+getMinChildkeyTakePerSubnet(uint16)
+getHotkeyOwner(bytes32)
+getOwnedHotkeys(bytes32)
+getAutoStakeDestination(bytes32,uint16)
+getAutoStakeDestinationColdkeys(bytes32,uint16)
+getHotkeySuccessor(bytes32,uint16)
+getHotkeyRoot(bytes32,uint16)
+getColdkeySuccessor(bytes32)
+getColdkeyRoot(bytes32)
+getColdkeySwapStatus(bytes32)
+getColdkeySwapDelays()
+getLastHotkeySwapOnSubnet(bytes32,uint16)
+```
+
+Optional relationships return an explicit `exists` flag. Child and parent
+links return typed `(proportion, account)` entries.
+
+## Accounting and collateral views
+
+```text
+getStakeAccounting()
+getMinerCollateral(uint16,bytes32,bytes32)
+getColdkeyCollateral(uint16,bytes32)
+getCollateralConfig(uint16)
+```
+
+Fixed-point collateral ratios are returned as their raw `U64F64` bits.
+
+## Locks and account policy
+
+```text
+lockStake(bytes32,uint256,uint256)
+moveLock(bytes32,uint256)
+setPerpetualLock(uint256,bool)
+setRejectLockedAlpha(bool)
+getColdkeyLock(bytes32,uint256)
+getHotkeyLock(bytes32,uint256)
+getHotkeyConvictions(uint256,bytes32[])
+getLockRates()
+getRejectLockedAlpha(bytes32)
+```
+
+The `get` functions are `view`; the other functions are `payable`.
+
+## Proxies and stake allowances
+
+```text
+addProxy(bytes32)
+removeProxy(bytes32)
+approve(address,uint256,uint256)
+allowance(address,address,uint256)
+increaseAllowance(address,uint256,uint256)
+decreaseAllowance(address,uint256,uint256)
+transferStakeFrom(address,address,bytes32,uint256,uint256,uint256)
+```
+
+`allowance` is `view`. Refer to the published ABI for the mutability and return
+encoding of the allowance mutations.
+
+## Added Subtensor operations
+
+| Function | Source extrinsic |
+|---|---|
+| `decreaseTake` | `SubtensorModule.decrease_take` |
+| `increaseTake` | `SubtensorModule.increase_take` |
+| `setChildkeyTake` | `SubtensorModule.set_childkey_take` |
+| `unstakeAll` | `SubtensorModule.unstake_all` |
+| `unstakeAllAlpha` | `SubtensorModule.unstake_all_alpha` |
+| `swapStake` | `SubtensorModule.swap_stake` |
+| `swapStakeLimit` | `SubtensorModule.swap_stake_limit` |
+| `recycleAlpha` | `SubtensorModule.recycle_alpha` |
+| `setColdkeyAutoStakeHotkey` | `SubtensorModule.set_coldkey_auto_stake_hotkey` |
+| `claimRoot` | `SubtensorModule.claim_root` |
+| `setRootClaimType` | `SubtensorModule.set_root_claim_type` |
+| `setRootClaimThreshold` | `SubtensorModule.sudo_set_root_claim_threshold` |
+| `addStakeBurn` | `SubtensorModule.add_stake_burn` |
+| `setAutoParentDelegationEnabled` | `SubtensorModule.set_auto_parent_delegation_enabled` |
+| `transferStakeAndHotkey` | `SubtensorModule.transfer_stake_and_hotkey` |
+| `addCollateral` | `SubtensorModule.add_collateral` |
+| `setMinCollateral` | `SubtensorModule.set_min_collateral` |
+
+`recycleAlpha` is distinct from deployed `burnAlpha`: recycling reduces
+`SubnetAlphaOut` and Alpha issuance, while burning does not reduce
+`SubnetAlphaOut`.
+
+## Added AdminUtils operations
+
+| Function | Source extrinsic |
+|---|---|
+| `setMinChildkeyTakePerSubnet` | `AdminUtils.sudo_set_min_childkey_take_per_subnet` |
+| `setCollateralLockShare` | `AdminUtils.sudo_set_collateral_lock_share` |
+| `setCollateralDrainRatio` | `AdminUtils.sudo_set_collateral_drain_ratio` |
+
+The listed owner-or-Root calls expose only their signed subnet-owner path.
+Every operation dispatches the highest-level extrinsic as the mapped caller so
+runtime authorization remains in force.
+
+## Proposed bulk runtime API
+
+`DelegateInfoRuntimeApi.get_delegates` remains proposed. Its current result can
+grow with chain state, so no Solidity selector is assigned in this change.
+Before implementation, it needs a bounded cursor-based or indexed interface
+whose stable return type is independent of the runtime's SCALE representation.
+
+Source: [`stakingV2.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/stakingV2.sol)
diff --git a/docs/guides/evm/precompiles/storage-query.mdx b/docs/guides/evm/precompiles/storage-query.mdx
new file mode 100644
index 0000000000..dd3636ed4d
--- /dev/null
+++ b/docs/guides/evm/precompiles/storage-query.mdx
@@ -0,0 +1,65 @@
+---
+title: Storage query
+description: Reference for the deployed selectorless StorageQueryPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `StorageQueryPrecompile` |
+| Solidity interface | None |
+| Address | `0x0000000000000000000000000000000000000807` |
+| Status | Deployed · deprecation planned |
+
+This precompile has no named Solidity functions or four-byte function selector.
+The complete call data is interpreted as a raw Substrate storage key. It returns
+the stored SCALE-encoded bytes, or empty bytes when the key does not exist.
+
+Only keys whose first 16 bytes match an authorized pallet prefix are accepted:
+SubtensorModule, Swap, Balances, Proxy, Scheduler, Drand, Crowdloan, Sudo,
+Multisig, and Timestamp.
+
+Raw storage access is brittle because callers depend on runtime storage names,
+hashers, key formats, and SCALE encodings.
+
+## Planned deprecation
+
+
+ Storage Query is still deployed and callable. Deprecation is planned, but it
+ does not begin until suitable typed replacement coverage is available.
+
+
+The planned lifecycle is:
+
+1. Add typed views for all storage currently authorized through this
+ precompile.
+2. Soft-deprecate Storage Query. Existing calls continue to execute identically
+ while the registry and documentation direct new callers to typed functions.
+3. Allow a documented migration window for existing contracts and tooling.
+4. Hard-deprecate Storage Query so calls return a descriptive precompile error.
+5. Eventually disable the precompile through the existing Root-controlled
+ precompile switch.
+
+No migration-window length or activation block has been assigned. The general
+lifecycle rules are described in
+[Precompile design and lifecycle](/docs/guides/evm/precompile-design#phasing-out-raw-storage-reads).
+
+## Replacement destinations
+
+Typed coverage should be completed at existing domain addresses whenever a
+compatible domain already exists. A new address is proposed only when no
+existing precompile has a coherent responsibility for that state.
+
+| Authorized storage prefix | Typed replacement |
+|---|---|
+| `SubtensorModule` | Extend [Staking V2](./staking-v2), [Metagraph](./metagraph), [Subnet](./subnet), [Neuron](./neuron), [Alpha](./alpha), [Leasing](./leasing), [UID lookup](./uid-lookup), [Address mapping](./address-mapping), and [Voting power](./voting-power), according to the meaning of each value. |
+| `Swap` | Extend [Alpha](./alpha) with typed liquidity, fee, balancer, reservoir, initialization, and migration-status views. |
+| `Balances` | Extend [Account balance](./account-balance) with typed account, issuance, lock, reserve, hold, and freeze views. |
+| `Proxy` | Extend [Proxy](./proxy) with typed announcement, last-call-result, and fee-payer views. |
+| `Crowdloan` | Extend [Crowdloan](./crowdloan) with typed ID, contribution-limit, current-operation, and migration-status views. |
+| `Scheduler` | Use the typed [Scheduler](./scheduler) precompile. |
+| `Drand` | Use the typed [Drand](./drand) precompile. |
+| `Sudo` | No dedicated EVM precompile is proposed; this access must be addressed explicitly before Storage Query is deprecated. |
+| `Multisig` | No dedicated EVM precompile is proposed; this access must be addressed explicitly before Storage Query is deprecated. |
+| `Timestamp` | Use the typed [Timestamp](./timestamp) precompile; `getTimestamp()` is equivalent to the existing EVM `block.timestamp` value. |
+
+Source: [`storage_query.rs`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/storage_query.rs)
diff --git a/docs/guides/evm/precompiles/subnet.mdx b/docs/guides/evm/precompiles/subnet.mdx
new file mode 100644
index 0000000000..0caa4dd9f6
--- /dev/null
+++ b/docs/guides/evm/precompiles/subnet.mdx
@@ -0,0 +1,188 @@
+---
+title: Subnet
+description: Reference for the deployed SubnetPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `SubnetPrecompile` |
+| Solidity interface | `ISubnet` |
+| Address | `0x0000000000000000000000000000000000000803` |
+| Status | Deployed |
+
+Registers subnets and exposes selected subnet configuration. State-changing
+functions are `payable`.
+
+## Registration
+
+`registerNetwork` has three overloads:
+
+```text
+registerNetwork(bytes32)
+registerNetwork(bytes32,string,string,string,string,string,string,string)
+registerNetwork(bytes32,string,string,string,string,string,string,string,string)
+```
+
+## Views
+
+```text
+getActivityCutoff(uint16)
+getActivityCutoffFactor(uint16)
+getAdjustmentAlpha(uint16)
+getAlphaSigmoidSteepness(uint16)
+getAlphaValues(uint16)
+getBondsMovingAverage(uint16)
+getBondsResetEnabled(uint16)
+getCommitRevealWeightsEnabled(uint16)
+getCommitRevealWeightsInterval(uint16)
+getDifficulty(uint16)
+getImmunityPeriod(uint16)
+getKappa(uint16)
+getLiquidAlphaEnabled(uint16)
+getMaxBurn(uint16)
+getMaxDifficulty(uint16)
+getMaxWeightLimit(uint16)
+getMinAllowedWeights(uint16)
+getMinBurn(uint16)
+getMinDifficulty(uint16)
+getNetworkPowRegistrationAllowed(uint16)
+getNetworkRegistrationAllowed(uint16)
+getNetworkRegistrationBlock(uint16)
+getRegisteredSubnetCounter(uint16)
+getOwnerCutAutoLockEnabled(uint16)
+getRho(uint16)
+getServingRateLimit(uint16)
+getWeightsSetRateLimit(uint16)
+getWeightsVersionKey(uint16)
+getYuma3Enabled(uint16)
+isSubnetDissolving(uint16)
+getSubnetDissolutionStatus(uint16)
+getSubnetMetadata(uint16)
+getSubnetCapacityConfig(uint16)
+getMechanismEmissionSplit(uint16)
+getBurnConfig(uint16)
+getGlobalNetworkLimits()
+getGlobalRateLimits()
+getGlobalProtocolConfig()
+```
+
+The grouped configuration views return stable typed fields rather than raw
+storage encodings. Fixed-point burn multipliers are returned as raw `U64F64`
+bits.
+
+`getRegisteredSubnetCounter` returns a monotonic generation number for a
+netuid. It increments on every successful registration, allowing a contract to
+distinguish a reused netuid even when it did not retain the previous
+registration block.
+
+`getSubnetDissolutionStatus` returns `(isDissolving, cleanupInProgress,
+cleanupPhase)`. Phase `0` means that detailed cleanup has not started. Active
+cleanup uses stable, append-only phase codes:
+
+| Code | Cleanup work |
+|---:|---|
+| 1 | Root claimable dividends |
+| 2 | Root claimed dividends |
+| 3 | Calculate stake value |
+| 4 | Settle stakes |
+| 5 | Clear alpha |
+| 6 | Clear hotkey totals |
+| 7 | Clear stake locks |
+| 8 | Clear decaying stake locks |
+| 9 | Finish stake cleanup |
+| 10 | Clear protocol liquidity |
+| 11 | Purge subnet commitments |
+| 12 | Clear network membership |
+| 13 | Clear network parameters |
+| 14 | Clear network maps |
+| 15 | Update root weights |
+| 16 | Clear childkey takes |
+| 17 | Clear childkeys |
+| 18 | Clear parentkeys |
+| 19 | Clear last hotkey emissions |
+| 20 | Clear last-epoch hotkey alpha |
+| 21 | Clear transaction rate-limit records |
+| 22 | Clear network locks |
+| 23 | Clear decaying network locks |
+
+The runtime does not currently store a per-subnet future dissolution block.
+The authorized dissolution calls execute dissolution immediately, while the
+generic scheduler stores calls by agenda position rather than maintaining a
+typed netuid-to-dissolution lookup. Consequently, there is no truthful,
+bounded per-subnet scheduled-block view to expose.
+
+## Configuration
+
+```text
+setActivityCutoff(uint16,uint16)
+setActivityCutoffFactor(uint16,uint32)
+setAdjustmentAlpha(uint16,uint64)
+setAlphaSigmoidSteepness(uint16,uint16)
+setAlphaValues(uint16,uint16,uint16)
+setBondsMovingAverage(uint16,uint64)
+setBondsResetEnabled(uint16,bool)
+setCommitRevealWeightsEnabled(uint16,bool)
+setCommitRevealWeightsInterval(uint16,uint64)
+setDifficulty(uint16,uint64)
+setImmunityPeriod(uint16,uint16)
+setKappa(uint16,uint16)
+setLiquidAlphaEnabled(uint16,bool)
+setMaxDifficulty(uint16,uint64)
+setMinAllowedWeights(uint16,uint16)
+setMinDifficulty(uint16,uint64)
+setNetworkPowRegistrationAllowed(uint16,bool)
+setNetworkRegistrationAllowed(uint16,bool)
+setOwnerCutAutoLockEnabled(uint16,bool)
+setRho(uint16,uint16)
+setServingRateLimit(uint16,uint64)
+setWeightsVersionKey(uint16,uint64)
+setYuma3Enabled(uint16,bool)
+toggleTransfers(uint16,bool)
+```
+
+## Legacy no-op functions
+
+These released selectors remain routed but intentionally do not change state:
+
+```text
+setWeightsSetRateLimit(uint16,uint64)
+setMinBurn(uint16,uint64)
+setMaxBurn(uint16,uint64)
+```
+
+Changing their behavior in place would break their released semantics. The
+real AdminUtils operations therefore use the V2 selectors below.
+
+## Added subnet operations
+
+| Function | Source extrinsic |
+|---|---|
+| `setSubnetIdentity` | `SubtensorModule.set_subnet_identity` |
+| `updateSubnetSymbol` | `SubtensorModule.update_symbol` |
+| `triggerEpoch` | `SubtensorModule.trigger_epoch` |
+
+## Added AdminUtils operations
+
+| Function | Source extrinsic |
+|---|---|
+| `setBondsPenalty` | `AdminUtils.sudo_set_bonds_penalty` |
+| `setMaxAllowedUids` | `AdminUtils.sudo_set_max_allowed_uids` |
+| `setMaxBurnV2` | `AdminUtils.sudo_set_max_burn` |
+| `setMechanismCount` | `AdminUtils.sudo_set_mechanism_count` |
+| `setMechanismEmissionSplit` | `AdminUtils.sudo_set_mechanism_emission_split` |
+| `setMinBurnV2` | `AdminUtils.sudo_set_min_burn` |
+| `setOwnerCutEnabled` | `AdminUtils.sudo_set_owner_cut_enabled` |
+| `setOwnerImmuneNeuronLimit` | `AdminUtils.sudo_set_owner_immune_neuron_limit` |
+| `setTempo` | `AdminUtils.sudo_set_tempo` |
+| `trimToMaxAllowedUids` | `AdminUtils.sudo_trim_to_max_allowed_uids` |
+
+Each listed AdminUtils call accepts a signed subnet owner as well as Root. The
+precompile exposes only the signed path and dispatches the highest-level
+extrinsic so owner limits, freeze windows, and other runtime checks remain in
+force. Root-only calls are classified as not EVM-callable in the
+[coverage audit](./extrinsic-coverage).
+
+These functions dispatch the listed highest-level extrinsics as the mapped
+signed caller.
+
+Source: [`subnet.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/subnet.sol)
diff --git a/docs/guides/evm/precompiles/timestamp.mdx b/docs/guides/evm/precompiles/timestamp.mdx
new file mode 100644
index 0000000000..5c78dfffee
--- /dev/null
+++ b/docs/guides/evm/precompiles/timestamp.mdx
@@ -0,0 +1,33 @@
+---
+title: Timestamp
+description: Typed EVM interface for Timestamp pallet state.
+---
+
+| Property | Value |
+|---|---|
+| Implementation | `TimestampPrecompile` |
+| Solidity interface | `ITimestamp` |
+| Address | `0x0000000000000000000000000000000000000811` |
+| Status | Deployed |
+
+## Views
+
+| Function | Replaces |
+|---|---|
+| `getTimestamp()` | `Timestamp.Now` |
+| `wasUpdatedThisBlock()` | `Timestamp.DidUpdate` |
+
+`getTimestamp()` returns the same underlying time as the EVM
+`block.timestamp` value. It exists here so every storage item authorized through
+`StorageQueryPrecompile` has an explicit typed replacement.
+`wasUpdatedThisBlock` provides the Timestamp pallet's update state without
+requiring contracts to construct a storage key or decode SCALE.
+
+`Timestamp.set` is an inherent submitted by block production, not a public
+user operation. The precompile therefore exposes no state-changing
+timestamp function.
+
+See the complete classification in
+[Extrinsic coverage](/docs/guides/evm/precompiles/extrinsic-coverage).
+
+Source: [`timestamp.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/timestamp.sol)
diff --git a/docs/guides/evm/precompiles/uid-lookup.mdx b/docs/guides/evm/precompiles/uid-lookup.mdx
new file mode 100644
index 0000000000..a34bf4a19f
--- /dev/null
+++ b/docs/guides/evm/precompiles/uid-lookup.mdx
@@ -0,0 +1,24 @@
+---
+title: UID lookup
+description: Reference for the deployed UidLookupPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `UidLookupPrecompile` |
+| Solidity interface | `IUidLookup` |
+| Address | `0x0000000000000000000000000000000000000806` |
+| Status | Deployed |
+
+## Views
+
+```text
+uidLookup(uint16,address,uint16)
+getAssociatedEvmAddress(uint16,uint16)
+```
+
+`uidLookup` returns the bounded reverse association list for an EVM address.
+`getAssociatedEvmAddress` returns the forward address and the block at which
+ownership was last proved, with an explicit `exists` flag.
+
+Source: [`uidLookup.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/uidLookup.sol)
diff --git a/docs/guides/evm/precompiles/voting-power.mdx b/docs/guides/evm/precompiles/voting-power.mdx
new file mode 100644
index 0000000000..6fda95fe61
--- /dev/null
+++ b/docs/guides/evm/precompiles/voting-power.mdx
@@ -0,0 +1,41 @@
+---
+title: Voting power
+description: Reference for the deployed VotingPowerPrecompile.
+---
+
+| Property | Value |
+|---|---|
+| Rust implementation | `VotingPowerPrecompile` |
+| Solidity interface | `IVotingPower` |
+| Address | `0x000000000000000000000000000000000000080d` |
+| Status | Deployed |
+
+All functions are `view`.
+
+## Functions
+
+```text
+getVotingPower(uint16,bytes32)
+isVotingPowerTrackingEnabled(uint16)
+getVotingPowerDisableAtBlock(uint16)
+getVotingPowerEmaAlpha(uint16)
+getTotalVotingPower(uint16)
+```
+
+`getTotalVotingPower` reads a maintained per-subnet aggregate. A one-time
+runtime migration initializes the aggregate from existing validator entries;
+normal epoch updates, removals, swaps, and tracking disablement keep it in
+sync without a precompile-side map scan.
+
+## Added operations
+
+| Function | Source extrinsic |
+|---|---|
+| `enableVotingPowerTracking` | `SubtensorModule.enable_voting_power_tracking` |
+| `disableVotingPowerTracking` | `SubtensorModule.disable_voting_power_tracking` |
+
+Both calls accept a signed subnet owner as well as Root. The precompile exposes
+only the signed path and preserves the pallet's owner checks. The Root-only EMA
+configuration call is not exposed.
+
+Source: [`votingPower.sol`](https://github.com/RaoFoundation/subtensor/blob/main/precompiles/src/solidity/votingPower.sol)
diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs
index 25877b7820..7aa15c2a75 100644
--- a/pallets/admin-utils/src/lib.rs
+++ b/pallets/admin-utils/src/lib.rs
@@ -203,6 +203,16 @@ pub mod pallet {
VotingPower,
/// Account balance precompile
AccountBalance,
+ /// Scheduler metadata precompile
+ Scheduler,
+ /// Drand metadata precompile
+ Drand,
+ /// Timestamp metadata precompile
+ Timestamp,
+ /// Global runtime configuration metadata precompile
+ RuntimeConfiguration,
+ /// Precompile lifecycle and availability registry
+ PrecompileRegistry,
}
#[pallet::type_value]
diff --git a/pallets/drand/src/lib.rs b/pallets/drand/src/lib.rs
index f92cc09236..90d50c5938 100644
--- a/pallets/drand/src/lib.rs
+++ b/pallets/drand/src/lib.rs
@@ -451,6 +451,13 @@ pub mod pallet {
}
}
+impl Pallet {
+ /// Return the block at which the next unsigned pulse submission is accepted.
+ pub fn next_unsigned_at() -> BlockNumberFor {
+ NextUnsignedAt::::get()
+ }
+}
+
impl Pallet {
/// fetch the latest public pulse from the configured drand beacon
/// then send a signed transaction to include it on-chain
diff --git a/pallets/proxy/src/lib.rs b/pallets/proxy/src/lib.rs
index 1fca855327..4e5b781555 100644
--- a/pallets/proxy/src/lib.rs
+++ b/pallets/proxy/src/lib.rs
@@ -93,6 +93,20 @@ pub struct Announcement {
height: BlockNumber,
}
+impl Announcement {
+ pub fn real(&self) -> &AccountId {
+ &self.real
+ }
+
+ pub fn call_hash(&self) -> &Hash {
+ &self.call_hash
+ }
+
+ pub fn height(&self) -> &BlockNumber {
+ &self.height
+ }
+}
+
/// The type of deposit
#[derive(
Encode,
diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs
index 3cf4318e9d..957df5ea93 100644
--- a/pallets/subtensor/src/lib.rs
+++ b/pallets/subtensor/src/lib.rs
@@ -2410,6 +2410,12 @@ pub mod pallet {
pub type VotingPower =
StorageDoubleMap<_, Identity, NetUid, Blake2_128Concat, T::AccountId, u64, ValueQuery>;
+ #[pallet::storage]
+ /// MAP ( netuid ) --> total_voting_power | Sum of all validator voting-power
+ /// entries on the subnet. Kept in sync with `VotingPower` so consumers can
+ /// read the aggregate without iterating the complete validator map.
+ pub type TotalVotingPower = StorageMap<_, Identity, NetUid, u64, ValueQuery>;
+
#[pallet::storage]
/// MAP ( netuid ) --> bool | Whether voting power tracking is enabled for this subnet.
/// When enabled, VotingPower EMA is updated every epoch. Default is false.
diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs
index 6d3692d9a2..97eb5125b8 100644
--- a/pallets/subtensor/src/macros/hooks.rs
+++ b/pallets/subtensor/src/macros/hooks.rs
@@ -183,7 +183,9 @@ mod hooks {
// Remove orphan SubnetIdentitiesV3 entries left for recycled netuids.
.saturating_add(migrations::migrate_clear_orphan_subnet_identities_v3::migrate_clear_orphan_subnet_identities_v3::())
// Backfill ColdkeyCollateralHotkeys from standing MinerCollateral rows.
- .saturating_add(migrations::migrate_coldkey_collateral_hotkeys::migrate_coldkey_collateral_hotkeys::());
+ .saturating_add(migrations::migrate_coldkey_collateral_hotkeys::migrate_coldkey_collateral_hotkeys::())
+ // Backfill the O(1) aggregate used by the voting-power precompile.
+ .saturating_add(migrations::migrate_total_voting_power::migrate_total_voting_power::());
weight
}
diff --git a/pallets/subtensor/src/migrations/migrate_total_voting_power.rs b/pallets/subtensor/src/migrations/migrate_total_voting_power.rs
new file mode 100644
index 0000000000..2fa5343846
--- /dev/null
+++ b/pallets/subtensor/src/migrations/migrate_total_voting_power.rs
@@ -0,0 +1,68 @@
+use crate::{Config, HasMigrationRun, TotalVotingPower, VotingPower};
+use alloc::collections::BTreeMap;
+use frame_support::{traits::Get, weights::Weight};
+use subtensor_runtime_common::NetUid;
+
+const MIGRATION_NAME: &[u8] = b"migrate_total_voting_power";
+
+/// Backfill the per-subnet voting-power aggregate from the existing
+/// `VotingPower` entries. The full scan happens once during the runtime
+/// upgrade; subsequent reads use `TotalVotingPower` in O(1).
+pub fn migrate_total_voting_power() -> Weight {
+ let migration_name = MIGRATION_NAME.to_vec();
+ let mut reads = 1u64;
+
+ if HasMigrationRun::::get(&migration_name) {
+ return T::DbWeight::get().reads(reads);
+ }
+
+ let mut totals = BTreeMap::::new();
+ for (netuid, _, voting_power) in VotingPower::::iter() {
+ reads = reads.saturating_add(1);
+ totals
+ .entry(netuid)
+ .and_modify(|total| *total = total.saturating_add(voting_power))
+ .or_insert(voting_power);
+ }
+
+ let mut writes = 1u64;
+ for (netuid, total) in totals {
+ TotalVotingPower::::insert(netuid, total);
+ writes = writes.saturating_add(1);
+ }
+
+ HasMigrationRun::::insert(&migration_name, true);
+ T::DbWeight::get().reads_writes(reads, writes)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{tests::mock::*, *};
+ use sp_core::U256;
+
+ #[test]
+ fn migration_backfills_total_voting_power_once() {
+ new_test_ext(1).execute_with(|| {
+ let first_netuid = NetUid::from(1);
+ let second_netuid = NetUid::from(2);
+ VotingPower::::insert(first_netuid, U256::from(1), 10);
+ VotingPower::::insert(first_netuid, U256::from(2), 20);
+ VotingPower::::insert(second_netuid, U256::from(3), 7);
+
+ let weight = migrate_total_voting_power::();
+
+ assert_eq!(TotalVotingPower::::get(first_netuid), 30);
+ assert_eq!(TotalVotingPower::::get(second_netuid), 7);
+ assert!(HasMigrationRun::::get(MIGRATION_NAME.to_vec()));
+ assert_eq!(
+ weight,
+ ::DbWeight::get().reads_writes(4, 3)
+ );
+
+ VotingPower::::insert(first_netuid, U256::from(4), 100);
+ migrate_total_voting_power::();
+ assert_eq!(TotalVotingPower::::get(first_netuid), 30);
+ });
+ }
+}
diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs
index 63a7ec4439..2cf4955e10 100644
--- a/pallets/subtensor/src/migrations/mod.rs
+++ b/pallets/subtensor/src/migrations/mod.rs
@@ -73,6 +73,7 @@ pub mod migrate_subnet_volume;
pub mod migrate_tao_in_refund_deployment_block;
pub mod migrate_to_v1_separate_emission;
pub mod migrate_to_v2_fixed_total_stake;
+pub mod migrate_total_voting_power;
pub mod migrate_transfer_ownership_to_foundation;
pub mod migrate_upgrade_revealed_commitments;
diff --git a/pallets/subtensor/src/tests/voting_power.rs b/pallets/subtensor/src/tests/voting_power.rs
index 9af3639b99..8a8f8c0664 100644
--- a/pallets/subtensor/src/tests/voting_power.rs
+++ b/pallets/subtensor/src/tests/voting_power.rs
@@ -354,6 +354,42 @@ fn test_voting_power_ema_calculation() {
});
}
+#[test]
+fn test_total_voting_power_tracks_updates_removals_and_swaps() {
+ new_test_ext(1).execute_with(|| {
+ let f = VotingPowerTestFixture::new();
+ f.setup_full();
+ f.run_epochs(1);
+
+ let voting_power = f.get_voting_power();
+ assert!(voting_power > 0);
+ assert_eq!(TotalVotingPower::::get(f.netuid), voting_power);
+
+ let replacement = U256::from(99);
+ SubtensorModule::swap_voting_power_for_hotkey(&f.hotkey, &replacement, f.netuid);
+ assert_eq!(
+ VotingPower::::get(f.netuid, replacement),
+ voting_power
+ );
+ assert_eq!(TotalVotingPower::::get(f.netuid), voting_power);
+
+ ValidatorPermit::::insert(f.netuid, vec![false]);
+ let mut output = BTreeMap::new();
+ output.insert(
+ replacement,
+ EpochTerms {
+ uid: 0,
+ new_validator_permit: false,
+ ..Default::default()
+ },
+ );
+ SubtensorModule::update_voting_power_for_subnet(f.netuid, &output);
+
+ assert_eq!(VotingPower::::get(f.netuid, replacement), 0);
+ assert_eq!(TotalVotingPower::::get(f.netuid), 0);
+ });
+}
+
// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::voting_power::test_voting_power_cleared_when_deregistered --exact --nocapture
#[test]
fn test_voting_power_cleared_when_deregistered() {
diff --git a/pallets/subtensor/src/utils/voting_power.rs b/pallets/subtensor/src/utils/voting_power.rs
index 11d8880b97..6af558c071 100644
--- a/pallets/subtensor/src/utils/voting_power.rs
+++ b/pallets/subtensor/src/utils/voting_power.rs
@@ -37,6 +37,11 @@ impl Pallet {
VotingPowerEmaAlpha::::get(netuid)
}
+ /// Get the maintained sum of voting power for all validators on a subnet.
+ pub fn get_total_voting_power(netuid: NetUid) -> u64 {
+ TotalVotingPower::::get(netuid)
+ }
+
// ========================
// === Extrinsic Handlers ===
// ========================
@@ -148,7 +153,7 @@ impl Pallet {
Self::update_voting_power_for_hotkey(netuid, hotkey, terms.stake, alpha, min_stake);
} else {
// Miner without vpermit - remove any existing voting power
- VotingPower::::remove(netuid, hotkey);
+ Self::remove_voting_power(netuid, hotkey);
}
}
@@ -174,7 +179,7 @@ impl Pallet {
// Remove voting power for deregistered hotkeys
for hotkey in hotkeys_to_remove {
- VotingPower::::remove(netuid, &hotkey);
+ Self::remove_voting_power(netuid, &hotkey);
log::trace!(
"VotingPower removed for deregistered hotkey {hotkey:?} on netuid {netuid:?}"
);
@@ -201,13 +206,13 @@ impl Pallet {
// This allows new validators to build up voting power from 0 without being removed.
if new_ema < min_stake && previous_ema >= min_stake {
// Was above threshold, now decayed below - remove
- VotingPower::::remove(netuid, hotkey);
+ Self::remove_voting_power(netuid, hotkey);
log::trace!(
"VotingPower removed for hotkey {hotkey:?} on netuid {netuid:?} (decayed below removal threshold: {new_ema:?} < {min_stake:?})"
);
} else if new_ema > 0 {
// Update voting power (building up or maintaining)
- VotingPower::::insert(netuid, hotkey, new_ema);
+ Self::set_voting_power(netuid, hotkey, previous_ema, new_ema);
log::trace!(
"VotingPower updated for hotkey {hotkey:?} on netuid {netuid:?}: {previous_ema:?} -> {new_ema:?}"
);
@@ -238,11 +243,39 @@ impl Pallet {
result.min(u64::MAX as u128) as u64
}
+ /// Store one validator's voting power and update the subnet aggregate by
+ /// the same delta.
+ fn set_voting_power(
+ netuid: NetUid,
+ hotkey: &T::AccountId,
+ previous_voting_power: u64,
+ new_voting_power: u64,
+ ) {
+ VotingPower::::insert(netuid, hotkey, new_voting_power);
+ TotalVotingPower::::mutate(netuid, |total| {
+ *total = total
+ .saturating_sub(previous_voting_power)
+ .saturating_add(new_voting_power);
+ });
+ }
+
+ /// Remove one validator's voting power and subtract it from the subnet
+ /// aggregate.
+ fn remove_voting_power(netuid: NetUid, hotkey: &T::AccountId) {
+ let removed = VotingPower::::take(netuid, hotkey);
+ if removed > 0 {
+ TotalVotingPower::::mutate(netuid, |total| {
+ *total = total.saturating_sub(removed);
+ });
+ }
+ }
+
/// Finalize the disabling of voting power tracking.
/// Clears all VotingPower entries for the subnet.
fn finalize_voting_power_disable(netuid: NetUid) {
// Clear all VotingPower entries for this subnet
let _ = VotingPower::::clear_prefix(netuid, u32::MAX, None);
+ TotalVotingPower::::remove(netuid);
// Disable tracking
VotingPowerTrackingEnabled::::insert(netuid, false);
diff --git a/precompiles/Cargo.toml b/precompiles/Cargo.toml
index 4c1b924ed9..8f2cd1f55f 100644
--- a/precompiles/Cargo.toml
+++ b/precompiles/Cargo.toml
@@ -39,7 +39,11 @@ pallet-subtensor-swap.workspace = true
pallet-admin-utils.workspace = true
subtensor-swap-interface.workspace = true
pallet-crowdloan.workspace = true
+pallet-drand.workspace = true
+pallet-evm-chain-id.workspace = true
pallet-shield.workspace = true
+pallet-scheduler.workspace = true
+pallet-timestamp.workspace = true
[lints]
workspace = true
@@ -103,9 +107,5 @@ runtime-benchmarks = [
]
[dev-dependencies]
-pallet-drand = { workspace = true, features = ["std"] }
-pallet-evm-chain-id = { workspace = true, features = ["std"] }
pallet-preimage = { workspace = true, features = ["std"] }
-pallet-scheduler = { workspace = true, features = ["std"] }
-pallet-timestamp = { workspace = true, features = ["std"] }
precompile-utils = { workspace = true, features = ["std", "testing"] }
diff --git a/precompiles/src/alpha.rs b/precompiles/src/alpha.rs
index 9840c42575..62b70272d4 100644
--- a/precompiles/src/alpha.rs
+++ b/precompiles/src/alpha.rs
@@ -2,9 +2,18 @@ use core::marker::PhantomData;
use crate::PrecompileExt;
use fp_evm::{ExitError, PrecompileFailure};
-use pallet_evm::{BalanceConverter, PrecompileHandle, SubstrateBalance};
-use precompile_utils::EvmResult;
-use sp_runtime::{SaturatedConversion, Vec};
+use frame_support::{
+ BoundedVec,
+ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
+ traits::{ConstU32, IsSubType},
+};
+use frame_system::RawOrigin;
+use pallet_evm::{AddressMapping, BalanceConverter, PrecompileHandle, SubstrateBalance};
+use precompile_utils::{EvmResult, prelude::BoundedBytes};
+use sp_runtime::{
+ SaturatedConversion, Vec,
+ traits::{AsSystemOriginSigner, Dispatchable},
+};
use crate::PrecompileHandleExt;
use sp_core::U256;
@@ -18,8 +27,24 @@ where
R: frame_system::Config
+ pallet_subtensor::Config
+ pallet_subtensor_swap::Config
- + pallet_evm::Config,
+ + pallet_evm::Config
+ + pallet_admin_utils::Config
+ + pallet_balances::Config
+ + pallet_shield::Config
+ + pallet_subtensor_proxy::Config
+ + Send
+ + Sync
+ + scale_info::TypeInfo,
R::AccountId: From<[u8; 32]>,
+ ::RuntimeOrigin: AsSystemOriginSigner + Clone,
+ ::RuntimeCall: From>
+ + GetDispatchInfo
+ + Dispatchable
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>,
+ ::AddressMapping: AddressMapping,
{
const INDEX: u64 = 2056;
}
@@ -30,7 +55,24 @@ where
R: frame_system::Config
+ pallet_subtensor::Config
+ pallet_subtensor_swap::Config
- + pallet_evm::Config,
+ + pallet_evm::Config
+ + pallet_admin_utils::Config
+ + pallet_balances::Config
+ + pallet_shield::Config
+ + pallet_subtensor_proxy::Config
+ + Send
+ + Sync
+ + scale_info::TypeInfo,
+ R::AccountId: From<[u8; 32]>,
+ ::RuntimeOrigin: AsSystemOriginSigner + Clone,
+ ::RuntimeCall: From>
+ + GetDispatchInfo
+ + Dispatchable
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>,
+ ::AddressMapping: AddressMapping,
{
#[precompile::public("getAlphaPrice(uint16)")]
#[precompile::view]
@@ -251,6 +293,183 @@ where
Ok(price_eth)
}
+
+ #[precompile::public("setRecycleOrBurn(uint16,uint8)")]
+ fn set_recycle_or_burn(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ mode: u8,
+ ) -> EvmResult<()> {
+ let recycle_or_burn = match mode {
+ 0 => pallet_subtensor::RecycleOrBurnEnum::Burn,
+ 1 => pallet_subtensor::RecycleOrBurnEnum::Recycle,
+ _ => {
+ return Err(PrecompileFailure::Error {
+ exit_status: ExitError::Other("invalid recycle-or-burn mode".into()),
+ });
+ }
+ };
+ let caller = handle.caller_account_id::();
+ let call = pallet_admin_utils::Call::::sudo_set_recycle_or_burn {
+ netuid: NetUid::from(netuid),
+ recycle_or_burn,
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
+
+ #[precompile::public("setBurnHalfLife(uint16,uint16)")]
+ fn set_burn_half_life(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ burn_half_life: u16,
+ ) -> EvmResult<()> {
+ let caller = handle.caller_account_id::();
+ let call = pallet_admin_utils::Call::::sudo_set_burn_half_life {
+ netuid: NetUid::from(netuid),
+ burn_half_life,
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
+
+ #[precompile::public("setBurnIncreaseMultiplier(uint16,uint128)")]
+ fn set_burn_increase_multiplier(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ raw_multiplier: u128,
+ ) -> EvmResult<()> {
+ let caller = handle.caller_account_id::();
+ let call = pallet_admin_utils::Call::::sudo_set_burn_increase_mult {
+ netuid: NetUid::from(netuid),
+ burn_increase_mult: U64F64::from_bits(raw_multiplier),
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
+
+ #[precompile::public("getEmissionAccounting(uint16,bytes32)")]
+ #[precompile::view]
+ fn get_emission_accounting(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ hotkey: sp_core::H256,
+ ) -> EvmResult<(u64, u64, u64, u64, u64, u64, u64, u128, u64)> {
+ handle.record_db_reads::(9)?;
+ let netuid = NetUid::from(netuid);
+ let hotkey = R::AccountId::from(hotkey.0);
+ Ok((
+ pallet_subtensor::AlphaDividendsPerSubnet::::get(netuid, &hotkey).to_u64(),
+ pallet_subtensor::RootAlphaDividendsPerSubnet::::get(netuid, &hotkey).to_u64(),
+ pallet_subtensor::LastHotkeyEmissionOnNetuid::::get(&hotkey, netuid).to_u64(),
+ pallet_subtensor::PendingServerEmission::::get(netuid).to_u64(),
+ pallet_subtensor::PendingValidatorEmission::::get(netuid).to_u64(),
+ pallet_subtensor::PendingRootAlphaDivs::::get(netuid).to_u64(),
+ pallet_subtensor::PendingOwnerCut::::get(netuid).to_u64(),
+ pallet_subtensor::MinerBurned::::get(netuid).to_bits(),
+ pallet_subtensor::RAORecycledForRegistration::::get(netuid).to_u64(),
+ ))
+ }
+
+ #[precompile::public("getSubnetEconomicState(uint16)")]
+ #[precompile::view]
+ fn get_subnet_economic_state(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ ) -> EvmResult<(bool, u128, u64, u64, u64)> {
+ handle.record_db_reads::(5)?;
+ let netuid = NetUid::from(netuid);
+ Ok((
+ pallet_subtensor::SubnetEmissionEnabled::::get(netuid),
+ pallet_subtensor::RootProp::::get(netuid).to_bits(),
+ pallet_subtensor::SubnetExcessTao::::get(netuid).to_u64(),
+ pallet_subtensor::SubnetRootSellTao::::get(netuid).to_u64(),
+ pallet_subtensor::SubnetProtocolAlpha::::get(netuid).to_u64(),
+ ))
+ }
+
+ #[precompile::public("getSubnetFlowState(uint16)")]
+ #[precompile::view]
+ fn get_subnet_flow_state(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ ) -> EvmResult<(U256, bool, u64, U256, U256, bool, u64, U256)> {
+ handle.record_db_reads::(4)?;
+ let netuid = NetUid::from(netuid);
+ let tao_ema = pallet_subtensor::SubnetEmaTaoFlow::::get(netuid);
+ let protocol_ema = pallet_subtensor::SubnetEmaProtocolFlow::::get(netuid);
+ Ok((
+ signed_i64_word(pallet_subtensor::SubnetTaoFlow::::get(netuid)),
+ tao_ema.is_some(),
+ tao_ema.map(|(block, _)| block).unwrap_or(0),
+ signed_i128_word(tao_ema.map(|(_, value)| value.to_bits()).unwrap_or(0)),
+ signed_i64_word(pallet_subtensor::SubnetProtocolFlow::::get(netuid)),
+ protocol_ema.is_some(),
+ protocol_ema.map(|(block, _)| block).unwrap_or(0),
+ signed_i128_word(protocol_ema.map(|(_, value)| value.to_bits()).unwrap_or(0)),
+ ))
+ }
+
+ #[precompile::public("getEmissionGateConfig()")]
+ #[precompile::view]
+ fn get_emission_gate_config(
+ handle: &mut impl PrecompileHandle,
+ ) -> EvmResult<(u64, U256, bool, U256, u128, u128, u128, u128, u64)> {
+ handle.record_db_reads::(9)?;
+ Ok((
+ #[allow(deprecated)]
+ pallet_subtensor::BlockEmission::::get(),
+ signed_i128_word(pallet_subtensor::SubnetMovingAlpha::::get().to_bits()),
+ pallet_subtensor::NetTaoFlowEnabled::::get(),
+ signed_i128_word(pallet_subtensor::TaoFlowCutoff::::get().to_bits()),
+ pallet_subtensor::FlowNormExponent::::get().to_bits(),
+ pallet_subtensor::EmissionBarQuantile::::get().to_bits(),
+ pallet_subtensor::EmissionGateExponent::::get().to_bits(),
+ pallet_subtensor::EmissionGateBar::::get().to_bits(),
+ pallet_subtensor::FlowEmaSmoothingFactor::::get(),
+ ))
+ }
+
+ #[precompile::public("getSwapState(uint16)")]
+ #[precompile::view]
+ fn get_swap_state(
+ handle: &mut impl PrecompileHandle,
+ netuid: u16,
+ ) -> EvmResult<(u16, bool, u64, u64, u64)> {
+ handle.record_db_reads::(5)?;
+ let netuid = NetUid::from(netuid);
+ Ok((
+ pallet_subtensor_swap::FeeRate::::get(netuid),
+ pallet_subtensor_swap::PalSwapInitialized::::get(netuid),
+ pallet_subtensor_swap::SwapBalancer::::get(netuid)
+ .get_quote_weight()
+ .deconstruct(),
+ pallet_subtensor_swap::BalancerTaoReservoir::::get(netuid).to_u64(),
+ pallet_subtensor_swap::BalancerAlphaReservoir::::get(netuid).to_u64(),
+ ))
+ }
+
+ #[precompile::public("hasSwapMigrationRun(bytes)")]
+ #[precompile::view]
+ fn has_swap_migration_run(
+ handle: &mut impl PrecompileHandle,
+ migration_name: BoundedBytes>,
+ ) -> EvmResult {
+ handle.record_db_reads::(1)?;
+ let migration_name = BoundedVec::>::truncate_from(migration_name.into());
+ Ok(pallet_subtensor_swap::HasMigrationRun::::get(
+ migration_name,
+ ))
+ }
+}
+
+fn signed_i64_word(value: i64) -> U256 {
+ let mut encoded = [if value.is_negative() { 0xff } else { 0 }; 32];
+ encoded[24..].copy_from_slice(&value.to_be_bytes());
+ U256::from_big_endian(&encoded)
+}
+
+fn signed_i128_word(value: i128) -> U256 {
+ let mut encoded = [if value.is_negative() { 0xff } else { 0 }; 32];
+ encoded[16..].copy_from_slice(&value.to_be_bytes());
+ U256::from_big_endian(&encoded)
}
#[cfg(test)]
@@ -613,4 +832,135 @@ mod tests {
);
});
}
+
+ #[test]
+ fn alpha_state_views_return_typed_runtime_state() {
+ new_test_ext().execute_with(|| {
+ let precompiles = precompiles::>();
+ let caller = addr_from_index(1);
+ let address = addr_from_index(AlphaPrecompile::::INDEX);
+ let netuid = NetUid::from(DYNAMIC_NETUID_U16);
+ let hotkey = sp_core::H256::repeat_byte(0x41);
+
+ assert_view(
+ &precompiles,
+ caller,
+ address,
+ "getEmissionAccounting(uint16,bytes32)",
+ (DYNAMIC_NETUID_U16, hotkey),
+ (
+ 0_u64, 0_u64, 0_u64, 0_u64, 0_u64, 0_u64, 0_u64, 0_u128, 0_u64,
+ ),
+ );
+
+ assert_view(
+ &precompiles,
+ caller,
+ address,
+ "getSubnetEconomicState(uint16)",
+ (DYNAMIC_NETUID_U16,),
+ (
+ pallet_subtensor::SubnetEmissionEnabled::::get(netuid),
+ pallet_subtensor::RootProp::::get(netuid).to_bits(),
+ pallet_subtensor::SubnetExcessTao::::get(netuid).to_u64(),
+ pallet_subtensor::SubnetRootSellTao::::get(netuid).to_u64(),
+ pallet_subtensor::SubnetProtocolAlpha::::get(netuid).to_u64(),
+ ),
+ );
+
+ let tao_ema = pallet_subtensor::SubnetEmaTaoFlow::::get(netuid);
+ let protocol_ema = pallet_subtensor::SubnetEmaProtocolFlow::::get(netuid);
+ assert_view(
+ &precompiles,
+ caller,
+ address,
+ "getSubnetFlowState(uint16)",
+ (DYNAMIC_NETUID_U16,),
+ (
+ signed_i64_word(pallet_subtensor::SubnetTaoFlow::::get(netuid)),
+ tao_ema.is_some(),
+ tao_ema.map(|(block, _)| block).unwrap_or(0),
+ signed_i128_word(tao_ema.map(|(_, value)| value.to_bits()).unwrap_or(0)),
+ signed_i64_word(pallet_subtensor::SubnetProtocolFlow::::get(netuid)),
+ protocol_ema.is_some(),
+ protocol_ema.map(|(block, _)| block).unwrap_or(0),
+ signed_i128_word(protocol_ema.map(|(_, value)| value.to_bits()).unwrap_or(0)),
+ ),
+ );
+
+ #[allow(deprecated)]
+ let emission_gate = (
+ pallet_subtensor::BlockEmission::::get(),
+ signed_i128_word(pallet_subtensor::SubnetMovingAlpha::::get().to_bits()),
+ pallet_subtensor::NetTaoFlowEnabled::::get(),
+ signed_i128_word(pallet_subtensor::TaoFlowCutoff::::get().to_bits()),
+ pallet_subtensor::FlowNormExponent::::get().to_bits(),
+ pallet_subtensor::EmissionBarQuantile::::get().to_bits(),
+ pallet_subtensor::EmissionGateExponent::::get().to_bits(),
+ pallet_subtensor::EmissionGateBar::::get().to_bits(),
+ pallet_subtensor::FlowEmaSmoothingFactor::::get(),
+ );
+ assert_view(
+ &precompiles,
+ caller,
+ address,
+ "getEmissionGateConfig()",
+ (),
+ emission_gate,
+ );
+
+ let balancer = pallet_subtensor_swap::SwapBalancer::::get(netuid);
+ assert_view(
+ &precompiles,
+ caller,
+ address,
+ "getSwapState(uint16)",
+ (DYNAMIC_NETUID_U16,),
+ (
+ pallet_subtensor_swap::FeeRate::::get(netuid),
+ pallet_subtensor_swap::PalSwapInitialized::::get(netuid),
+ balancer.get_quote_weight().deconstruct(),
+ pallet_subtensor_swap::BalancerTaoReservoir::::get(netuid).to_u64(),
+ pallet_subtensor_swap::BalancerAlphaReservoir::::get(netuid).to_u64(),
+ ),
+ );
+
+ let migration_name = b"reader-test".to_vec();
+ pallet_subtensor_swap::HasMigrationRun::::insert(
+ BoundedVec::truncate_from(migration_name.clone()),
+ true,
+ );
+ assert_view(
+ &precompiles,
+ caller,
+ address,
+ "hasSwapMigrationRun(bytes)",
+ (BoundedBytes::>::from(migration_name),),
+ true,
+ );
+ });
+ }
+
+ fn assert_view(
+ precompiles: &impl pallet_evm::PrecompileSet,
+ caller: sp_core::H160,
+ address: sp_core::H160,
+ signature: &str,
+ args: Args,
+ expected: Output,
+ ) where
+ Args: precompile_utils::solidity::Codec,
+ Output: precompile_utils::solidity::Codec,
+ {
+ use precompile_utils::testing::PrecompileTesterExt;
+
+ precompiles
+ .prepare_test(
+ caller,
+ address,
+ encode_with_selector(selector_u32(signature), args),
+ )
+ .with_static_call(true)
+ .execute_returns(expected);
+ }
}
diff --git a/precompiles/src/balance.rs b/precompiles/src/balance.rs
index 36b80489e3..af990b40ad 100644
--- a/precompiles/src/balance.rs
+++ b/precompiles/src/balance.rs
@@ -1,8 +1,14 @@
use core::marker::PhantomData;
-use pallet_evm::PrecompileHandle;
-use precompile_utils::EvmResult;
+use frame_support::{
+ dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
+ traits::{ConstU32, IsSubType},
+};
+use frame_system::RawOrigin;
+use pallet_evm::{AddressMapping, PrecompileHandle};
+use precompile_utils::{EvmResult, prelude::BoundedVec};
use sp_core::{H256, U256};
+use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable};
use crate::PrecompileExt;
use crate::PrecompileHandleExt;
@@ -11,9 +17,26 @@ pub struct BalancePrecompile(PhantomData);
impl PrecompileExt for BalancePrecompile
where
- R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
+ R: frame_system::Config
+ + pallet_balances::Config
+ + pallet_evm::Config
+ + pallet_subtensor::Config
+ + pallet_shield::Config
+ + pallet_subtensor_proxy::Config
+ + Send
+ + Sync
+ + scale_info::TypeInfo,
R::AccountId: From<[u8; 32]>,
- ::Balance: Into,
+ ::RuntimeOrigin: AsSystemOriginSigner + Clone,
+ ::RuntimeCall: From>
+ + GetDispatchInfo
+ + Dispatchable
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>,
+ ::Balance: Into + TryFrom,
+ ::AddressMapping: AddressMapping,
{
const INDEX: u64 = 2062;
}
@@ -21,9 +44,26 @@ where
#[precompile_utils::precompile]
impl BalancePrecompile
where
- R: frame_system::Config + pallet_balances::Config + pallet_evm::Config,
+ R: frame_system::Config
+ + pallet_balances::Config
+ + pallet_evm::Config
+ + pallet_subtensor::Config
+ + pallet_shield::Config
+ + pallet_subtensor_proxy::Config
+ + Send
+ + Sync
+ + scale_info::TypeInfo,
R::AccountId: From<[u8; 32]>,
- ::Balance: Into,
+ ::RuntimeOrigin: AsSystemOriginSigner + Clone,
+ ::RuntimeCall: From>
+ + GetDispatchInfo
+ + Dispatchable
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>
+ + IsSubType>,
+ ::Balance: Into + TryFrom,
+ ::AddressMapping: AddressMapping,
{
#[precompile::public("getFreeBalance(bytes32)")]
#[precompile::view]
@@ -32,6 +72,47 @@ where
let coldkey = R::AccountId::from(coldkey.0);
Ok(pallet_balances::Pallet::::free_balance(&coldkey).into())
}
+
+ #[precompile::public("getTotalIssuance()")]
+ #[precompile::view]
+ fn get_total_issuance(handle: &mut impl PrecompileHandle) -> EvmResult {
+ handle.record_db_reads::(1)?;
+ Ok(pallet_balances::Pallet::::total_issuance().into())
+ }
+
+ #[precompile::public("burnBalance(uint256,bool)")]
+ fn burn_balance(
+ handle: &mut impl PrecompileHandle,
+ amount: U256,
+ keep_alive: bool,
+ ) -> EvmResult<()> {
+ let caller = handle.caller_account_id::();
+ let call = pallet_balances::Call::::burn {
+ value: amount
+ .try_into()
+ .map_err(|_| fp_evm::PrecompileFailure::Error {
+ exit_status: fp_evm::ExitError::Other(
+ "balance amount does not fit runtime".into(),
+ ),
+ })?,
+ keep_alive,
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
+
+ #[precompile::public("upgradeAccounts(bytes32[])")]
+ fn upgrade_accounts(
+ handle: &mut impl PrecompileHandle,
+ accounts: BoundedVec>,
+ ) -> EvmResult<()> {
+ let caller = handle.caller_account_id::();
+ let who = Vec::::from(accounts)
+ .into_iter()
+ .map(|account| R::AccountId::from(account.0))
+ .collect();
+ let call = pallet_balances::Call::::upgrade_accounts { who };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
}
#[cfg(test)]
@@ -87,6 +168,17 @@ mod tests {
.with_static_call(true)
.expect_cost(RuntimeHelper::::db_read_gas_cost())
.execute_returns_raw(abi_word(U256::from(amount)));
+
+ let total_issuance: U256 = pallet_balances::Pallet::::total_issuance().into();
+ precompiles::>()
+ .prepare_test(
+ caller,
+ addr_from_index(BalancePrecompile::::INDEX),
+ selector_u32("getTotalIssuance()").to_be_bytes().to_vec(),
+ )
+ .with_static_call(true)
+ .expect_cost(RuntimeHelper::::db_read_gas_cost())
+ .execute_returns(total_issuance);
});
}
diff --git a/precompiles/src/balance_transfer.rs b/precompiles/src/balance_transfer.rs
index d8d10970a3..5bf66af47c 100644
--- a/precompiles/src/balance_transfer.rs
+++ b/precompiles/src/balance_transfer.rs
@@ -3,7 +3,7 @@ use core::marker::PhantomData;
use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo};
use frame_support::traits::IsSubType;
use frame_system::RawOrigin;
-use pallet_evm::PrecompileHandle;
+use pallet_evm::{AddressMapping, PrecompileHandle};
use precompile_utils::EvmResult;
use sp_core::{H256, U256};
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, StaticLookup, UniqueSaturatedInto};
@@ -36,6 +36,7 @@ where
+ Dispatchable,
<::Lookup as StaticLookup>::Source: From,
::Balance: TryFrom,
+ ::AddressMapping: AddressMapping,
{
const INDEX: u64 = 2048;
}
@@ -65,6 +66,7 @@ where
+ Dispatchable,
<::Lookup as StaticLookup>::Source: From,
::Balance: TryFrom,
+ ::AddressMapping: AddressMapping,
{
#[precompile::public("transfer(bytes32)")]
#[precompile::payable]
@@ -84,4 +86,76 @@ where
handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(Self::account_id()))
}
+
+ #[precompile::public("transferKeepAlive(bytes32,uint256)")]
+ fn transfer_keep_alive(
+ handle: &mut impl PrecompileHandle,
+ address: H256,
+ amount: U256,
+ ) -> EvmResult<()> {
+ let caller = handle.caller_account_id::();
+ let call = pallet_balances::Call::::transfer_keep_alive {
+ dest: R::AccountId::from(address.0).into(),
+ value: amount
+ .try_into()
+ .map_err(|_| fp_evm::PrecompileFailure::Error {
+ exit_status: fp_evm::ExitError::Other(
+ "balance amount does not fit runtime".into(),
+ ),
+ })?,
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
+
+ #[precompile::public("transferAll(bytes32,bool)")]
+ fn transfer_all(
+ handle: &mut impl PrecompileHandle,
+ address: H256,
+ keep_alive: bool,
+ ) -> EvmResult<()> {
+ let caller = handle.caller_account_id::();
+ let call = pallet_balances::Call::::transfer_all {
+ dest: R::AccountId::from(address.0).into(),
+ keep_alive,
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(caller))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::mock::{
+ AccountId, Runtime, addr_from_index, fund_account, mapped_account, new_test_ext,
+ precompiles, selector_u32,
+ };
+ use precompile_utils::{solidity::encode_with_selector, testing::PrecompileTesterExt};
+
+ #[test]
+ fn transfer_keep_alive_dispatches_as_mapped_caller() {
+ new_test_ext().execute_with(|| {
+ let caller = addr_from_index(0x8100);
+ let caller_account = mapped_account(caller);
+ let destination = RUNTIME_DESTINATION;
+ fund_account(&caller_account, 1_000);
+
+ precompiles::>()
+ .prepare_test(
+ caller,
+ addr_from_index(BalanceTransferPrecompile::::INDEX),
+ encode_with_selector(
+ selector_u32("transferKeepAlive(bytes32,uint256)"),
+ (destination, U256::from(100u64)),
+ ),
+ )
+ .execute_returns(());
+
+ assert_eq!(
+ pallet_balances::Pallet::::free_balance(AccountId::from(destination.0)),
+ 100u64.into()
+ );
+ });
+ }
+
+ const RUNTIME_DESTINATION: H256 = H256([0x44; 32]);
}
diff --git a/precompiles/src/crowdloan.rs b/precompiles/src/crowdloan.rs
index 1c66d941ca..a5152d14c6 100644
--- a/precompiles/src/crowdloan.rs
+++ b/precompiles/src/crowdloan.rs
@@ -244,6 +244,21 @@ where
handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id))
}
+
+ #[precompile::public("setMaxContribution(uint32,bool,uint64)")]
+ fn set_max_contribution(
+ handle: &mut impl PrecompileHandle,
+ crowdloan_id: u32,
+ has_max_contribution: bool,
+ max_contribution: u64,
+ ) -> EvmResult<()> {
+ let account_id = handle.caller_account_id::();
+ let call = pallet_crowdloan::Call::::set_max_contribution {
+ crowdloan_id,
+ new_max_contribution: has_max_contribution.then_some(max_contribution.into()),
+ };
+ handle.try_dispatch_runtime_call::(call, RawOrigin::Signed(account_id))
+ }
}
#[derive(Codec)]
diff --git a/precompiles/src/drand.rs b/precompiles/src/drand.rs
new file mode 100644
index 0000000000..ee98d5e821
--- /dev/null
+++ b/precompiles/src/drand.rs
@@ -0,0 +1,185 @@
+use core::marker::PhantomData;
+
+use alloc::vec::Vec;
+use fp_evm::{ExitError, PrecompileFailure};
+use frame_support::BoundedVec;
+use pallet_evm::PrecompileHandle;
+use precompile_utils::{
+ EvmResult,
+ prelude::{BoundedBytes, UnboundedBytes},
+};
+use sp_core::ConstU32;
+
+use crate::{PrecompileExt, PrecompileHandleExt};
+
+type BeaconConfiguration = (
+ UnboundedBytes,
+ u32,
+ u32,
+ UnboundedBytes,
+ UnboundedBytes,
+ UnboundedBytes,
+ UnboundedBytes,
+);
+
+pub struct DrandPrecompile(PhantomData);
+
+impl PrecompileExt for DrandPrecompile
+where
+ R: frame_system::Config + pallet_evm::Config + pallet_drand::Config,
+ R::AccountId: From<[u8; 32]>,
+ frame_system::pallet_prelude::BlockNumberFor: TryInto,
+{
+ const INDEX: u64 = 2064;
+}
+
+#[precompile_utils::precompile]
+impl DrandPrecompile