> **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.

# Solana

Status: Experimental.

The Solana provider connects Amp to Solana, which divides time into slots of a fraction of a second, each of which can
hold one block. It reads recent slots from a Solana JSON-RPC endpoint, and older ones from
[Old Faithful](https://docs.old-faithful.net), a public archive of Solana mainnet's full history.
[Raw datasets](/datasets/raw-datasets) of kind `solana` extract through it into five tables: block headers,
transactions, their messages and instructions, and block rewards. [Solana Tables](/datasets/tables/solana) lists their
columns.

## Prerequisites

* A running Amp: [Solo Mode](/running-amp/solo-mode) is the quickest.
* [`ampctl`](/clis/ampctl).
* A Solana JSON-RPC endpoint that supports `getSlot`, `getBlock`, and `getBlocksWithLimit`. Amp reads older slots from
  the archive, so the endpoint only needs the slots the archive doesn't have, like the last day or two. Solana's
  public endpoint serves the whole history, but only about six `getBlock` calls every 10 seconds: enough for the
  example below, which reads the archive, but not to extract more than a few slots over RPC (see
  [`429 Too Many Requests`](/providers/solana#http-status-client-error-429-too-many-requests)).

Export the endpoint's URL:

```bash
export SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
```

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

## Configure the provider

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

```toml [solana_mainnet.toml]
kind = "solana"
network = "solana-mainnet"

[rpc_provider_info]
url = "${SOLANA_RPC_URL}"
```

* `kind` is always `solana`.
* `network` must match the `network` of the datasets that extract through it.
* `rpc_provider_info.url` is the RPC endpoint, over HTTP or HTTPS.

A fuller one adds a token, a rate limit, faster transaction downloads, and the optional endpoints the sections below
describe. 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 [solana_mainnet.toml]
kind = "solana"
network = "solana-mainnet"
max_rpc_calls_per_second = 50
transaction_encoding = "base64"
chain_head_refresh_interval_ms = 1000

[rpc_provider_info]
url = "${SOLANA_RPC_URL}"
auth_token = "${SOLANA_RPC_TOKEN}"

[fallback_rpc_provider_info]
url = "${SOLANA_FALLBACK_RPC_URL}"

[ws_provider_info]
url = "${SOLANA_WS_URL}"

[old_faithful_archive]
max_concurrent_requests = 16
```

* `max_rpc_calls_per_second` caps the requests Amp sends the RPC endpoint. Set it to your endpoint's limit.
* `transaction_encoding` sets how the endpoint sends transactions (see [Transaction encoding](/providers/solana#transaction-encoding)).
* `chain_head_refresh_interval_ms` sets how often Amp checks for newly finalized slots (see
  [Following the chain](/providers/solana#following-the-chain)).
* `rpc_provider_info.auth_token` is sent as `Authorization: Bearer <token>`. Add `auth_header` to send it in another
  header, like `auth_header = "x-api-key"`.
* `fallback_rpc_provider_info` fills in truncated logs (see [Truncated logs](/providers/solana#truncated-logs)).
* `ws_provider_info` follows the finalized slot over WebSocket (see [Following the chain](/providers/solana#following-the-chain)).
* `old_faithful_archive` sets how Amp reads the archive (see [Old Faithful archive](/providers/solana#old-faithful-archive)).

The [configuration reference](/providers/configuration-reference#solana) 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 solana_mainnet ./solana_mainnet.toml
```

### Generate the manifest

```bash
ampctl manifest generate --kind solana --network solana-mainnet --start-block 456192000 -o solana_mainnet.json
```

`--start-block 456192000` starts the dataset at the first slot of epoch 1056, from October 12, 2026, rather than at
slot 0. Pick a start block that has a block: see [Skipped slots](/providers/solana#skipped-slots).

### Register the dataset

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

### Deploy it

```bash
ampctl dataset deploy acme/solana_mainnet@1.0.0 --end-block 456192099
```

`--end-block 456192099` stops the job after 100 slots, which it reads from the [archive](/providers/solana#old-faithful-archive) in
about 10 seconds. Without it, the job would keep extracting up to the chain's head, and then follow it, which Amp
v0.0.42 can't do from here: see [Old Faithful archive](/providers/solana#old-faithful-archive).
`ampctl dataset status acme/solana_mainnet@1.0.0` shows `Status: COMPLETED` once it's done. The 100 slots hold
96 blocks: the chain [skipped](/providers/solana#skipped-slots) four.
:::

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

## Tables

* [`block_headers`](/datasets/tables/solana#block_headers): one row per block, with its slot, parent slot, hashes,
  height, and time.
* [`transactions`](/datasets/tables/solana#transactions): one row per transaction, with its signatures, error, fee,
  balances before and after, and log messages.
* [`messages`](/datasets/tables/solana#messages): each transaction's message, with its accounts and recent block hash.
* [`instructions`](/datasets/tables/solana#instructions): one row per instruction, inner instructions included.
* [`block_rewards`](/datasets/tables/solana#block_rewards): the rewards paid in each block.

[Solana functions](/querying/functions/solana) decode Borsh instruction data and Anchor events in SQL.

## Old Faithful archive

Old Faithful holds Solana mainnet's history as one file per epoch, 432,000 slots or about a day. It publishes an
epoch once it's over, so it trails the chain by one or two epochs. A deployment's `--backfill-strategy` decides where
the job reads the slots before the chain's head:

* `archive-then-rpc`, the default: from the archive as far as it goes, then the rest from the RPC endpoint. A range that
  starts in the current epoch skips the archive, which can't have it yet.
* `rpc-only`: everything from the RPC endpoint, which must then hold the whole range.

New slots always come from the RPC endpoint. The archive only holds mainnet: for devnet or testnet, deploy with
`--backfill-strategy rpc-only`.

```bash
ampctl dataset deploy acme/solana_devnet@1.0.0 --backfill-strategy rpc-only
```

An epoch's file runs to hundreds of gigabytes, and how much of it Amp reads depends on the deployment's
[`--parallelism`](/clis/ampctl/dataset#--parallelism-parallelism):

* With `1`, the default, Amp reads the file from the epoch's first slot, so a range that starts late in an epoch takes
  as long as reading the slots before it.
* With more, Amp first reads the archive's index of the file, then reads only the parts that hold the range, that many
  at once. Reading the index takes minutes: a range of 100 slots, 85,000 slots into an epoch, took 12 minutes with
  `--parallelism 8`, 11 of them on the index.

The archive limits clients that open too many connections, so Amp halves its requests when the archive pushes back, and
raises them again as requests succeed, up to `old_faithful_archive.max_concurrent_requests` (`64` by default).

:::warning
Amp v0.0.42 can't read the archive's epochs from 1064 on, which starts at slot 459,648,000, on October 21, 2026: the
job fails for good with
[`Block node has 5 elements, expected 3`](/providers/solana#car-read-error-block-node-has-5-elements-expected-3). Deploy ranges past
slot 459,647,999 with `--backfill-strategy rpc-only`, and leave out the first slot of each epoch, which v0.0.42 can't
read over RPC either (see [`unknown variant VATDebit`](/providers/solana#unknown-variant-vatdebit)).
:::

To read files you downloaded instead, point `old_faithful_archive.local_dir` to their directory, on the machine `ampd`
runs on. Keep the archive's file names, like `epoch-0.car`:

```bash
mkdir -p /data/solana/car
curl -o /data/solana/car/epoch-0.car https://files.old-faithful.net/0/epoch-0.car
```

Amp reads epochs from the directory until one is missing, then reads the rest from the RPC endpoint.

## Truncated logs

RPC endpoints cut a transaction's log messages short past a size limit, and end them with `Log truncated`. Solana's
public endpoint cut 48 of the 5,052 transactions in five slots of the example's dataset. The archive can store them
that way too, but less often: it had all 48 whole. With `fallback_rpc_provider_info`, Amp fetches each truncated
transaction again from that endpoint, and keeps its logs when they're complete and longer. Point it to an endpoint with
a higher limit, or none, or it can't fill them in. Without it, `log_messages` keeps the truncated list.
`max_rpc_calls_per_second` doesn't apply to the fallback endpoint.

## Commitment

`commitment` is the commitment level Amp requests blocks at: `finalized` (the default), `confirmed`, or `processed`.
Whatever it's set to, Amp follows the chain's finalized slot, so a Solana dataset only holds finalized slots, and the
manifest's `finalized_blocks_only` makes no difference. Leave `commitment` at its default.

## Transaction encoding

`transaction_encoding` sets how the RPC endpoint sends each transaction. Under `json`, the default, the endpoint
decodes each transaction into verbose JSON, which is slower and several times larger. Under `base64`, it sends the
transaction's binary form, and Amp decodes it. The extracted rows are the same either way, so prefer `base64`. Some RPC
nodes send version 1 transactions in a layout Amp can't decode, and the job fails with
`malformed transaction bytes at slot …`: use `json` with those endpoints.

## Following the chain

Amp tracks the chain's latest finalized slot in the background. Without a WebSocket endpoint, it asks the RPC endpoint
(`getSlot`) every `chain_head_refresh_interval_ms`, `1000` (one second) by default. That interval is how long a newly
finalized slot can wait before Amp extracts it, and each check counts against `max_rpc_calls_per_second`.

With `ws_provider_info`, Amp subscribes to new slots (`slotSubscribe`), and learns of each newly finalized slot as the
endpoint reports it. It asks the RPC endpoint only when the subscription has been quiet for longer than the interval,
and for good if the endpoint refuses the subscription. The URL takes `ws://` or `wss://`, and is usually a different
host or path than the RPC endpoint, not the same URL with another scheme. Amp uses it only to follow the chain: blocks
always come over HTTP.

## Skipped slots

Not every slot produces a block. Amp uses slot numbers as block numbers, so `_block_num` equals `slot`, and a skipped
slot has no rows: block numbers have gaps, and a range of slots holds fewer blocks than its length. In the example's
dataset, slots 456,192,072 to 456,192,075 have no block, and the block at 456,192,076 names 456,192,071 as its parent:

:::terminal
```bash
ampsql 'SELECT slot, parent_slot, block_height FROM "acme/solana_mainnet".block_headers WHERE slot BETWEEN 456192070 AND 456192077 ORDER BY slot'
```

```
+-----------+-------------+--------------+
| slot      | parent_slot | block_height |
+-----------+-------------+--------------+
| 456192070 | 456192069   | 434228855    |
| 456192071 | 456192070   | 434228856    |
| 456192076 | 456192071   | 434228857    |
| 456192077 | 456192076   | 434228858    |
+-----------+-------------+--------------+
```
:::

Over RPC, before Amp treats a slot as skipped, it checks that the next block's parent comes before it, so an endpoint
missing a block can't make Amp drop it. That costs two more requests per skipped slot, one of which downloads the next
block. The archive needs no check.

Set a [start block](/datasets/raw-datasets#start-block) only to a slot that has a block, which the RPC method
`getBlocks` lists. Amp counts the dataset as synced only once it has the start block itself, which never exists if the
chain skipped that slot.

## Verification

Deploy with `--verify solana` to check each block as it's extracted: Amp parses its hashes and verifies every
transaction's signatures against its message, rebuilt from the `transactions`, `messages`, and `instructions` rows. A
block that fails is extracted again. To check blocks already extracted, run [`ampctl verify`](/clis/ampctl/verify):

```bash
ampctl verify --encoding solana --dataset acme/solana_mainnet@1.0.0 --start-block 456192000 --end-block 456192099
```

:::warning
Amp v0.0.42 can't verify version 1 transactions, which Solana has carried since epoch 1036, in September 2026. Both ways
fail on any slot that holds one, like the example's: extraction with
`unsupported transaction version 'v1' for slot 456192001, tx index 1`, retrying without end, and `ampctl verify` with
`Verification worker(s) failed`. See [Verification](/managing-amp/verification#solana).
:::

## Limitations

* A Solana dataset's start block must be a slot that has a block.
* The archive only holds Solana mainnet, and trails the chain by one or two epochs.
* Amp v0.0.42 can't read the archive's epochs from 1064 on, the first block of those epochs over RPC, or version 1
  transactions when it verifies. So a v0.0.42 deployment that follows the chain fails for good at the next epoch.
* Amp extracts blocks over HTTP only. The WebSocket endpoint only follows the chain.
* `max_rpc_calls_per_second` applies to each provider on its own, not to all of them together.

## Errors

### `HTTP status client error (429 Too Many Requests)`

The RPC endpoint refused requests over its rate limit, and the job failed with an error like
`failed to get block for slot 464000381`, followed by this one. Amp retries the job, but starts its range over each
time, so an endpoint whose limit is lower than Amp's request rate never lets it finish. Lower
`max_rpc_calls_per_second` to the endpoint's limit, or use an endpoint with a higher one. Solana's public endpoint
allows about six `getBlock` calls every 10 seconds, below the lowest limit you can set, one call per second: use it
for ranges the archive serves, or a few slots over RPC.

### `CAR read error`, `Block node has 5 elements, expected 3`

Amp v0.0.42 can't read the archive's files from epoch 1064 on, and fails the job for good. Deploy ranges from slot
459,648,000 on with `--backfill-strategy rpc-only`.

### `unknown variant VATDebit`

The full message is
`` unknown variant `VATDebit`, expected one of `Fee`, `Rent`, `Staking`, `Voting`, `DeactivatedStake` ``. The RPC
endpoint sent a block with a kind of reward Amp v0.0.42 doesn't know, which the first block of each epoch has from epoch
1064 on, like slot 459,648,000. The job fails for good. Deploy a range that leaves out those slots.

### `failed to read the endpoint's initial chain head`

The RPC endpoint didn't answer `getSlot` when the job started. Check its URL, its token, and that it's reachable from
the machine `ampd` runs on.

### `invalid archive directory: /data/solana/car`

`old_faithful_archive.local_dir` isn't a directory on the machine `ampd` runs on. Create it, or fix the path.

### `unsupported endpoint URL scheme https, expected one of: ws, wss`

`ws_provider_info.url` names an HTTP endpoint. Use the provider's WebSocket endpoint. The RPC URLs take `http` or
`https`, and fail the same way with any other scheme.

### `provider pool has incompatible extraction settings`

Several Solana providers serve the dataset's network, with a different `commitment` or `old_faithful_archive.local_dir`.
Give them the same values, or deploy with `--provider` to use only one.
