Chain Interactions
How to read from and write to Subtensor. This page covers the practical patterns for interacting with the blockchain.
Reading from Subtensor (Storage)
Storage is on-chain state you can query at any time. Each storage item has a key (what you're looking up) and a value (what you get back).
Storage Query Patterns
Example: TotalIssuance returns the total TAO supply
Example: Tempo[netuid] returns the tempo for a subnet
Example: Uids[netuid][hotkey] returns the UID for a hotkey
on a subnet
Example: Reading a DoubleMap
The Uids storage is a DoubleMap keyed by subnet ID and hotkey, returning the neuron's
UID (a u16). Both keys are required.
// Using polkadot.js API
const uid = await api.query.subtensorModule.uids(netuid, hotkey);
console.log('UID: ' + uid.unwrap().toNumber());When you query storage, you get the value as of the latest finalized block. For historical values, you need to query at a specific block hash or use an indexer.
Writing to Subtensor (Calls)
Calls (also called extrinsics) are how you modify on-chain state. Every call requires a signature from the account submitting it.
Call Lifecycle
Example: Adding Stake
The add_stake call transfers TAO from your coldkey to stake on a hotkey in a specific
subnet.
// Build the call
const tx = api.tx.subtensorModule.addStake(hotkey, netuid, amount);
// Sign and send
const hash = await tx.signAndSend(coldkey, { signer });
// Wait for finalization
await tx.signAndSend(coldkey, { signer }, ({ status, events }) => {
if (status.isFinalized) {
console.log('Stake added!');
}
});On Success
- • Storage is updated
- • Events are emitted
- • Transaction fee is charged
On Failure
- • Error is returned
- • Storage unchanged
- • Transaction fee still charged
Reacting to Changes (Events)
Events are emitted when things happen on-chain. You can subscribe to them in real-time or query them historically (via an indexer).
Common Subtensor Events
Example: Subscribing to StakeAdded
// Subscribe to new blocks and filter events
api.query.system.events((events) => {
events.forEach(({ event }) => {
if (event.section === 'subtensorModule' &&
event.method === 'StakeAdded') {
const [coldkey, hotkey, taoAmount, alphaAmount, netuid, alphaPrice] = event.data;
console.log(`Staked ${taoAmount} RAO → ${alphaAmount} alpha on SN${netuid}`);
}
});
});Events are transient: they're included in blocks but not queryable like storage. For historical events, use an indexer (like Subsquid) that processes blocks and stores events in a database.
Static Values (Constants)
Constants are fixed values baked into the runtime. They can only change with a runtime upgrade.
| Constant | Value | Purpose |
|---|---|---|
| BlockWeights.max_block | 2 trillion | Maximum weight per block |
| Timestamp.MinimumPeriod | 6,000 ms | Half the target block time (block time = 2 × MinimumPeriod = 12s) |
| ExistentialDeposit | 500 RAO | Minimum account balance |
// Query a constant
const blockTime = api.consts.timestamp.minimumPeriod;
console.log('Block time: ' + blockTime + 'ms');What Can Go Wrong (Errors)
When a call fails, the chain returns an error. Each pallet defines its own error types with descriptions of what went wrong.
Common SubtensorModule Errors
Example: Handling Errors
tx.signAndSend(coldkey, { signer }, ({ status, dispatchError }) => {
if (dispatchError) {
if (dispatchError.isModule) {
const decoded = api.registry.findMetaError(dispatchError.asModule);
console.error(decoded.section + '.' + decoded.name + ': ' + decoded.docs);
// e.g., "subtensorModule.NotEnoughBalanceToStake: ..."
} else {
console.error(dispatchError.toString());
}
}
});Continue Learning
You've completed the Fundamentals! Here's where to go next: