Guides/EVM

EVM

Use the Bittensor EVM from the terminal — keys, funding, MetaMask, precompiles, and the ss58 ↔ h160 seam.

View as Markdown

Subtensor runs a full EVM runtime: standard smart contracts, standard eth_* JSON-RPC, MetaMask, Hardhat, and Remix all work. Contracts execute on the Bittensor chain (not Ethereum) with TAO as the native currency.

This guide starts with the smallest working path — create a key, fund it, check the balance — then walks through every btcli evm command and the deeper concepts (address mappings, decimals, precompiles).

Walkthroughs

End-to-end tutorials that build on the commands below:

Quick start: your first EVM key

Prerequisites: bittensor installed (includes eth-account for EVM signing). A configured coldkey wallet (btcli wallet list).

1. Check the EVM endpoint

btcli evm doctor

Confirms the JSON-RPC URL, chain ID, gas price, and (if you have one) your default EVM key's balance. On localnet, start the chain first; if chain ID is unset, see Connectivity below.

2. Create an encrypted EVM key

btcli evm key new -w my_coldkey
btcli evm key list -w my_coldkey

Creates a random secp256k1 key stored as Ethereum keystore V3 JSON next to your hotkeys (~/.bittensor/wallets/<wallet>/evmkeys/). The file imports directly into MetaMask (btcli evm key export).

3. Fund the key from your coldkey

btcli evm fund --amount-tao 1 -w my_coldkey
btcli evm balance -w my_coldkey

fund transfers TAO from the coldkey to the EVM key's ss58 mirror (substrate extrinsic, coldkey-signed). The balance then appears on the EVM side — MetaMask shows it with 18 decimals (see Decimals). The mirror is an ordinary substrate account, so the existential deposit (500 rao) stays locked in it and is invisible from the EVM side: fund 1 TAO into a fresh key and the EVM balance reads τ0.999999500. It's withheld once, not per transfer.

Preview first with --dry-run:

btcli evm fund --amount-tao 1 --dry-run -w my_coldkey

4. (Optional) Connect MetaMask

btcli evm config --format metamask

Paste the network settings into MetaMask (Add network → Add manually). Import the keystore with btcli evm key export --out ./mykey.json.

You now have a funded EVM account on Subtensor. The sections below explain why funding uses a mirror address, what every other command does, and how to move TAO back to ss58.

Two address domains

Two signing domains, two address mappings

Conversions are deterministic but never carry private keys — a btcli wallet cannot sign EVM txs, and MetaMask cannot sign extrinsics.

Native (ss58)

5GrwvaEF…

sr25519 / ed25519 extrinsics

Coldkeys, hotkeys, neurons

Used by btcli tx …

same chain

EVM (h160)

0x742d35Cc…

secp256k1 EVM transactions

MetaMask, Hardhat, contracts

Used by eth_sendRawTransaction

Hashed mirror (fund an EVM account)

Every h160 has an ss58 mirror: ss58(blake2("evm:" ++ h160)). Transfer TAO to the mirror and it appears as that EVM account's balance.

btcli evm mirror · btcli evm fund

Truncated mapping (claim a MetaMask deposit)

Every ss58 account controls one h160: the first 20 bytes of its public key. Send TAO from MetaMask to that address, then claim it on the native side.

btcli evm deposit-address · btcli evm claim-deposit

Native decimals

1 TAO = 1e9 rao

EVM decimals

1 TAO = 1e18 wei

Same funds, different display scale

Key rule

Never mixed

Fund with mirror; claim with truncated

Takeaways:

  • ss58 (5…) — native accounts; sign extrinsics with sr25519/ed25519.
  • h160 (0x…) — EVM accounts; sign transactions with secp256k1.
  • Mirror mapping — fund an arbitrary EVM address: btcli evm mirror 0x…
  • Truncated mapping — claim a MetaMask send into your coldkey: btcli evm deposit-address

Utility commands:

btcli evm mirror 0x742d35Cc6634C0532925a3b844Bc454e4438f44e   # ss58 mirror of an h160
btcli evm pubkey 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY  # bytes32 for precompiles
btcli evm deposit-address -w my_coldkey                         # where MetaMask should send

Money flows

Four ways TAO crosses the ss58 ↔ EVM seam

Pick the path that matches who holds the keys. The two “withdraw” names are easy to confuse — read the signer and gas columns.

Fund an EVM key from your coldkey

btcli evm fund
signs: coldkey (substrate)gas: substrate fee only
  1. 1.

    Create or import an EVM key

    btcli evm key new

  2. 2.

    Coldkey transfers TAO to the key’s ss58 mirror

    Wraps fund_evm_key intent

  3. 3.

    Balance shows in MetaMask / btcli evm balance

    18-decimal EVM view

Send between EVM accounts

btcli evm send
signs: stored EVM keygas: EVM gas (wei)
  1. 1.

    Pick source key (--evm-key)

    Defaults to wallet default key

  2. 2.

    Ordinary value transfer to another 0x address

    Like Ethereum send

EVM key → any ss58 address

btcli evm send-to-ss58
signs: stored EVM keygas: EVM gas (wei)
  1. 1.

    Call BalanceTransfer precompile with msg.value

    Destination needs no setup

  2. 2.

    TAO credits the ss58 account natively

    Not the same as claim-deposit

Formerly named evm withdraw — renamed to avoid confusion with claim-deposit.

MetaMask deposit → coldkey

btcli evm claim-deposit
signs: coldkey (substrate)gas: substrate fee only
  1. 1.

    Show your deposit address

    btcli evm deposit-address

  2. 2.

    Send TAO from MetaMask to that 0x address

    Credits the truncated mirror

  3. 3.

    Pull funds into the coldkey

    Also: btcli tx evm-withdraw

Uses the truncated mapping (first 20 bytes of pubkey), not the hashed mirror.

Naming cheat sheet

GoalCommandWho signsPays gas
Coldkey → EVM key balancebtcli evm fundcoldkeysubstrate
EVM → EVMbtcli evm sendEVM keyEVM (wei)
EVM key → any ss58btcli evm send-to-ss58EVM keyEVM (wei)
MetaMask deposit → coldkeybtcli evm claim-depositcoldkeysubstrate

Worked example: fund → send → exit to ss58

# Fund
btcli evm fund --amount-tao 2 -w my_coldkey

# Send 0.5 TAO to another EVM address
btcli evm send --to 0xabc… --amount-tao 0.5 -w my_coldkey

# Send 0.5 TAO to a friend's ss58 (they need no EVM key)
btcli evm send-to-ss58 --to 5F… --amount-tao 0.5 -w my_coldkey

Worked example: MetaMask → coldkey

btcli evm deposit-address -w my_coldkey
# Send TAO from MetaMask to the printed 0x deposit address

btcli evm claim-deposit --amount-tao 1 -w my_coldkey

btcli evm command reference

All commands accept -w / --wallet, -n / --network, and --json like the rest of btcli; commands that submit something also accept --dry-run and --yes (read-only commands don't). EVM transaction commands additionally accept --rpc-url to override the network's JSON-RPC endpoint.

EVM keys (btcli evm key …)

CommandWhat it does
key newGenerate a random EVM key; store encrypted keystore V3.
key importImport from --private-key, --keystore file, or --mnemonic.
key exportPrint or write keystore JSON (--out writes mode 0600); --private-key decrypts to raw hex for ETH_PRIVATE_KEY.
key listName, h160 address, and ss58 mirror for each stored key.
key showDetails for one key.

--evm-key elsewhere accepts a key name (default) or WALLET/NAME.

Address helpers

CommandWhat it does
mirror [ADDRESS]ss58 mirror of an h160 (where native TAO lands for that EVM account).
pubkey SS5832-byte public key as 0x-hex (what precompiles expect as bytes32).
deposit-addressTruncated h160 + mirror for MetaMask → coldkey deposits.

Money movement

CommandWhat it does
balance [ADDRESS]EVM balance in TAO and wei via JSON-RPC.
fund --amount-tao NColdkey → EVM key mirror (fund-evm-key).
send --to 0x… --amount-tao NOrdinary EVM value transfer between h160 accounts.
send-to-ss58 --to SS58 --amount-tao NEVM key → ss58 via BalanceTransfer precompile.
claim-deposit --amount-tao NColdkey claims MetaMask deposit (evm-withdraw).

Hotkey association

CommandWhat it does
associate --netuid NLink stored EVM key to wallet hotkey on a subnet (proof + extrinsic).

Some subnets require an associated EVM key. The command:

  1. Reads substrate block height (not EVM RPC).
  2. Unlocks the EVM key and produces an EIP-191 signature over hotkey_pubkey (32B) ++ keccak(SCALE-u64(block_number)).
  3. Submits associate-evm-key signed by the hotkey.

Query the link with associated-evm-key.

Precompiles & contracts

CommandWhat it does
precompilesCatalog: name, address, description.
abi NAMEAddress + ABI JSON for Hardhat/ethers/viem.
call NAME [FN] [ARGS…]View calls free via eth_call; writes need --evm-key. Also takes a 0x contract address with --abi.
deploy ARTIFACT [ARGS…]Deploy a compiled contract (Hardhat/Foundry artifact or .bin).
stake add/remove/showStaking-v2 precompile sugar (TAO/alpha unit conversion handled).

Examples:

btcli evm call metagraph getUidCount 1              # view — no key, no gas
btcli evm call staking-v2 getStake HOTKEY MIRROR 1  # view stake position
btcli evm stake add --netuid 1 --hotkey 5F… --amount-tao 2
btcli evm deploy artifacts/…/TaoVault.json          # your own contracts
btcli evm call 0xCONTRACT… balance --abi artifacts/…/TaoVault.json

Before a write is signed, the CLI echoes the decoded arguments — bytes32 keys shown as ss58, rao amounts shown in TAO — and states which substrate role the EVM key's mirror plays in the dispatched call (coldkey, hotkey, owner). Deprecated functions are flagged in listings and warned about when called.

Setup & diagnosis

CommandWhat it does
networksChain ID and RPC URL per preset network.
config --format metamaskPaste-ready MetaMask / Hardhat / Remix settings.
doctorProbe RPC reachability, chain ID, gas price, key balance.
setup-localnetMake a fresh localnet EVM-ready: set the chain ID, disable the deploy whitelist (two sudo calls, signed by Alice).

Connectivity

NetworkEVM RPCChain ID
Mainnet (finney)https://lite.chain.opentensor.ai964
Testnethttps://test.chain.opentensor.ai945
Localnethttp://127.0.0.1:994442 at genesis

Mainnet is also on ChainList as chain 964. A fresh localnet boots with the generic substrate chain ID 42 and blocks contract deployment behind a whitelist — btcli evm setup-localnet -w alice fixes both in one command (--chain-id 945 by default).

Override the RPC for any command:

btcli evm balance --rpc-url http://127.0.0.1:9944
export BT_EVM_ENDPOINT=http://127.0.0.1:9944   # local preset default

The decimals trap

Native TAO has 9 decimals (1 TAO = 1e9 rao). The EVM side uses Ethereum's 18 decimals: 1 TAO = 1e18 wei in a transaction's value field. MetaMask always assumes 18, so displayed EVM balances use a different exponent than substrate wallets — the funds are the same.

Contract code forwarding msg.value into a precompile must convert to rao:

uint256 amountRao = msg.value / 1e9;

Precompile amount parameters are in rao, not 1e18 TAO.

Precompiles

Beyond standard Ethereum precompiles (ECRecover, Sha256, …), subtensor exposes chain operations at fixed addresses (the 20-byte zero-padded index — e.g. index 20530x…0805):

NameIndexAddressPurpose
balance-transfer20480x800Send TAO from EVM to any ss58 (msg.value).
staking (v1)20490x801Deprecated — use staking-v2.
metagraph20500x802Read neuron/subnet state.
subnet20510x803Subnet registration and owner params.
neuron20520x804Weights, registration, serving.
staking-v220530x805add/remove/move/transfer stake (amounts in rao).
uid-lookup20540x806UIDs for an EVM address on a subnet.
storage-query20550x807Raw substrate storage reads (allow-listed pallet prefixes).
alpha20560x808Subnet alpha token info.
crowdloan20570x809Crowdloan from EVM.
leasing20580x80aSubnet leasing.
proxy20590x80bProxy delegations.
address-mapping20600x80cOn-chain h160 → mirror bytes32.
voting-power20610x80dValidator voting power (stake EMA) per subnet.
ed25519-verify10260x402Verify ed25519 (prove ss58 ownership).
sr25519-verify10270x403Verify sr25519.

Semantics that surprise Ethereum developers:

  • When a contract calls a precompile, the contract address is the coldkey — it must hold the funds.
  • Hotkey/coldkey arguments are bytes32 public keys, not ss58 strings. btcli evm call converts ss58 automatically; Solidity must pass raw keys.
  • Amount parameters are in rao (see decimals above).

Source: subtensor/precompiles. Examples: RaoFoundation/evm-bittensor.

Python SDK

The same layer lives in bittensor.evm (included in the base install):

import bittensor as sub
from bittensor.evm import h160_to_ss58, association_proof
from bittensor.evm.keys import create_evm_key, unlock_evm_key
from bittensor.evm.precompiles import encode_call, get_precompile

Substrate-side intents (fund-evm-key, evm-withdraw, associate-evm-key) use the normal client.plan / client.execute flow. EVM-side sends use bittensor.evm.rpc and bittensor.evm.transactions directly.

Hosted bridges

Third-party services (not part of this SDK):

  • tao.app/bridge — TAO between substrate and EVM wallets.
  • vTAO — liquid-staked TAO ERC-20 on the Bittensor EVM, bridgeable to Base.

Tooling notes

  • Compile with Solidity 0.8.24 or lower, EVM target Cancun. Newer targets can fail deploy with InvalidCode(Opcode) or gas-estimation errors.
  • eth_estimateGas failures mean any invalid transaction — insufficient balance, bad calldata, unset chain ID, or localnet deployment whitelist — not just gas problems. When an estimate fails, btcli probes for the usual causes and reports what it finds.
  • MetaMask nonces stuck after a localnet restart: Settings → Advanced → "Clear activity tab data".
  • Localnet contract deployment is blocked by a whitelist until btcli evm setup-localnet disables it.

See also

  • Local development — run a chain for testing.
  • Transfer — send TAO to an EVM mirror manually.
  • Staking guide — native-side stake; compare with btcli evm stake add for EVM-key-funded stake.