Learn / The Query Stack
This chapter builds on: Ch 13 Chain Interactions

The Query Stack

Every interaction with Subtensor passes through a stack of abstraction layers. Understanding these layers helps you choose the right tool, debug problems when they arise, and know what's happening under the hood when you call api.query.

The 5 Layers

When you query a Subtensor node, your request passes through up to five abstraction layers. Higher layers are easier to use; lower layers give you more control.

1
SDKs & Libraries
polkadot.js, bittensor SDK, subxt — typed, ergonomic, handles encoding
2
Runtime APIs
Named functions executed inside the runtime via state_call
3
Pallet Storage & Calls
Direct access to on-chain storage items, extrinsics, events, constants, and errors
4
JSON-RPC Methods
The wire protocol — state_getStorage, chain_getBlock, etc.
5
Transport
HTTP or WebSocket connection to the node

Layer 1: SDKs & Libraries

SDKs are the highest abstraction layer. They handle SCALE encoding, type resolution, metadata parsing, and connection management so you can write clean, typed code.

SDKLanguageBest For
polkadot.jsTypeScriptWeb apps, scripting, most common choice
bittensor SDKPythonMiners, validators, data science
subxtRustHigh-performance, type-safe, compiled

What Happens Under the Hood

When you write api.query.subtensorModule.tempo(1), the SDK performs four steps:

1. Encode key
2. Build RPC call
3. Send over WS
4. Decode result
When to Use

Almost always. Unless you have a specific reason to go lower (custom encoding, unsupported methods, debugging), start with an SDK.

Layer 2: Runtime APIs

Runtime APIs are named functions that execute inside the Wasm runtime. You call them via the state_call JSON-RPC method, which acts as a bridge between the outer node and the inner runtime.

SDK vs Raw JSON-RPC

The SDK wraps state_call for you. Here's what it looks like at each level:

With SDK (Layer 1)
const result = await api.call.subtensorModule.getServingRateLimit(netuid);
Raw JSON-RPC (Layer 4)
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "state_call",
  "params": [
    "SubtensorModuleApi_get_serving_rate_limit",
    "0x0100000000000000",
    null
  ]
}

Key Subtensor Runtime APIs

Subtensor exposes several runtime API traits. These provide computed values that aren't directly available in storage.

  • SubtensorModuleApi — subnet parameters, neuron info, staking data
  • DelegateInfoRuntimeApi — delegate metadata and nominations
  • NeuronInfoRuntimeApi — neuron details for a subnet
  • SubnetInfoRuntimeApi — subnet metadata and hyperparameters
  • StakeInfoRuntimeApi — staking information by coldkey or hotkey

See the full list in the Runtime APIs Reference.

Layer 3: Pallet Storage & Calls

At this layer you work directly with the pallet's storage items, calls, events, constants, and errors. The runtime metadata (available via state_getMetadata) describes all of these, including their SCALE types.

Pallet ItemRPC MethodDirection
Storagestate_getStorageRead
Callsauthor_submitExtrinsicWrite
Eventschain_getBlockRead (from block)
Constantsstate_getMetadataRead (from metadata)
ErrorsReturned in dispatch results

Layer 4: JSON-RPC Methods

JSON-RPC is the wire protocol for communicating with a Substrate node. Every query ultimately becomes a JSON-RPC call. Methods are organized into namespaces.

NamespacePurposeExamples
stateQuery storage, metadata, runtimestate_getStorage, state_call
chainBlocks, headers, finalitychain_getBlock, chain_getHeader
systemNode info, health, peerssystem_chain, system_health
authorSubmit extrinsicsauthor_submitExtrinsic
paymentFee estimationpayment_queryInfo

Example: Raw curl Request

curl -X POST https://entrypoint-finney.opentensor.ai:443 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "state_getStorage",
    "params": ["0x658faa385070e074c85bf6b568cf055564b..."]
  }'

See the full list in the JSON-RPC Methods Reference.

Layer 5: Transport

The lowest layer is the network connection itself. Substrate nodes expose two transport options.

TransportProtocolSubscriptionsBest For
HTTPPOST request/responseNoOne-off queries, serverless functions
WebSocketPersistent bidirectionalYesReal-time apps, event subscriptions
Subscriptions Require WebSocket

If you need to subscribe to new blocks, finalized heads, or storage changes, you must use a WebSocket connection (wss://). HTTP connections only support request/response.

Historical Queries

By default, queries return data from the latest finalized block. To query state at a specific point in time, pass a block hash.

Example: Querying at a Past Block

// Get the block hash for a specific block number
const blockHash = await api.rpc.chain.getBlockHash(4_200_000);

// Query storage at that block
const apiAt = await api.at(blockHash);
const tempo = await apiAt.query.subtensorModule.tempo(1);

// Raw JSON-RPC equivalent: pass blockHash as last param
// { "method": "state_getStorage", "params": ["0x...", blockHash] }
Archive Nodes Required

Historical queries only work against archive nodes, which store the full state at every block. Standard (pruned) nodes only keep recent state and will return errors for old block hashes. Public endpoints are typically pruned. See Node Operations for more.

JSON-RPC v1 vs v2

Subtensor exposes both legacy v1 methods and several newer Substrate JSON-RPC v2 namespaces. Most SDKs and documentation still use v1, but the v2 API is available for chain head tracking, chain spec queries, and transaction broadcasting.

v2 NamespaceStatusv1 Equivalent
chainHead_v1_*Availablechain_subscribeNewHeads
chainSpec_v1_*Availablesystem_chain, system_properties
transaction_v1_*Availableauthor_submitExtrinsic
transactionWatch_v1_*Availableauthor_submitAndWatchExtrinsic
archive_unstable_*Not Availablestate_getStorage + blockHash
archive_unstable_* Is Not Available

The archive_unstable_* namespace is not present on Subtensor nodes. For historical state queries, use state_getStorage with a blockHash parameter on an archive node. Always verify method availability against the supported methods list.

Choosing the Right Layer

Most developers should stay at Layer 1 (SDKs). Here's when you'd reach for a lower layer.

I want to...Use ThisLayer
Query storage or submit extrinsicspolkadot.js / bittensor SDK1
Call a computed runtime functionRuntime API via SDK1-2
Understand available storage itemsRuntime metadata3
Debug a failing querycurl + JSON-RPC4
Build a lightweight HTTP clientRaw HTTP + JSON-RPC4-5
Subscribe to live eventsWebSocket connection5
Rule of Thumb

Start at the highest layer that meets your needs. Drop down a layer only when the one above doesn't expose what you need, or when you need to debug what's happening underneath.

Related Resources

Dive deeper into the individual layers.