Logo
New RPC users get 35% off their first monthView the offer
RPC Assistant

Bittensor Tutorial: How to Connect, Query, and Build on Subtensor

Summary

This tutorial walks through the practical parts of building on Bittensor: connecting to the Subtensor chain over RPC, reading metagraph and subnet state, and submitting extrinsics with the Python SDK. It focuses on the connection layer that most tutorials skip, so your scripts stay reliable as you move from local experiments to something that runs continuously.

You will see how to configure a WebSocket endpoint, run JSON-RPC and SDK calls, and decide when a shared public endpoint is enough versus when a dedicated node makes more sense. The goal is a working setup you can extend into miners, validators, dashboards, or subnet tooling.

Bittensor is a decentralized network of machine learning subnets, and the chain that coordinates all of them is Subtensor. If you searched for a Bittensor tutorial, you probably want to do something concrete: read subnet data, register a hotkey, run a miner, or build a dashboard. Almost all of those tasks start with the same step — getting a reliable connection to Subtensor.

This tutorial is built around that connection layer. Instead of only explaining what Bittensor is, it shows how to connect, which calls to make first, and how to decide whether a shared public endpoint or a dedicated node fits your workload. You can follow it with the Python SDK or with raw JSON-RPC.

Start here: pick your connection path

Before writing code, decide how you will talk to Subtensor. The right choice depends on whether you are exploring, running a long-lived process, or serving other users.

Your situationRecommended connectionWhy
Learning, one-off scripts, reading a few valuesShared public RPC endpointFast to set up, no infrastructure to manage
Miner or validator running continuouslyDedicated node or a provider plan with stable capacityLong-lived WebSocket sessions and consistent throughput matter
Dashboard or app serving many usersProvider RPC with monitoring and failoverYou need predictable capacity and a fallback path
Indexing historical chain dataArchive-capable nodeFull history is required for backfills

If you are just starting the tutorial, use a public endpoint and move on. Revisit this table once your script needs to stay online.

What Subtensor actually is

Subtensor is a Substrate-based blockchain. That matters because it shapes how you interact with it. You do not call smart contracts the way you would on an EVM chain. Instead, you read chain state through storage queries and submit extrinsics (transactions) that map to specific pallets.

The pieces you will touch most often:

  • Subnets — independent networks of miners and validators, each identified by a netuid.
  • Metagraph — the per-subnet snapshot of neurons, their stakes, incentives, and ranks.
  • Hotkeys and coldkeys — hotkeys sign operational activity, coldkeys hold stake and control registration.
  • TAO — the native token used for staking, registration, and incentives.

Because Subtensor is Substrate-based, most tooling uses the Polkadot-style stack: the substrate-interface or bittensor Python packages, or polkadot-js in JavaScript. The RPC layer underneath is standard Substrate JSON-RPC.

Connect to Subtensor over RPC

OnFinality exposes Bittensor Finney mainnet over both HTTP and WebSocket. The public endpoints are:

  • HTTP: https://bittensor-finney.api.onfinality.io/public
  • WebSocket: wss://bittensor-finney.api.onfinality.io/public-ws

For most Bittensor work you want the WebSocket endpoint, because the SDKs and subscriptions rely on it. A quick check that the node is reachable and returning chain metadata:

curl -s https://bittensor-finney.api.onfinality.io/public \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"system_chain","params":[]}'

A healthy response returns the chain name. If you get a connection error, the endpoint or your network is the first thing to check. If you get a JSON-RPC error, the method name or parameters are the likely cause.

For anything interactive, switch to WebSocket. In Python with the Bittensor SDK:

import bittensor as bt

subtensor = bt.subtensor(network="wss://bittensor-finney.api.onfinality.io/public-ws")
print(subtensor.get_current_block())
print(subtensor.get_total_stake())

If you prefer the lower-level Substrate interface, the same endpoint works:

from substrateinterface import SubstrateInterface

substrate = SubstrateInterface(
    url="wss://bittensor-finney.api.onfinality.io/public-ws"
)
print(substrate.get_chain_head())

Both approaches connect to the same node; the SDK just wraps common Bittensor calls for you.

Read subnet and metagraph data

The first useful thing most tutorials do is read the state of a subnet. The metagraph gives you a snapshot of every neuron in a subnet, including stake, incentive, and consensus. This is the data miners and validators use to make decisions.

With the Bittensor SDK:

import bittensor as bt

subtensor = bt.subtensor(network="wss://bittensor-finney.api.onfinality.io/public-ws")
metagraph = subtensor.metagraph(netuid=1)

print("neurons:", metagraph.n)
print("top incentive:", metagraph.incentive.max())
print("total stake:", metagraph.total_stake)

The metagraph is a point-in-time view. If you are building a dashboard or a miner that reacts to changes, you need to refresh it on an interval or subscribe to new blocks. That is where connection quality starts to matter: a metagraph refresh on a busy subnet pulls a meaningful amount of data, and doing it every block over a flaky connection will produce gaps.

A simple polling loop looks like this:

import time
import bittensor as bt

subtensor = bt.subtensor(network="wss://bittensor-finney.api.onfinality.io/public-ws")
last_block = 0

while True:
    block = subtensor.get_current_block()
    if block != last_block:
        metagraph = subtensor.metagraph(netuid=1)
        print(block, metagraph.n, float(metagraph.total_stake))
        last_block = block
    time.sleep(6)

Adjust the sleep interval to your needs. Polling every block is fine for a single subnet; polling many subnets at once is a heavier workload and a good reason to move to a dedicated node.

Submit an extrinsic without breaking things

Reading data is safe. Writing to the chain is where mistakes cost money, so treat this section as a checklist rather than a copy-paste script.

  1. Use a testnet or a low-value hotkey first. Confirm your extrinsic builds and submits before touching real stake.
  2. Check the current block and your account balance before submitting, so you know the transaction is valid at that moment.
  3. Set a sensible era and tip. A short era keeps the transaction from lingering; a tip can help inclusion during congestion.
  4. Wait for finalization, not just submission. A submitted extrinsic can still fail.

A minimal transfer-style extrinsic with the SDK:

import bittensor as bt

wallet = bt.wallet(name="my_coldkey", hotkey="my_hotkey")
subtensor = bt.subtensor(network="wss://bittensor-finney.api.onfinality.io/public-ws")

result = subtensor.transfer(
    wallet=wallet,
    dest_ss58="5F...destination...",
    amount=bt.Balance.from_tao(0.1),
)
print(result)

Replace the destination and amount with your own values. The important habit is to log the extrinsic hash and check its status rather than assuming success.

Common failure modes and how to debug them

Most Bittensor connection problems fall into a small number of categories. This table maps the symptom to the likely cause.

SymptomLikely causeWhat to try
WebSocket disconnects after a whileIdle timeout or unstable endpointReconnect with backoff; use a dedicated node for long sessions
Method not foundWrong method name or endpoint typeConfirm you are on a Substrate JSON-RPC endpoint, not an EVM one
Metagraph returns stale dataCached or lagging nodeQuery the current block and compare; switch endpoints
Extrinsic submitted but never finalizedEra expired or fee too lowResubmit with a fresh era and adequate tip
Slow responses during peak activityShared endpoint under loadMove heavy or continuous workloads to a dedicated node

The pattern is consistent: read-only, occasional calls tolerate shared endpoints, while continuous or high-volume work benefits from dedicated capacity.

When a shared endpoint is not enough

A public endpoint is fine for learning and light scripts. It becomes a problem when your process needs to stay connected for hours, refresh metagraphs across many subnets, or serve other users. At that point you are not debugging your code anymore — you are debugging your infrastructure.

OnFinality provides Bittensor RPC through shared API endpoints and dedicated nodes. A dedicated node gives your workload its own capacity, which helps when you run miners, validators, or dashboards that cannot afford connection gaps. You can review RPC pricing and the Bittensor network page to see what fits your workload, and compare options against other supported RPC networks if you operate across chains.

If you are evaluating providers in general, the same criteria apply: transport support (HTTP and WebSocket), archive availability if you need history, and how failover is handled. A short checklist for choosing an RPC provider covers those tradeoffs in more detail.

A practical first project

If you want a concrete goal for this tutorial, build a small subnet monitor. It should:

  • Connect to Subtensor over WebSocket.
  • Poll the current block on an interval.
  • Refresh the metagraph for one or two subnets each block.
  • Print or store neuron count, total stake, and top incentive.
  • Reconnect automatically if the WebSocket drops.

That project exercises every part of the connection layer you will reuse later: endpoint selection, block tracking, state queries, and reconnect logic. Once it runs reliably for a day, you have a foundation for miner or validator tooling.

Key Takeaways

  • Subtensor is a Substrate-based chain, so you interact through storage queries and extrinsics rather than EVM contracts.
  • Use the WebSocket endpoint for SDK work and subscriptions; HTTP is fine for quick JSON-RPC checks.
  • The metagraph is a snapshot — refresh it on an interval or per block if your logic depends on current state.
  • Treat extrinsic submission as a checklist: test first, check balance and block, wait for finalization.
  • Shared public endpoints suit learning and light scripts; dedicated nodes suit continuous miners, validators, and dashboards.

Frequently Asked Questions

Do I need a node to follow this Bittensor tutorial?

No. You can complete the tutorial using a shared public RPC endpoint. A dedicated node becomes useful when your process needs to stay connected continuously or handle higher volume.

Which endpoint should I use, HTTP or WebSocket?

Use WebSocket for SDK work, subscriptions, and anything long-lived. Use HTTP for quick JSON-RPC calls and health checks.

Why does my metagraph look outdated?

You may be reading from a node that is lagging or cached. Compare the current block across endpoints and switch if the values diverge.

Can I run this against a testnet?

Yes. The workflow is the same; only the endpoint changes. Test extrinsics on a testnet or with a low-value hotkey before touching real stake.

How do I keep a miner or validator online?

Run it against a stable endpoint, add reconnect logic with backoff, and consider a dedicated node so your connection does not depend on shared capacity.

Next steps

Once your monitor runs reliably, extend it: add alerts when a subnet's incentive distribution shifts, store historical metagraph snapshots, or wire it into a miner that adjusts based on live state. Each of those steps increases your dependency on a stable connection, so plan your endpoint strategy before you scale. Start with the Bittensor network page for endpoint details, and review dedicated nodes when you are ready to move off shared capacity.

RPC Knowledge Base

Related RPC details

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started