> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# Bittensor

Status: Experimental.

The Bittensor provider connects Amp to Subtensor, the Substrate chain Bittensor runs on. It reads a Subtensor node's
JSON-RPC endpoint, over HTTP or WebSocket. [Raw datasets](/datasets/raw-datasets) of kind `bittensor` extract through
it into tables of blocks, extrinsics (Substrate's transactions), events, and the runtime each block ran under, and
optionally snapshots of the chain's state. [Bittensor Tables](/datasets/tables/bittensor) lists their columns.

## Prerequisites

* A running Amp: [Solo Mode](/running-amp/solo-mode) is the quickest.
* [`ampctl`](/clis/ampctl).
* A Subtensor JSON-RPC endpoint that supports the `chain_getBlockHash`, `chain_getBlock`, `chain_getHeader`,
  `chain_getFinalizedHead`, `state_getStorage`, `state_getRuntimeVersion`, and `state_getMetadata` methods, plus
  `state_getKeysPaged` and `state_queryStorageAt` for
  [state snapshots](/providers/bittensor#state-snapshots).
* For history, an archive node. Amp reads each block's events and runtime from the chain's state at that block, which
  other nodes discard after a few hundred blocks. The Opentensor Foundation's public archive node,
  `https://archive.chain.opentensor.ai:443`, needs no key, but limits how much history it serves you (see
  [`Historical work rate limit exceeded`](/providers/bittensor#rpc-error--32004-historical-work-rate-limit-exceeded)).

Export the endpoint's URL, and its token if it needs one:

```bash
export BITTENSOR_RPC_URL=…
export BITTENSOR_RPC_TOKEN=…
```

`ampd` substitutes the variables when it extracts, not `ampctl` when you register, so set them in the environment
`ampd` runs in. When one is missing, `ampd` logs `environment variable substitution failed for provider, skipping`,
and the job fails with `No provider found for kind 'bittensor' and network 'finney'`. See
[Environment variables](/providers#environment-variables).

## Configure the provider

A minimal configuration names the kind, the network, and the endpoint:

```toml [bittensor_finney.toml]
kind = "bittensor"
network = "finney"
url = "${BITTENSOR_RPC_URL}"
```

* `kind` is always `bittensor`.
* `network` must match the `network` of the datasets that extract through it. Bittensor's main network is `finney`.
* `url` is the endpoint: `http://`, `https://`, `ws://`, or `wss://`.

A fuller one adds a token and limits. Export each variable it refers to, or leave out the fields you don't use: Amp
skips a provider with a variable that isn't set.

```toml [bittensor_finney.toml]
kind = "bittensor"
network = "finney"
url = "${BITTENSOR_RPC_URL}"
auth_token = "${BITTENSOR_RPC_TOKEN}"
rate_limit_per_minute = 600
concurrent_request_limit = 5
timeout_secs = 30
```

* `auth_token` is sent as `Authorization: Bearer <token>`. Add `auth_header` to send it in another header instead,
  like `auth_header = "X-API-Key"`.
* `rate_limit_per_minute` caps the requests Amp sends the endpoint. Set it to your endpoint's limit.
* `concurrent_request_limit` caps the requests in flight at once. It defaults to `5`, about what one block takes.
* `timeout_secs` is how long Amp waits for a request, `30` seconds by default.

The [configuration reference](/providers/configuration-reference#bittensor) documents every field, with its type and
default.

## Create a dataset

::::steps
### Register the provider

Provider names take lowercase letters, digits, and underscores, and start with a letter or underscore.
[Providers](/providers) explains how registration works.

```bash
ampctl provider register bittensor_finney ./bittensor_finney.toml
```

### Generate the manifest

`--start-block` skips the chain's earlier history (see [Start block](/datasets/raw-datasets#start-block)). This
dataset starts at block 7,000,200, from December 2025:

```bash
ampctl manifest generate --kind bittensor --network finney --start-block 7000200 -o bittensor_finney.json
```

Add [state snapshots](/providers/bittensor#state-snapshots) to it if you want them.

### Register the dataset

```bash
ampctl dataset register acme/bittensor_finney ./bittensor_finney.json --tag 1.0.0
```

### Deploy it

`--end-block` stops the job at block 7,000,209, after 10 blocks. Without it, the job keeps up with the chain.

```bash
ampctl dataset deploy acme/bittensor_finney@1.0.0 --end-block 7000209
```

### Query it

Once `ampctl dataset status acme/bittensor_finney@1.0.0` says `COMPLETED`, count the extrinsics each pallet's
calls made, and how many succeeded:

:::terminal
```bash
ampsql 'SELECT pallet_name, count(*) AS extrinsics, count(*) FILTER (WHERE success) AS succeeded
FROM "acme/bittensor_finney".extrinsics
GROUP BY pallet_name
ORDER BY extrinsics DESC'
```

```
+-----------------+------------+-----------+
| pallet_name     | extrinsics | succeeded |
+-----------------+------------+-----------+
| SubtensorModule | 176        | 145       |
| Commitments     | 54         | 26        |
| Drand           | 41         | 41        |
| Utility         | 19         | 17        |
| Balances        | 11         | 11        |
| Timestamp       | 10         | 10        |
| Proxy           | 9          | 9         |
| Ethereum        | 4          | 4         |
+-----------------+------------+-----------+
```
:::
::::

[Raw Datasets](/datasets/raw-datasets) covers the manifest, finalized blocks, and how extraction works.

## Tables

* [`blocks`](/datasets/tables/bittensor#blocks): one row per block, with its hashes and roots.
* [`digest_logs`](/datasets/tables/bittensor#digest_logs): the entries of each block header's digest, its consensus
  data.
* [`extrinsics`](/datasets/tables/bittensor#extrinsics): one row per extrinsic, with its raw bytes, its signer, whether
  it succeeded, and the pallet (one of the runtime's modules) and call it names, when Amp can decode them.
* [`events`](/datasets/tables/bittensor#events): one row per event, with its raw bytes, the extrinsic it belongs to,
  and its pallet and name.
* [`runtime_metadata`](/datasets/tables/bittensor#runtime_metadata) and
  [`runtime_symbols`](/datasets/tables/bittensor#runtime_symbols): the runtime each block ran under (see
  [Runtime metadata](/providers/bittensor#runtime-metadata)).
* [`storage_entries`](/datasets/tables/bittensor#storage_entries): the [state snapshots](/providers/bittensor#state-snapshots), if any.

Payloads stay in Substrate's binary encoding, SCALE. [Substrate functions](/querying/functions/substrate) decode them in
SQL.

## Runtime metadata

A Substrate chain's runtime, the code that defines its pallets, calls, events, and storage, changes with upgrades, and
its metadata describes how to decode everything else. Amp reads each block's runtime version and metadata.

Every block gets a `runtime_metadata` row, with the metadata itself and its version numbers, and `runtime_symbols` rows
naming the runtime's pallets, calls, and events. So you can decode any block with the runtime it ran under. To list the
runtimes and the block each started at:

:::terminal
```bash
ampsql 'SELECT spec_version, min(block_num) AS first_block
FROM "acme/bittensor_finney".runtime_metadata
GROUP BY spec_version
ORDER BY first_block'
```

```
+--------------+-------------+
| spec_version | first_block |
+--------------+-------------+
| 348          | 7000200     |
+--------------+-------------+
```
:::

The dataset's 10 blocks all ran spec version 348.

A block that upgrades the runtime still runs under the previous one, so Amp decodes its extrinsics and events with the
previous runtime's metadata, and their version columns say so. Its `runtime_metadata` row describes the new runtime.

## State snapshots

A state snapshot copies storage entries, like an account balance or a subnet's settings, at chosen blocks, into the
`storage_entries` table. You choose them in the dataset's manifest, not the provider's configuration: add
`state_snapshots` beside `network` in the generated manifest, and leave its `tables` as they are (shortened here).

```json [bittensor_finney.json]
{
  "kind": "bittensor",
  "network": "finney",
  "start_block": 7000200,
  "finalized_blocks_only": false,
  "state_snapshots": {
    "selectors": ["Balances.TotalIssuance", { "selector": "SubtensorModule.Difficulty", "block_interval": 360 }]
  },
  "tables": { "...": "..." }
}
```

* A **selector** is a storage entry's exact `Pallet.StorageEntry` name, as the runtime's metadata spells it, case
  included. Not a storage key or prefix.
* A string snapshots the entry at every block. An object with a `block_interval` snapshots it at the blocks whose number
  is a multiple of it: 0, 360, 720, and so on, wherever extraction starts.
* A plain value gives one row per snapshot. A map, like `System.Account`, gives one row per entry. An absent value or
  empty map gives none, and so does a selector the runtime doesn't have, misspelled or wrongly cased included.
* Keep the `storage_entries` table the generated manifest has: registration fails without it.

Register the manifest as a new [version](/datasets/versioning), like `--tag 1.1.0`, then deploy it.

A snapshot's cost grows with its entry's size: `System.Account` holds hundreds of thousands of accounts, and Amp
fetches each of them at every snapshot. A larger interval takes fewer snapshots, but each costs the same. For heavy
snapshots, raise `concurrent_request_limit`, like to `40`: Amp extracts as many blocks at once as the limit allows, up
to eight, and each block takes about five requests.

`storage_key` and `storage_value` hold raw bytes. A map's key starts with the entry's prefix, followed by the map's key:
`SubtensorModule.Difficulty` is keyed by subnet, so each snapshot has a row per subnet. Over the dataset's 10 blocks,
`Balances.TotalIssuance` has a row per block, and `SubtensorModule.Difficulty` has 129 rows, one per subnet, all at
block 7,000,200, the only multiple of 360. Decode values with [`scale_decode`](/querying/functions/substrate) and the
entry's type, `u64` for both selectors here:

:::terminal
```bash
ampsql "SELECT block_num, selector, scale_decode(storage_value, 'u64') AS value
FROM \"acme/bittensor_finney@1.1.0\".storage_entries
WHERE selector IN ('Balances.TotalIssuance', 'SubtensorModule.Difficulty')
ORDER BY block_num, selector, storage_key
LIMIT 5"
```

```
+-----------+----------------------------+---------------------+
| block_num | selector                   | value               |
+-----------+----------------------------+---------------------+
| 7000200   | Balances.TotalIssuance     | 3091406829074423    |
| 7000200   | SubtensorModule.Difficulty | 4611686018427387903 |
| 7000200   | SubtensorModule.Difficulty | 10000000            |
| 7000200   | SubtensorModule.Difficulty | 944989622734        |
| 7000200   | SubtensorModule.Difficulty | 10000000            |
+-----------+----------------------------+---------------------+
```
:::

## Limitations

* A selector snapshots a whole storage entry: you can't narrow a map to one account, subnet, or key.
* Amp doesn't check selectors when you register: one that matches nothing gives no rows.
* `rate_limit_per_minute` and `concurrent_request_limit` apply to each provider on its own, not to all of them together.

## Errors

### `Bittensor state snapshot selectors require the storage_entries table in the manifest`

`ampctl dataset register` rejects a manifest with `state_snapshots` but no `storage_entries` table. Generate the
manifest again, and keep its tables.

### `duplicate state snapshot selector 'System.Account'`

`ampctl dataset register` rejects a manifest that lists the same selector twice. Keep one, with the interval you want.

### `custom authentication header requires auth_token`

The provider sets `auth_header` without `auth_token`. `ampctl provider register` accepts it, but the job that extracts
through the provider fails at once, without retrying, and `ampd` logs the error. Add the token, or remove the header,
and register the provider again.

### `RPC error -32004: Historical work rate limit exceeded`

The endpoint limits how much of the chain's history it serves you, like the public archive node at
`https://archive.chain.opentensor.ai:443`, and you've used up your share. It can also answer `HTTP status client error
(429 Too Many Requests)`. `ampd` logs it with `provider operation failed, reselecting endpoint`, then the job fails with
`failed to fetch runtime metadata for block …`, or another of its requests, and Amp retries the job about a minute
later. The job completes once the endpoint serves you again, a few minutes later: extracting 10 blocks took up to 8
minutes. Amp fetches each block's runtime metadata, a response of about 500 KB, so history adds up fast. To extract more
than a few blocks, use an archive node without that limit, your own or a provider's.
