Learn / Chain Interactions
This chapter builds on: Ch 12 Pallets & the Runtime

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

Single
No keys: one global value

Example: TotalIssuance returns the total TAO supply

Map
One key: lookup by key

Example: Tempo[netuid] returns the tempo for a subnet

DoubleMap
Two keys: lookup by both keys

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());
Storage is Current State

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

Build
Sign
Submit
Include in Block
Finalize

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

StakeAdded Emitted when stake is added to a hotkey
NeuronRegistered Emitted when a neuron joins a subnet
WeightsSet Emitted when a validator sets weights

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 Not Storage

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.

ConstantValuePurpose
BlockWeights.max_block2 trillionMaximum weight per block
Timestamp.MinimumPeriod6,000 msHalf the target block time (block time = 2 × MinimumPeriod = 12s)
ExistentialDeposit500 RAOMinimum 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

NotEnoughBalanceToStake Coldkey doesn't have enough free balance
HotKeyNotRegisteredInSubNet Hotkey isn't registered in the specified subnet
SubNetworkDoesNotExist The specified subnet does not exist

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: