Guides/EVM
Read chain state
The metagraph, alpha, and uid-lookup precompiles — free view calls from the CLI, Python, and Solidity.
Everything a subnet dashboard needs — stake, incentive, axon endpoints, pool
prices — is exposed to the EVM through read-only precompiles. View calls go
through eth_call: no key, no gas, no setup. This walkthrough queries subnet
1 on mainnet three ways: btcli evm call, the Python SDK, and from a
contract.
If you just want the data (not the EVM), the native reads are richer and one
call: metagraph, alpha-price.
The precompiles matter when the consumer is EVM code — a contract, an
ethers app, an indexer speaking JSON-RPC.
Discover what's callable
The catalog and per-precompile function listings come from the CLI:
btcli evm precompiles # every precompile: name, address, description
btcli evm call metagraph # its functions, inputs, outputs, mutability
btcli evm abi metagraph # address + ABI JSON for ethers/viem/HardhatThe metagraph precompile
metagraph (address 0x…0802) answers per-neuron questions keyed by
(netuid, uid):
btcli evm call metagraph getUidCount 1 # neurons on subnet 1
btcli evm call metagraph getHotkey 1 0 # uid 0's hotkey (bytes32 pubkey)
btcli evm call metagraph getStake 1 0 # uid 0's stake, in rao
btcli evm call metagraph getIncentive 1 0 # u16-normalized (0–65535)
btcli evm call metagraph getAxon 1 0 # (block, version, ip, port, ip_type, protocol)
btcli evm call metagraph getValidatorStatus 1 0 # holds a validator permit?Three data-shape rules cover every surprise:
- Keys are
bytes32public keys, not ss58 strings. Convert withbtcli evm pubkey SS58one way andpython -c "from bittensor.evm import pubkey_to_ss58; print(pubkey_to_ss58('0x…'))"the other. - Amounts are rao (1 TAO = 1e9 rao) — not the EVM's 18-decimal wei.
- Scores are u16-normalized: incentive, consensus, dividends, and vtrust
come back as 0–65535, meaning 0.0–1.0. Divide by 65535. (
getRankandgetTrustare deprecated and always return 0 — compute standing from incentive instead.)
Prices and pools: the alpha precompile
alpha (address 0x…0808) exposes the subnet pool:
btcli evm call alpha getAlphaPrice 1 # TAO per alpha, 18-decimal fixed point
btcli evm call alpha getTaoInPool 1 # pool TAO reserve, in rao
btcli evm call alpha getAlphaInPool 1 # pool alpha reserve, in rao-scale alpha
btcli evm call alpha simSwapTaoForAlpha 1 1000000000 # what 1 TAO buys right nowgetAlphaPrice returns the price scaled to 1e18 (EVM balance scale), so
2.5e17 means 0.25 TAO per alpha. The simSwap* functions quote a swap
including slippage — the EVM equivalent of quote-stake.
From an EVM address to its UIDs
uid-lookup (address 0x…0806) answers "which neurons belong to this EVM
key?" — it only returns results for hotkeys that have
associated an EVM key:
btcli evm call uid-lookup uidLookup 1 0x1074Ad… 16 # up to 16 (uid, block_associated) pairsFrom Python
The SDK's EVM layer encodes and decodes for you — this is exactly what
btcli evm call does internally, usable from any script without web3:
from bittensor.evm import EvmRpc, encode_call, decode_result, get_precompile, evm_network
rpc = EvmRpc(evm_network("finney").rpc_url)
metagraph = get_precompile("metagraph")
fn = metagraph.function("getUidCount")
raw = rpc.eth_call({"to": metagraph.address, "data": encode_call(fn, [1])})
(count,) = decode_result(fn, raw)
fn = metagraph.function("getStake")
stakes = []
for uid in range(count):
raw = rpc.eth_call({"to": metagraph.address, "data": encode_call(fn, [1, uid])})
(rao,) = decode_result(fn, raw)
stakes.append(rao / 1e9)
print(f"subnet 1: {count} neurons, top stake {max(stakes):,.0f} TAO")encode_call accepts ss58 addresses wherever the ABI wants a bytes32 key,
so you never hand-convert.
From Solidity
Contracts call precompiles like any other contract — copy the interface from
precompiles/src/solidity/
(or extract it from btcli evm abi metagraph). A contract that pays out only
to registered validators:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IMetagraph {
function getUidCount(uint16 netuid) external view returns (uint16);
function getValidatorStatus(uint16 netuid, uint16 uid) external view returns (bool);
function getHotkey(uint16 netuid, uint16 uid) external view returns (bytes32);
}
contract ValidatorGate {
IMetagraph constant METAGRAPH = IMetagraph(0x0000000000000000000000000000000000000802);
uint16 public immutable netuid;
constructor(uint16 _netuid) {
netuid = _netuid;
}
function isValidatorHotkey(bytes32 hotkey) public view returns (bool) {
uint16 count = METAGRAPH.getUidCount(netuid);
for (uint16 uid = 0; uid < count; uid++) {
if (METAGRAPH.getHotkey(netuid, uid) == hotkey) {
return METAGRAPH.getValidatorStatus(netuid, uid);
}
}
return false;
}
}(A per-call loop over 256 UIDs is fine for view calls, which are free; inside
a paid transaction you'd pass the uid in and verify it instead.)
Deploy it exactly like any other contract, and from ethers the reads are ordinary:
const abi = /* output of: btcli evm abi metagraph */;
const metagraph = new ethers.Contract("0x0000000000000000000000000000000000000802", abi.abi, provider);
console.log(await metagraph.getUidCount(1));See also
- Stake from the EVM — the write-side counterpart.
- Metagraph query — the native read with everything in one call.