From bf6f434e943b3fb9429630c763930df103618c5d Mon Sep 17 00:00:00 2001 From: alchemy-bot <80712764+alchemy-bot@users.noreply.github.com> Date: Fri, 31 Jul 2026 22:08:45 +0000 Subject: [PATCH 1/8] [docs-agent] Add Solana Account Archive guide, OpenRPC entry, CU pricing row Adds the initial docs for Solana Account Archive: an extension of `getAccountInfo` with three new mutually-exclusive parameters (`slot`, `lastUpdateBeforeSlot`, `firstUpdateAfterSlot`) for point-in-time and cursor-based historical reads. Coverage extends back to July 2025 with no pruning. Changes: * New guide + FAQ page at content/api-reference/solana/account-archive.mdx sourced from the internal Notion doc. * New OpenRPC spec at src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml with three worked examples (point-in-time, walk-backward, walk-forward) and error -32020 documented for cursor-past-coverage. * docs.yml: new 'Solana Account Archive' section under Solana with the guide page + flattened endpoints entry. * compute-unit-costs.mdx: new 'Solana: Account Archive' block with TBD rows and a note that final per-method pricing is pending. Assumptions flagged for reviewer confirmation: * Endpoint host uses the standard `solana-mainnet.g.alchemy.com/v2/{apiKey}` (drop-in with normal Solana calls). Easy to switch to a distinct archive host if that's the actual routing. * Chain scope: mainnet only for v1 (no devnet/testnet server entries). Add a devnet server if the archive is available there. * No preview / private-beta callout on the guide. Add one if this ships behind an allowlist. * CU pricing left as TBD per the original ask ("temporary for now if you dont have per method CU pricing"). Refs DOCS-168 Requested-by: @clinder777 --- .../pricing/compute-unit-costs.mdx | 10 + .../api-reference/solana/account-archive.mdx | 198 ++++++++++++++ content/docs.yml | 6 + .../solana-account-archive.yaml | 255 ++++++++++++++++++ 4 files changed, 469 insertions(+) create mode 100644 content/api-reference/solana/account-archive.mdx create mode 100644 src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml diff --git a/content/api-reference/pricing-resources/pricing/compute-unit-costs.mdx b/content/api-reference/pricing-resources/pricing/compute-unit-costs.mdx index 7c40101b2..146176c70 100644 --- a/content/api-reference/pricing-resources/pricing/compute-unit-costs.mdx +++ b/content/api-reference/pricing-resources/pricing/compute-unit-costs.mdx @@ -158,6 +158,16 @@ For more details, check out the [Compute Units](/docs/reference/compute-units#wh * To view the batch request breakdown in the Alchemy Dashboard, click on "raw request" +# Solana: Account Archive + +The [Solana Account Archive](/docs/solana/account-archive) is served through the standard Solana Alchemy endpoint (`https://solana-mainnet.g.alchemy.com/v2/{apiKey}`). It extends `getAccountInfo` with `slot`, `lastUpdateBeforeSlot`, and `firstUpdateAfterSlot` parameters for historical queries. Per-method compute unit pricing for archive lookups is being finalized and will be published here once confirmed. Contact your Alchemy point of contact for interim pricing details. + +| Method | CU | Throughput CU | +| ----------------------------------------------- | --- | ------------- | +| getAccountInfo (archive: `slot` set) | TBD | | +| getAccountInfo (archive: `lastUpdateBeforeSlot`) | TBD | | +| getAccountInfo (archive: `firstUpdateAfterSlot`) | TBD | | + # Solana: DAS APIs (NFT/Token) {/* cu:auto product="solana-das" */} diff --git a/content/api-reference/solana/account-archive.mdx b/content/api-reference/solana/account-archive.mdx new file mode 100644 index 000000000..220963787 --- /dev/null +++ b/content/api-reference/solana/account-archive.mdx @@ -0,0 +1,198 @@ +--- +title: Solana Account Archive +description: Query the state of any Solana account at any historical slot with a drop-in extension to getAccountInfo that adds slot, lastUpdateBeforeSlot, and firstUpdateAfterSlot cursor parameters. +subtitle: Answer "what did this account look like at slot N" with a single getAccountInfo call +slug: docs/solana/account-archive +--- + +Solana Account Archive answers `getAccountInfo` for any Solana account at any historical slot (vote accounts and per-slot sysvars excepted), with a deep, ever-growing window of historical coverage that is never pruned. Use the `slot` parameter to read state at a point in time, or `lastUpdateBeforeSlot` and `firstUpdateAfterSlot` to walk an account's update history. Reads are served with median latency in the microseconds. + +## Solana only remembers the present + +Here's a detail about Solana that surprises even experienced builders: **a validator stores exactly one copy of each account, the latest one.** Every write overwrites the previous state in place, and so the moment an account is touched, whatever it looked like before is gone. Even for a slot the node still has full blocks for, the *account state* at that slot no longer exists anywhere on the machine. + +This is a feature, not a bug. It's part of why Solana is fast. But it means a whole class of very reasonable questions have no answer, like: + +* **Debugging:** "Our liquidation fired at slot 285,401,337. What was the oracle price account at that exact slot?" +* **Backtesting:** "Reconstruct this pool's reserves at every point over the last six months." +* **Audits:** "Prove what this token account held on March 3rd." +* **Recovery:** "Our indexer was down for four hours. Rebuild exactly the state transitions we missed." + +You might think `getBlock` covers this, but it doesn't. It returns a past slot's transactions and their pre/post *balances*, not account *data*. For most applications, the state that matters lives in the data bytes: the order book, price feed, position, or configuration. + +Solana Account Archive brings the full history back. It answers `getAccountInfo` for any account at any slot since July 2025, using the same request and response shapes you already use. Two things set it apart. The archive never prunes, so the queryable window only grows over time. And it skips the chain's own per-slot bookkeeping (vote accounts and a handful of sysvars that get rewritten every slot), since that's noise, not useful history for app builders. + +## The API: `getAccountInfo`, extended + +We deliberately did not invent a new method. The archive speaks standard JSON-RPC and implements [`getAccountInfo`](/docs/chains/solana/solana-api-endpoints/get-account-info) with all the config options you already know: `encoding` (`base64`, `base58`, `base64+zstd`, `jsonParsed`), `commitment`, `dataSlice`, and `minContextSlot`, plus three new, mutually exclusive parameters: + +| Parameter | Semantics | Use it for | +| --- | --- | --- | +| `slot` | State *as of* slot `S` (inclusive: the latest write with `slot <= S`) | Point-in-time snapshots | +| `lastUpdateBeforeSlot` | The most recent write *strictly before* `S` | Walking an account's history backward | +| `firstUpdateAfterSlot` | The first write *strictly after* `S` | Walking an account's history forward | + +These three are also mutually exclusive with `minContextSlot`; pairing it with any of them is rejected. Omit all three and you get a normal, latest-state `getAccountInfo`; the archive is a drop-in superset. Here is a complete point-in-time read, a token account's state as of slot 310,000,000: + +```bash +curl https://solana-mainnet.g.alchemy.com/v2/{apiKey} \ + -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getAccountInfo", + "params": [ + "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", + { "encoding": "jsonParsed", "slot": 310000000 } + ] + }' +``` + +The response is the standard `getAccountInfo` shape: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "context": { "slot": 310000000 }, + "value": { + "lamports": 2039280, + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "data": { "program": "spl-token", "parsed": { "...": "..." } }, + "executable": false, + "rentEpoch": 361, + "space": 165 + } + } +} +``` + +Note the semantics: `slot: S` means *state as of S*. If the account was last written at slot `S - 40,000` and untouched since, you get that write, exactly what any program executing at slot `S` would have seen. And `jsonParsed` works on historical state too: the same decoders a standard RPC node uses are applied to the historical bytes, so a token account from early in the coverage window comes back decoded, not opaque. + +## An iterator over an account's history + +`lastUpdateBeforeSlot` and `firstUpdateAfterSlot` turn the archive into an *iterator over every state transition of an account*. + +`lastUpdateBeforeSlot` returns the *actual slot* of the write it found in `context.slot`, so each response is the cursor for the next request: + +```python +# Walk every state transition of an account, newest to oldest. +cursor = current_slot + 1 +while True: + try: + result = rpc("getAccountInfo", [pubkey, {"lastUpdateBeforeSlot": cursor}]) + except RpcError as e: + if e.code == -32020: # walked past the edge of coverage; done + break + raise + if result["value"] is None: # the account had no earlier writes + break + slot = result["context"]["slot"] # the slot this write landed at + handle(slot, result["value"]) + cursor = slot # strictly-before: no overlap, no gaps +``` + +Iterating through an account's history has two possible stopping points: a `null` value means the account genuinely had no earlier writes, while error `-32020` means the cursor reached the edge of the archive's coverage. Keeping them distinct lets you differentiate between "this account's history begins here" and "history continues below what we've indexed." + +`firstUpdateAfterSlot` does the reverse: instead of stepping back in time, it moves forward, returning the next write at or after a given slot. It's ideal for an indexer catching up from a known slot that wants every intermediate state, not just the latest. + +Between the two, you can walk an account's entire lifecycle: every balance change, every data mutation, every ownership change, with the exact slot each one landed at. + +## How the archive stays trustworthy + +The archive listens to the network through a Geyser stream and records account writes as they happen, with two rules that keep the record trustworthy: + +**Only finalized slots enter the archive.** Solana forks constantly at the tip, and a block that looks real for a few seconds can simply vanish. The archive holds each slot's updates back until the network finalizes it, so the permanent record contains only what actually happened. Recent unfinalized state is still served for `processed` and `confirmed` queries from a separate short-lived tier. + +**Nothing is silently lost.** Every Solana block names its parent, so any block the stream misses (a hiccup, a reconnect, a service deploy) is detected immediately and backfilled. + +The storage layer underneath is a story of its own. What matters here is the result: point-in-time reads are fast enough to sit in a hot path, with **median latency in the microseconds**, measured at our internal service layer. + +## Rebuilding the past year + +Ingesting from today onward gets you an archive that's useful *next* year. We wanted the past year too, which means reconstructing account state for tens of millions of slots that had already happened, on a chain that keeps no history. And it's a lot of history: Solana produces a block roughly every 400 ms, about 216,000 slots per day. Over a year that's **~78 million slots, hundreds of billions of account updates, and more than a petabyte of raw account data**, each slot carrying thousands of account writes with the full data payload attached. + +### Why "just parse the transactions" doesn't work + +There is a tempting shortcut to fetch historical blocks and decode what each transaction did to each account. It fails for a fundamental reason that's worth internalizing: **a Solana transaction is not a description of a state change; it's a program invocation.** + +What actually happens to an account depends on the program's execution: CPIs fanning out into other programs, sysvars read mid-flight, compute metering, the precise semantics of the runtime *at that slot*. The block records which accounts a transaction touched and how balances moved, but the data bytes, the part you actually want, are determined only by running the code. + +There is only one faithful way to know what a transaction did to an account: **execute it, with the real runtime, against the real state it executed against.** + +### Replay: running the chain again + +So that's what we do. The backfill pipeline is, in essence, a validator that relives history: + +1. **Boot from a trusted snapshot.** The Solana Foundation maintains public archives of historical snapshots: full captures of every account at a given slot. Loading the snapshot at slot `A` gives us the complete, canonical state of several hundred million accounts at that moment. +2. **Replay every block forward.** From `A + 1`, each historical block runs through the actual validator runtime: real execution, not simulation, not log-parsing. Every transaction executes; the account writes the runtime produces are captured and indexed, slot by slot. +3. **Stop at the next snapshot and prove it.** This is the step that makes the whole thing trustworthy. When replay reaches slot `B`, where the next canonical snapshot exists, we compare our replayed end-state against it, including the **accounts lattice hash**: a cryptographic commitment to the entire account set that the network itself computes and agrees on. If a single byte of a single account diverged anywhere in the range, the hashes won't match and the range is rejected. We don't *assume* replay is correct; every backfilled range is checked against consensus ground truth before it's trusted. + +### Old blocks need the runtime from their era + +Here's the wrinkle that makes replay genuinely hard: **Solana's execution semantics are versioned in time.** Feature gates activate at specific epochs, compute budget rules change, syscalls get added, edge-case behaviors get fixed. A transaction from twelve months ago must be replayed by a runtime that behaves exactly as the cluster did *at that slot*. Replay it with today's validator and step 3 will tell you, loudly, that you manufactured a history that never happened. + +In practice, the backfill fleet runs several pinned validator lineages, each responsible for the era it can faithfully reproduce, with work chunked along epoch boundaries so that every chunk begins and ends at a verifiable snapshot. Extending the archive further back is largely a matter of standing up the right runtime era and paying the replay compute. And because each epoch-aligned chunk is independent, history is perfectly parallel; backfilling a year is mostly a question of how many replay workers you run at once. + +## Trust, continuously verified + +Hash-verified backfill covers the past; a separate concern is whether the *serving path* stays honest in production. An independent watchdog continuously cross-validates the archive's answers against live RPC nodes, through the same public API you'd use. Correctness is paramount to us, and so it's re-verified every minute the service runs. A multi-layer approach, consensus-anchored hashes for backfilled history, finalization-gated writes at the tip, and continuous live cross-validation on top, ensures that you can trust a record of truth moving forward in time. + +## What you can build with this + +Every question from the top of this page is now an API call: + +* **"What was the oracle account at that exact slot?"** One `getAccountInfo` with `slot` set. Pull the exact state of every account a failing transaction read, at the slot it executed. No more reconstructing oracle inputs from screenshots and guesswork. +* **"Reconstruct this pool's reserves over six months."** Sample the pool account at a fixed slot cadence and backtest against what was actually on chain, not an approximation stitched together from trade events. +* **"Prove what this token account held on March 3rd."** "As of slot S, account X contained exactly these bytes." That is answerable, and anchored to hashes the network itself agreed on. +* **"Rebuild the state transitions our indexer missed."** Page forward with `firstUpdateAfterSlot` from your last known slot and receive every intermediate state, in order. + +And one the intro didn't ask: **balance and position history without running an indexer at all.** Walk a token account backward with `lastUpdateBeforeSlot` and you have its complete timeline. + +## Get started + +Solana stays fast by keeping validators lean and focused on current state. Historical account state has simply lived elsewhere, and until now the ecosystem has worked around that. Solana Account Archive changes that. With finalization-gated live ingestion and a backfill pipeline that re-executes history through era-faithful runtimes and proves the result against consensus hashes, historical `getAccountInfo` becomes just another RPC call: same method, same response shape, one extra parameter. + +Everything the archive indexes is compressed, stored with at least 3 replicas for redundancy, and never pruned, so the queryable window only grows. + +[Get started on Solana](/docs/reference/solana-api-quickstart), and [reach out to us](https://www.alchemy.com/contact-sales) if your use case needs deeper history, or you want to explore specialized solutions and pricing at scale. + +## FAQ + +### How do I get a Solana account's state at a specific slot? + +Call `getAccountInfo` with the account's pubkey and a `slot` parameter in the config object: `getAccountInfo(pubkey, { "slot": S })`. The response is the standard `getAccountInfo` shape and returns the account's state as of slot `S`, the latest write at or before `S`. + +### How do I get the full update history of a Solana account? + +Page with the cursor parameters. `getAccountInfo(pubkey, { "lastUpdateBeforeSlot": S })` returns the most recent write strictly before `S`, with the write's actual slot in `context.slot`; feed that slot back in as the next cursor to walk backward through every state transition. `firstUpdateAfterSlot` walks forward the same way. + +### What's the difference between `slot`, `lastUpdateBeforeSlot`, and `firstUpdateAfterSlot`? + +`slot` is a point-in-time read: state *as of* slot `S`, inclusive. `lastUpdateBeforeSlot` and `firstUpdateAfterSlot` are exclusive history cursors: the nearest write strictly before or strictly after `S`. The three are mutually exclusive; omit all of them for a normal latest-state read. + +### Does `jsonParsed` encoding work for historical account state? + +Yes. The same account decoders a standard Solana RPC node uses are applied to the historical bytes, so token accounts, mints, and other known program accounts come back parsed at any slot in coverage. All standard encodings work: `base64`, `base58`, `base64+zstd`, and `jsonParsed`, plus `dataSlice`. + +### How far back can I query? + +Coverage currently extends back to July 2025, with backfill extending it further back over time. History is never pruned, so everything indexed stays queryable and the window only grows. + +### How is the historical data known to be correct? + +Backfilled ranges are produced by re-executing every transaction through the validator runtime of the corresponding era, and the resulting state is verified against the network's own consensus artifacts, including the accounts lattice hash from canonical snapshots. Live ingestion commits only finalized slots, and an independent watchdog continuously cross-checks served results against live RPC nodes. + +### Are all accounts covered? + +All accounts except pure per-slot chain bookkeeping: vote accounts and the three sysvars rewritten every slot (`SlotHashes`, `SlotHistory`, `RecentBlockhashes`) are excluded from the index. Program accounts, token accounts, mints, PDAs, wallets, and the remaining sysvars (Clock, Rent, and so on) are all covered. + +### Do I need a new SDK or client? + +No. Any Solana JSON-RPC client works. The archive implements standard `getAccountInfo`, and the historical parameters are just extra fields in the existing config object. + +### What error do I get when I query past the archive's coverage? + +Error code `-32020`. This is distinct from `value: null`, which means the account genuinely had no earlier (or later) writes at all. Handling them separately lets you tell "this account's history begins here" from "history continues below what we've indexed." diff --git a/content/docs.yml b/content/docs.yml index 6b546499c..cb94ccf51 100644 --- a/content/docs.yml +++ b/content/docs.yml @@ -332,6 +332,12 @@ navigation: path: api-reference/solana/solana-api-overview.mdx - api: Solana API Endpoints api-name: solana + - section: Solana Account Archive + path: api-reference/solana/account-archive.mdx + contents: + - api: Solana Account Archive Endpoints + api-name: solana-account-archive + flattened: true - link: Solana DAS APIs href: /docs/reference/alchemy-das-apis-for-solana - link: Solana DAS APIs v2 diff --git a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml new file mode 100644 index 000000000..8dd762c90 --- /dev/null +++ b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml @@ -0,0 +1,255 @@ +# yaml-language-server: $schema=https://meta.open-rpc.org/ + +$schema: https://meta.open-rpc.org/ +openrpc: 1.2.4 +info: + title: Alchemy Solana Account Archive JSON-RPC Specification + description: |- + JSON-RPC specification for Solana Account Archive, Alchemy's historical + account state service. Answers `getAccountInfo` for any Solana account at + any historical slot from July 2025 onward, using the same request and + response shapes as standard Solana RPC. Coverage is never pruned, so the + queryable window only grows over time. + + The archive extends `getAccountInfo` with three mutually-exclusive + parameters (`slot`, `lastUpdateBeforeSlot`, `firstUpdateAfterSlot`) that + also cannot be combined with `minContextSlot`. Omit all three to get a + normal, latest-state read: the archive is a drop-in superset of standard + `getAccountInfo`. + + Vote accounts and the three per-slot sysvars (`SlotHashes`, `SlotHistory`, + `RecentBlockhashes`) are excluded from the archive. All other accounts, + including program accounts, token accounts, mints, PDAs, wallets, and the + remaining sysvars, are covered. + version: 0.0.0 +servers: + - url: https://solana-mainnet.g.alchemy.com/v2 + name: Solana Mainnet +methods: + - name: getAccountInfo + summary: >- + Returns account information at a specific slot, or walks an account's + update history using cursor parameters. + description: |- + Returns all information associated with the account at the given Pubkey, + with optional historical lookups. Supports three mutually-exclusive + history parameters in the configuration object: + + - `slot`: state *as of* slot `S` (inclusive: the latest write with + `slot <= S`). Use for point-in-time snapshots. + - `lastUpdateBeforeSlot`: the most recent write *strictly before* slot + `S`. Use to walk an account's history backward. + - `firstUpdateAfterSlot`: the first write *strictly after* slot `S`. + Use to walk an account's history forward or catch an indexer up from + a known slot. + + These three are also mutually exclusive with `minContextSlot`; pairing + it with any of them returns an invalid-params error. Omit all three for + a normal, latest-state `getAccountInfo`. + + The response is the standard `getAccountInfo` shape. When using + `lastUpdateBeforeSlot` or `firstUpdateAfterSlot`, the `context.slot` + field returns the actual slot of the write that was found, so each + response is the cursor for the next request. + + `jsonParsed` encoding works on historical state: the same decoders a + standard RPC node uses are applied to the historical bytes. + x-compute-units: 10 + params: + - name: Pubkey + required: true + description: Pubkey of the account to query (base-58 encoded). + schema: + $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/Pubkey + - name: Configuration + required: false + description: >- + Optional configuration object. Includes the standard `getAccountInfo` + options (`encoding`, `commitment`, `dataSlice`, `minContextSlot`) + plus the three mutually-exclusive archive parameters. + schema: + $ref: "#/components/schemas/GetAccountInfoArchiveConfig" + examples: + - name: Point-in-time read at slot 310,000,000 + params: + - name: Pubkey + value: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + - name: Configuration + value: + encoding: jsonParsed + slot: 310000000 + result: + name: Account information at slot + value: + context: + apiVersion: "2.3.3" + slot: 310000000 + value: + lamports: 2039280 + owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + data: + program: "spl-token" + space: 165 + parsed: + type: "account" + info: + isNative: false + mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + owner: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + state: "initialized" + tokenAmount: + amount: "1500000" + decimals: 6 + uiAmount: 1.5 + uiAmountString: "1.5" + executable: false + rentEpoch: 361 + space: 165 + - name: Walk history backward with lastUpdateBeforeSlot + params: + - name: Pubkey + value: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + - name: Configuration + value: + encoding: base64 + lastUpdateBeforeSlot: 310000000 + result: + name: Previous write on the account + value: + context: + apiVersion: "2.3.3" + slot: 309998412 + value: + lamports: 2039280 + owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + data: + - "PGJhc2U2NC1lbmNvZGVkIGFjY291bnQgYnl0ZXM+" + - "base64" + executable: false + rentEpoch: 361 + space: 165 + - name: Walk history forward with firstUpdateAfterSlot + params: + - name: Pubkey + value: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + - name: Configuration + value: + encoding: base64 + firstUpdateAfterSlot: 300000000 + result: + name: Next write on the account + value: + context: + apiVersion: "2.3.3" + slot: 300001527 + value: + lamports: 2039280 + owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" + data: + - "PGJhc2U2NC1lbmNvZGVkIGFjY291bnQgYnl0ZXM+" + - "base64" + executable: false + rentEpoch: 355 + space: 165 + result: + name: Account information + description: >- + Returns the standard `getAccountInfo` shape. `context.slot` reflects + the actual slot at which the write was found (for cursor queries) or + the requested `slot` (for point-in-time reads). `value` is `null` + when the account has no writes matching the query (for example, a + `lastUpdateBeforeSlot` cursor for an account with no earlier writes). + schema: + $ref: "#/components/schemas/AccountInfoArchiveResult" + errors: + - code: -32020 + message: >- + Cursor walked past the edge of the archive's coverage window. This + is distinct from a `null` value response, which means the account + genuinely had no earlier (or later) writes. Coverage currently + extends back to July 2025 and grows over time. + - code: -32602 + message: >- + Invalid params. Common causes include combining more than one of + `slot`, `lastUpdateBeforeSlot`, and `firstUpdateAfterSlot`, + combining any of those three with `minContextSlot`, a slot value + that is not a non-negative integer, or a malformed Pubkey. + - code: -32601 + message: Method not found. + - code: -32603 + message: >- + Internal error. Retry the request; if it persists, contact support. + +components: + schemas: + GetAccountInfoArchiveConfig: + title: getAccountInfo (Archive) Configuration + type: object + description: >- + Configuration for `getAccountInfo` with archive extensions. `slot`, + `lastUpdateBeforeSlot`, and `firstUpdateAfterSlot` are mutually + exclusive with each other and with `minContextSlot`; omit all three + for a normal latest-state read. + properties: + commitment: + $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/Commitment + encoding: + $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/Encoding + description: Encoding format for account data. + dataSlice: + $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/DataSlice + minContextSlot: + $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/MinContextSlot + slot: + type: integer + minimum: 0 + description: >- + Return the account's state as of slot `S` (inclusive). The + response is the latest write with `slot <= S`. Mutually + exclusive with `lastUpdateBeforeSlot`, `firstUpdateAfterSlot`, + and `minContextSlot`. + lastUpdateBeforeSlot: + type: integer + minimum: 0 + description: >- + Return the most recent write to this account strictly before + slot `S`. The response's `context.slot` is the actual slot at + which that write landed and can be used as the cursor for the + next call. Mutually exclusive with `slot`, + `firstUpdateAfterSlot`, and `minContextSlot`. + firstUpdateAfterSlot: + type: integer + minimum: 0 + description: >- + Return the first write to this account strictly after slot `S`. + The response's `context.slot` is the actual slot at which that + write landed and can be used as the cursor for the next call. + Mutually exclusive with `slot`, `lastUpdateBeforeSlot`, and + `minContextSlot`. + + AccountInfoArchiveResult: + title: getAccountInfo (Archive) Result + type: object + properties: + context: + type: object + properties: + apiVersion: + type: string + description: RPC node API version. + slot: + type: integer + description: >- + The slot at which the returned state was written. For + point-in-time reads (`slot` parameter set), this is the + requested slot. For cursor reads + (`lastUpdateBeforeSlot` / `firstUpdateAfterSlot`), this is + the actual slot of the write that was found and can be used + as the next cursor. + value: + oneOf: + - $ref: ../../chains/_components/solana/account.yaml#/components/schemas/AccountInfo + - type: "null" + description: >- + The account's state, or `null` when there is no write matching + the query. From a6d64051b43da345bd60feae2c7488f10d06066a Mon Sep 17 00:00:00 2001 From: alchemy-bot <80712764+alchemy-bot@users.noreply.github.com> Date: Sat, 1 Aug 2026 00:04:17 +0000 Subject: [PATCH 2/8] [docs-agent] Drop hard-coded x-compute-units from archive getAccountInfo Codex flagged the inconsistency between the OpenRPC spec advertising x-compute-units: 10 and the pricing page showing TBD for the three archive params. Since Canaan's original ask was to leave pricing as TBD until topconfig.yml values land, remove the hardcoded CU value from the spec so both surfaces agree. Refs DOCS-168 Requested-by: @clinder777 --- .../alchemy/solana-account-archive/solana-account-archive.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml index 8dd762c90..2805fd404 100644 --- a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml +++ b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml @@ -54,7 +54,6 @@ methods: `jsonParsed` encoding works on historical state: the same decoders a standard RPC node uses are applied to the historical bytes. - x-compute-units: 10 params: - name: Pubkey required: true From d44370e066e485cddb5f605327a260166aaf6ddb Mon Sep 17 00:00:00 2001 From: deepak <96074752+deepakbnsl@users.noreply.github.com> Date: Mon, 3 Aug 2026 07:36:30 -0500 Subject: [PATCH 3/8] [docs-agent] Fix null semantics, use real slot-400M examples verified against prod Verified against the live endpoint: cursor walks terminate only with -32020 (a null value mid-walk is a deletion write, not end-of-history), slot 310M is below the coverage floor (~353.9M) so examples move to slot 400M, and all example responses are now real output for the top USDC token account. Also drops the excluded-accounts sentence (kept in FAQ) and the unfinalized-tier parenthetical, matching the internal Notion doc. Co-authored-by: Cursor --- .../api-reference/solana/account-archive.mdx | 51 +++++++++----- .../solana-account-archive.yaml | 67 ++++++++++--------- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/content/api-reference/solana/account-archive.mdx b/content/api-reference/solana/account-archive.mdx index 220963787..75d134ce1 100644 --- a/content/api-reference/solana/account-archive.mdx +++ b/content/api-reference/solana/account-archive.mdx @@ -20,7 +20,7 @@ This is a feature, not a bug. It's part of why Solana is fast. But it means a wh You might think `getBlock` covers this, but it doesn't. It returns a past slot's transactions and their pre/post *balances*, not account *data*. For most applications, the state that matters lives in the data bytes: the order book, price feed, position, or configuration. -Solana Account Archive brings the full history back. It answers `getAccountInfo` for any account at any slot since July 2025, using the same request and response shapes you already use. Two things set it apart. The archive never prunes, so the queryable window only grows over time. And it skips the chain's own per-slot bookkeeping (vote accounts and a handful of sysvars that get rewritten every slot), since that's noise, not useful history for app builders. +Solana Account Archive brings the full history back. It answers `getAccountInfo` for any account at any slot since July 2025, using the same request and response shapes you already use. The archive never prunes, so the queryable window only grows over time. ## The API: `getAccountInfo`, extended @@ -32,7 +32,7 @@ We deliberately did not invent a new method. The archive speaks standard JSON-RP | `lastUpdateBeforeSlot` | The most recent write *strictly before* `S` | Walking an account's history backward | | `firstUpdateAfterSlot` | The first write *strictly after* `S` | Walking an account's history forward | -These three are also mutually exclusive with `minContextSlot`; pairing it with any of them is rejected. Omit all three and you get a normal, latest-state `getAccountInfo`; the archive is a drop-in superset. Here is a complete point-in-time read, a token account's state as of slot 310,000,000: +These three are also mutually exclusive with `minContextSlot`; pairing it with any of them is rejected. Omit all three and you get a normal, latest-state `getAccountInfo`; the archive is a drop-in superset. Here is a complete point-in-time read, a token account's state as of slot 400,000,000: ```bash curl https://solana-mainnet.g.alchemy.com/v2/{apiKey} \ @@ -43,8 +43,8 @@ curl https://solana-mainnet.g.alchemy.com/v2/{apiKey} \ "id": 1, "method": "getAccountInfo", "params": [ - "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM", - { "encoding": "jsonParsed", "slot": 310000000 } + "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa", + { "encoding": "jsonParsed", "slot": 400000000 } ] }' ``` @@ -56,20 +56,41 @@ The response is the standard `getAccountInfo` shape: "jsonrpc": "2.0", "id": 1, "result": { - "context": { "slot": 310000000 }, + "context": { + "apiVersion": "4.1.0", + "slot": 400000000 + }, "value": { - "lamports": 2039280, - "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", - "data": { "program": "spl-token", "parsed": { "...": "..." } }, + "data": { + "parsed": { + "info": { + "isNative": false, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "owner": "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE", + "state": "initialized", + "tokenAmount": { + "amount": "1056153754430274", + "decimals": 6, + "uiAmount": 1056153754.430274, + "uiAmountString": "1056153754.430274" + } + }, + "type": "account" + }, + "program": "spl-token", + "space": 165 + }, "executable": false, - "rentEpoch": 361, + "lamports": 2040821, + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "rentEpoch": 18446744073709551615, "space": 165 } } } ``` -Note the semantics: `slot: S` means *state as of S*. If the account was last written at slot `S - 40,000` and untouched since, you get that write, exactly what any program executing at slot `S` would have seen. And `jsonParsed` works on historical state too: the same decoders a standard RPC node uses are applied to the historical bytes, so a token account from early in the coverage window comes back decoded, not opaque. +Note the semantics: `slot: S` means *state as of S*. If the account was last written at slot `S - 40,000` and untouched since, you get that write, exactly what any program executing at slot `S` would have seen. ## An iterator over an account's history @@ -87,14 +108,12 @@ while True: if e.code == -32020: # walked past the edge of coverage; done break raise - if result["value"] is None: # the account had no earlier writes - break slot = result["context"]["slot"] # the slot this write landed at - handle(slot, result["value"]) + handle(slot, result["value"]) # value is null if this write closed the account cursor = slot # strictly-before: no overlap, no gaps ``` -Iterating through an account's history has two possible stopping points: a `null` value means the account genuinely had no earlier writes, while error `-32020` means the cursor reached the edge of the archive's coverage. Keeping them distinct lets you differentiate between "this account's history begins here" and "history continues below what we've indexed." +The walk has exactly one stopping point: error `-32020`, meaning the cursor has stepped past the edge of the archive's coverage. A `null` value along the way means what it always means in `getAccountInfo`: the account did not exist as of that slot — here, because the write at `context.slot` deleted it. Feed that slot back in as the cursor and the walk continues through the account's earlier life. `firstUpdateAfterSlot` does the reverse: instead of stepping back in time, it moves forward, returning the next write at or after a given slot. It's ideal for an indexer catching up from a known slot that wants every intermediate state, not just the latest. @@ -104,7 +123,7 @@ Between the two, you can walk an account's entire lifecycle: every balance chang The archive listens to the network through a Geyser stream and records account writes as they happen, with two rules that keep the record trustworthy: -**Only finalized slots enter the archive.** Solana forks constantly at the tip, and a block that looks real for a few seconds can simply vanish. The archive holds each slot's updates back until the network finalizes it, so the permanent record contains only what actually happened. Recent unfinalized state is still served for `processed` and `confirmed` queries from a separate short-lived tier. +**Only finalized slots enter the archive.** Solana forks constantly at the tip, and a block that looks real for a few seconds can simply vanish. The archive holds each slot's updates back until the network finalizes it, so the permanent record contains only what actually happened. **Nothing is silently lost.** Every Solana block names its parent, so any block the stream misses (a hiccup, a reconnect, a service deploy) is detected immediately and backfilled. @@ -195,4 +214,4 @@ No. Any Solana JSON-RPC client works. The archive implements standard `getAccoun ### What error do I get when I query past the archive's coverage? -Error code `-32020`. This is distinct from `value: null`, which means the account genuinely had no earlier (or later) writes at all. Handling them separately lets you tell "this account's history begins here" from "history continues below what we've indexed." +Error code `-32020`, and it is the only terminal for a cursor walk — a never-created account errors the same way. A `value: null` response means something different: the account did not exist as of the resolved slot. For point-in-time reads that covers both "not created yet" and "already closed"; for cursor reads it means the write found at `context.slot` deleted the account, and the walk can continue from that slot. diff --git a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml index 2805fd404..c61228f89 100644 --- a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml +++ b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml @@ -69,22 +69,22 @@ methods: schema: $ref: "#/components/schemas/GetAccountInfoArchiveConfig" examples: - - name: Point-in-time read at slot 310,000,000 + - name: Point-in-time read at slot 400,000,000 params: - name: Pubkey - value: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" - name: Configuration value: encoding: jsonParsed - slot: 310000000 + slot: 400000000 result: name: Account information at slot value: context: - apiVersion: "2.3.3" - slot: 310000000 + apiVersion: "4.1.0" + slot: 400000000 value: - lamports: 2039280 + lamports: 2040821 owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" data: program: "spl-token" @@ -94,61 +94,61 @@ methods: info: isNative: false mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - owner: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + owner: "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE" state: "initialized" tokenAmount: - amount: "1500000" + amount: "1056153754430274" decimals: 6 - uiAmount: 1.5 - uiAmountString: "1.5" + uiAmount: 1056153754.430274 + uiAmountString: "1056153754.430274" executable: false - rentEpoch: 361 + rentEpoch: 18446744073709551615 space: 165 - name: Walk history backward with lastUpdateBeforeSlot params: - name: Pubkey - value: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" - name: Configuration value: encoding: base64 - lastUpdateBeforeSlot: 310000000 + lastUpdateBeforeSlot: 400000000 result: name: Previous write on the account value: context: - apiVersion: "2.3.3" - slot: 309998412 + apiVersion: "4.1.0" + slot: 399988788 value: - lamports: 2039280 + lamports: 2040821 owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" data: - - "PGJhc2U2NC1lbmNvZGVkIGFjY291bnQgYnl0ZXM+" + - "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWFgZQGzAuGAGJL4Cil59YX4hV0PIDR5CiRV90T6xQPXtUJrXPWQwAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "base64" executable: false - rentEpoch: 361 + rentEpoch: 18446744073709551615 space: 165 - name: Walk history forward with firstUpdateAfterSlot params: - name: Pubkey - value: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" + value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" - name: Configuration value: encoding: base64 - firstUpdateAfterSlot: 300000000 + firstUpdateAfterSlot: 400000000 result: name: Next write on the account value: context: - apiVersion: "2.3.3" - slot: 300001527 + apiVersion: "4.1.0" + slot: 400005406 value: - lamports: 2039280 + lamports: 2040821 owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" data: - - "PGJhc2U2NC1lbmNvZGVkIGFjY291bnQgYnl0ZXM+" + - "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWFgZQGzAuGAGJL4Cil59YX4hV0PIDR5CiRV90T6xQPXtaI/d7WLwAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "base64" executable: false - rentEpoch: 355 + rentEpoch: 18446744073709551615 space: 165 result: name: Account information @@ -156,17 +156,19 @@ methods: Returns the standard `getAccountInfo` shape. `context.slot` reflects the actual slot at which the write was found (for cursor queries) or the requested `slot` (for point-in-time reads). `value` is `null` - when the account has no writes matching the query (for example, a - `lastUpdateBeforeSlot` cursor for an account with no earlier writes). + when the account did not exist as of the resolved slot: for + point-in-time reads, an account not created yet or already closed; + for cursor reads, a write that deleted the account (its slot is + still returned in `context.slot` and works as the next cursor). schema: $ref: "#/components/schemas/AccountInfoArchiveResult" errors: - code: -32020 message: >- Cursor walked past the edge of the archive's coverage window. This - is distinct from a `null` value response, which means the account - genuinely had no earlier (or later) writes. Coverage currently - extends back to July 2025 and grows over time. + is the only terminal for a cursor walk; a never-indexed account + errors the same way. Coverage currently extends back to July 2025 + and grows over time. - code: -32602 message: >- Invalid params. Common causes include combining more than one of @@ -250,5 +252,6 @@ components: - $ref: ../../chains/_components/solana/account.yaml#/components/schemas/AccountInfo - type: "null" description: >- - The account's state, or `null` when there is no write matching - the query. + The account's state, or `null` when the account did not exist as + of the resolved slot (for cursor reads, the write found at + `context.slot` deleted the account). From c1a49898ec6dec335adb0e239ee82a6a2202e314 Mon Sep 17 00:00:00 2001 From: deepak <96074752+deepakbnsl@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:10:01 -0500 Subject: [PATCH 4/8] [docs-agent] Fold archive params into the standard getAccountInfo reference Drops the separate solana-account-archive OpenRPC spec and nav section: the archive is served on the standard endpoint, so the slot / lastUpdateBeforeSlot / firstUpdateAfterSlot params now live on the original getAccountInfo reference page, with the guide kept as a plain page. Also repositions the historical-account-state guide to lead with the Account Archive as the direct primitive, keeping Yellowstone capture and transaction replay for program-wide sets and pre-coverage history. Co-authored-by: Cursor --- .../solana/historical-account-state.mdx | 41 ++- content/docs.yml | 6 +- .../solana-account-archive.yaml | 257 ------------------ .../chains/_components/solana/account.yaml | 28 ++ .../chains/_components/solana/methods.yaml | 21 +- 5 files changed, 75 insertions(+), 278 deletions(-) delete mode 100644 src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml diff --git a/content/api-reference/solana/historical-account-state.mdx b/content/api-reference/solana/historical-account-state.mdx index 0b5cc3f85..edf2f55ab 100644 --- a/content/api-reference/solana/historical-account-state.mdx +++ b/content/api-reference/solana/historical-account-state.mdx @@ -1,19 +1,27 @@ --- title: Snapshotting historical Solana account state -description: Patterns for capturing Solana account state at regular intervals (hourly, daily, or arbitrary slot cadence) using Alchemy's Yellowstone gRPC streams and archival JSON-RPC methods. -subtitle: Capture point-in-time Solana account state at any cadence using gRPC subscriptions and archival JSON-RPC +description: Patterns for reading and capturing historical Solana account state using the Solana Account Archive, Yellowstone gRPC streams, and archival JSON-RPC methods. +subtitle: Read point-in-time Solana account state directly with the Account Archive, or capture it at any cadence with gRPC subscriptions and archival JSON-RPC slug: docs/solana/historical-account-state --- -Many Solana workloads need to know what an account looked like at a previous point in time: portfolio analytics, TVL history, vault accounting, oracle audits, treasury reporting, ML feature stores. Standard Solana JSON-RPC does not expose a "read this account as of slot N" primitive. This page covers the two patterns Alchemy supports today for reconstructing historical account state, how to combine them, and how to land at common cadences like hourly snapshots. +Many Solana workloads need to know what an account looked like at a previous point in time: portfolio analytics, TVL history, vault accounting, oracle audits, treasury reporting, ML feature stores. For reading a specific account at a specific slot, the direct answer is the [Solana Account Archive](/docs/solana/account-archive): pass a `slot` parameter to `getAccountInfo` for point-in-time state, or page with `lastUpdateBeforeSlot` / `firstUpdateAfterSlot` to walk an account's full update history. This page covers when the archive is all you need, and the two capture-it-yourself patterns for the cases it does not answer directly: program-wide account sets with custom filters, and history older than archive coverage. -## Why Solana RPC alone is not enough +## Start here: the Account Archive -`getAccountInfo` returns the **current** state of an account. The `minContextSlot` parameter is a freshness floor (the read must be evaluated at a slot greater than or equal to `minContextSlot`), not a historical lookup. Solana validators do not retain prior versions of an account's data once the account is updated, so no JSON-RPC method can answer "what did this account hold at slot N" directly. +On a standard Solana node, `getAccountInfo` returns only the **current** state of an account (the `minContextSlot` parameter is a freshness floor, not a historical lookup), and validators do not retain prior versions of an account's data. Alchemy's [Account Archive](/docs/solana/account-archive) removes that limitation: `getAccountInfo(pubkey, { "slot": S })` returns the account's state as of slot `S`, and the cursor parameters iterate every state transition, full `data` bytes included. Coverage currently extends back to July 2025, is never pruned, and grows over time. -What Solana **does** retain is the full transaction and block history. Alchemy's archival infrastructure exposes that history through `getTransaction`, `getBlock`, and `getSignaturesForAddress`, which means you can reconstruct historical account state by: +If your workload is "read known accounts at points in time" — snapshots at an hourly cadence, an oracle value at a specific slot, a token account's balance timeline — use the archive and skip the pipelines below entirely. Sampling at a cadence is one `getAccountInfo` call per boundary slot, and walking every update is the cursor loop described in the [archive guide](/docs/solana/account-archive#an-iterator-over-an-accounts-history). -1. **Capturing it forward** as the chain advances, persisting periodic snapshots to your own store. +You still need the workflows on this page when: + +1. **The account set is dynamic or program-wide.** Archive reads are per-pubkey. To capture every account owned by a program, or accounts matching memcmp/data-size filters, stream them with Yellowstone gRPC (workflow A). +2. **You need history from before archive coverage began** (currently July 2025). Balance history can be reconstructed from transactions (workflow B); full account `data` history generally cannot. +3. **You want the data resident in your own store** for joins, aggregations, or query patterns an RPC read-path does not serve. + +For those cases, the two patterns are: + +1. **Capturing state forward** as the chain advances, persisting periodic snapshots to your own store. 2. **Reconstructing it backward** by replaying the transactions that wrote to the account. Most production indexers combine both: stream forward from "now" while backfilling history for the period before the stream started. @@ -133,7 +141,7 @@ What `getTransaction` does **not** return: pre or post account `data` blobs. The Transaction replay can reliably reconstruct three things: **SOL lamport balances** (from pre/post balances), **SPL token balances** (from pre/post token balances), and **events emitted via program logs** (Anchor `emit!` macros, custom `msg!` lines). - It **cannot** reconstruct arbitrary program account `data` blobs from `getTransaction` alone. To rebuild full account `data` for historical periods, you need either (a) a starting `data` snapshot from before the period of interest, plus program-specific decoding of each touching instruction to derive the new state, or (b) the program emitting comprehensive event logs that cover every state mutation. If neither applies, the supported path is to start workflow A as early as possible and accept that history before the subscription start is not recoverable from transactions alone. + It **cannot** reconstruct arbitrary program account `data` blobs from `getTransaction` alone. For slots within [Account Archive](/docs/solana/account-archive) coverage (July 2025 onward), full `data` history is available directly via `getAccountInfo` with a `slot` or cursor parameter — use that instead of replay. For earlier periods, you need either (a) a starting `data` snapshot from before the period of interest, plus program-specific decoding of each touching instruction to derive the new state, or (b) the program emitting comprehensive event logs that cover every state mutation. ### Pattern @@ -148,13 +156,13 @@ Alchemy's Solana archival surface is optimized for this kind of historical scan. ### When you need program-account state, not just balances -For lending position values, vault share prices, oracle values, market state, and any other field that lives inside a program account's `data` blob, workflow B is not sufficient on its own (see the warning above). The supported path is: +For lending position values, vault share prices, oracle values, market state, and any other field that lives inside a program account's `data` blob, workflow B is not sufficient on its own (see the warning above). -1. Enumerate the account set with paginated [`getProgramAccounts`](/docs/chains/solana/solana-api-endpoints/get-program-accounts) calls using Alchemy's [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) (`pageKey` + `order`). This gives you every account and its **current** `data`. -2. Start a Yellowstone gRPC subscription (workflow A) filtered by program owner. From that point forward, every state change is captured with the full new `data` bytes. -3. Treat the moment the subscription starts as your "history begins here" marker. Reads at any slot at or after that marker resolve against the snapshot table; reads before it are not supported by this workflow. +If the accounts are known and the period is within [Account Archive](/docs/solana/account-archive) coverage, no pipeline is needed: read each account's `data` at the slots you care about with `getAccountInfo` plus `slot`, or walk its transitions with the cursor parameters. For dynamic account sets, or history before archive coverage, the supported path is: -The earlier you start workflow A, the smaller the unsupported window. If a backfill before the subscription start is required, the practical options are to bootstrap from an external historical-snapshot source for the program or to model the program's instructions program-side and replay them against a starting snapshot. Both are out of scope for this guide. +1. Enumerate the account set with paginated [`getProgramAccounts`](/docs/chains/solana/solana-api-endpoints/get-program-accounts) calls using Alchemy's [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) (`pageKey` + `order`). This gives you every account and its **current** `data`. +2. Backfill each enumerated account's `data` history within archive coverage using `lastUpdateBeforeSlot`, and/or start a Yellowstone gRPC subscription (workflow A) filtered by program owner to capture every state change going forward. +3. For history older than archive coverage, the practical options are to bootstrap from an external historical-snapshot source for the program or to model the program's instructions program-side and replay them against a starting snapshot. Both are out of scope for this guide. ### Minimal TypeScript example: lamport balance backfill @@ -228,7 +236,7 @@ For SPL token balances, swap `meta.postBalances[index]` for the matching entry i ## Choosing a sampling cadence -The two workflows above give you per-update granularity. Picking a coarser cadence (hourly, daily) is a query-time concern, not an ingest-time one. Two common approaches: +Within [Account Archive](/docs/solana/account-archive) coverage, cadence is purely a query-time concern with no ingest at all: map each boundary timestamp to a slot and issue one `getAccountInfo` with `slot` per boundary. For self-hosted stores fed by the workflows above, you have per-update granularity, and picking a coarser cadence (hourly, daily) is likewise a query-time concern, not an ingest-time one. Two common approaches: * **Store every update, derive snapshots at query time.** Recommended when the account set is small or update volume is moderate. Lets you change cadence later without re-ingesting. * **Materialize fixed-cadence rollups.** Run a periodic job that, for each tracked account, writes the latest value at or before each cadence boundary into a separate table. Reduces query cost when you only ever read at fixed intervals. @@ -237,6 +245,8 @@ To map wall-clock timestamps to slots for the boundaries, call [`getBlockTime`]( ## Workflow comparison +For single-account historical reads within coverage (July 2025 onward), prefer the [Account Archive](/docs/solana/account-archive) over either workflow: no pipeline, no store, full `data` bytes at any covered slot. The table below compares the two capture-it-yourself workflows for the cases the archive does not cover. + | Aspect | Yellowstone gRPC (Workflow A) | Transaction replay (Workflow B) | | --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------- | | Time horizon | From subscription start, forward | From any point in history, backward | @@ -247,10 +257,11 @@ To map wall-clock timestamps to slots for the boundaries, call [`getBlockTime`]( | Best for | Ongoing capture of a known account or program set | Backfilling SOL/token balance history; not sufficient for arbitrary program state | | Plan requirement | PAYG or Enterprise | Available on all paid plans | -Combine the two: turn on the gRPC subscription first, persist its slot as your "snapshot start", then run workflow B in parallel to backfill SOL and SPL token balance history before that slot. For full account `data` history before the subscription start, see the warning under [Workflow B](#workflow-b-historical-backfill-via-transaction-replay): start workflow A as early as possible, or source a starting snapshot from an external historical-data product. +Combine the two: turn on the gRPC subscription first, persist its slot as your "snapshot start", then run workflow B in parallel to backfill SOL and SPL token balance history before that slot. For full account `data` history before the subscription start, backfill each account from the [Account Archive](/docs/solana/account-archive) with `lastUpdateBeforeSlot` (coverage extends back to July 2025); for earlier periods, see the warning under [Workflow B](#workflow-b-historical-backfill-via-transaction-replay). ## Related references +* [Solana Account Archive](/docs/solana/account-archive) — historical `getAccountInfo`: point-in-time reads with `slot` and full update-history iteration with `lastUpdateBeforeSlot` / `firstUpdateAfterSlot`. * [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) — paginated `getProgramAccounts` and `getTokenLargestAccounts` for enumerating large account sets. * [Yellowstone gRPC Overview](/docs/reference/yellowstone-grpc-overview) and [Subscribe to Accounts](/docs/reference/yellowstone-grpc-subscribe-accounts) — full filter reference and protobuf definitions. * [Solana API FAQ — Historical Data (Archival)](/docs/solana-api-faq#historical-data-archival) — the full set of archival JSON-RPC methods. diff --git a/content/docs.yml b/content/docs.yml index cb94ccf51..efd3382ff 100644 --- a/content/docs.yml +++ b/content/docs.yml @@ -332,12 +332,8 @@ navigation: path: api-reference/solana/solana-api-overview.mdx - api: Solana API Endpoints api-name: solana - - section: Solana Account Archive + - page: Solana Account Archive path: api-reference/solana/account-archive.mdx - contents: - - api: Solana Account Archive Endpoints - api-name: solana-account-archive - flattened: true - link: Solana DAS APIs href: /docs/reference/alchemy-das-apis-for-solana - link: Solana DAS APIs v2 diff --git a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml b/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml deleted file mode 100644 index c61228f89..000000000 --- a/src/openrpc/alchemy/solana-account-archive/solana-account-archive.yaml +++ /dev/null @@ -1,257 +0,0 @@ -# yaml-language-server: $schema=https://meta.open-rpc.org/ - -$schema: https://meta.open-rpc.org/ -openrpc: 1.2.4 -info: - title: Alchemy Solana Account Archive JSON-RPC Specification - description: |- - JSON-RPC specification for Solana Account Archive, Alchemy's historical - account state service. Answers `getAccountInfo` for any Solana account at - any historical slot from July 2025 onward, using the same request and - response shapes as standard Solana RPC. Coverage is never pruned, so the - queryable window only grows over time. - - The archive extends `getAccountInfo` with three mutually-exclusive - parameters (`slot`, `lastUpdateBeforeSlot`, `firstUpdateAfterSlot`) that - also cannot be combined with `minContextSlot`. Omit all three to get a - normal, latest-state read: the archive is a drop-in superset of standard - `getAccountInfo`. - - Vote accounts and the three per-slot sysvars (`SlotHashes`, `SlotHistory`, - `RecentBlockhashes`) are excluded from the archive. All other accounts, - including program accounts, token accounts, mints, PDAs, wallets, and the - remaining sysvars, are covered. - version: 0.0.0 -servers: - - url: https://solana-mainnet.g.alchemy.com/v2 - name: Solana Mainnet -methods: - - name: getAccountInfo - summary: >- - Returns account information at a specific slot, or walks an account's - update history using cursor parameters. - description: |- - Returns all information associated with the account at the given Pubkey, - with optional historical lookups. Supports three mutually-exclusive - history parameters in the configuration object: - - - `slot`: state *as of* slot `S` (inclusive: the latest write with - `slot <= S`). Use for point-in-time snapshots. - - `lastUpdateBeforeSlot`: the most recent write *strictly before* slot - `S`. Use to walk an account's history backward. - - `firstUpdateAfterSlot`: the first write *strictly after* slot `S`. - Use to walk an account's history forward or catch an indexer up from - a known slot. - - These three are also mutually exclusive with `minContextSlot`; pairing - it with any of them returns an invalid-params error. Omit all three for - a normal, latest-state `getAccountInfo`. - - The response is the standard `getAccountInfo` shape. When using - `lastUpdateBeforeSlot` or `firstUpdateAfterSlot`, the `context.slot` - field returns the actual slot of the write that was found, so each - response is the cursor for the next request. - - `jsonParsed` encoding works on historical state: the same decoders a - standard RPC node uses are applied to the historical bytes. - params: - - name: Pubkey - required: true - description: Pubkey of the account to query (base-58 encoded). - schema: - $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/Pubkey - - name: Configuration - required: false - description: >- - Optional configuration object. Includes the standard `getAccountInfo` - options (`encoding`, `commitment`, `dataSlice`, `minContextSlot`) - plus the three mutually-exclusive archive parameters. - schema: - $ref: "#/components/schemas/GetAccountInfoArchiveConfig" - examples: - - name: Point-in-time read at slot 400,000,000 - params: - - name: Pubkey - value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" - - name: Configuration - value: - encoding: jsonParsed - slot: 400000000 - result: - name: Account information at slot - value: - context: - apiVersion: "4.1.0" - slot: 400000000 - value: - lamports: 2040821 - owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - data: - program: "spl-token" - space: 165 - parsed: - type: "account" - info: - isNative: false - mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" - owner: "7VHUFJHWu2CuExkJcJrzhQPJ2oygupTWkL2A2For4BmE" - state: "initialized" - tokenAmount: - amount: "1056153754430274" - decimals: 6 - uiAmount: 1056153754.430274 - uiAmountString: "1056153754.430274" - executable: false - rentEpoch: 18446744073709551615 - space: 165 - - name: Walk history backward with lastUpdateBeforeSlot - params: - - name: Pubkey - value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" - - name: Configuration - value: - encoding: base64 - lastUpdateBeforeSlot: 400000000 - result: - name: Previous write on the account - value: - context: - apiVersion: "4.1.0" - slot: 399988788 - value: - lamports: 2040821 - owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - data: - - "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWFgZQGzAuGAGJL4Cil59YX4hV0PIDR5CiRV90T6xQPXtUJrXPWQwAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - - "base64" - executable: false - rentEpoch: 18446744073709551615 - space: 165 - - name: Walk history forward with firstUpdateAfterSlot - params: - - name: Pubkey - value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" - - name: Configuration - value: - encoding: base64 - firstUpdateAfterSlot: 400000000 - result: - name: Next write on the account - value: - context: - apiVersion: "4.1.0" - slot: 400005406 - value: - lamports: 2040821 - owner: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" - data: - - "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWFgZQGzAuGAGJL4Cil59YX4hV0PIDR5CiRV90T6xQPXtaI/d7WLwAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - - "base64" - executable: false - rentEpoch: 18446744073709551615 - space: 165 - result: - name: Account information - description: >- - Returns the standard `getAccountInfo` shape. `context.slot` reflects - the actual slot at which the write was found (for cursor queries) or - the requested `slot` (for point-in-time reads). `value` is `null` - when the account did not exist as of the resolved slot: for - point-in-time reads, an account not created yet or already closed; - for cursor reads, a write that deleted the account (its slot is - still returned in `context.slot` and works as the next cursor). - schema: - $ref: "#/components/schemas/AccountInfoArchiveResult" - errors: - - code: -32020 - message: >- - Cursor walked past the edge of the archive's coverage window. This - is the only terminal for a cursor walk; a never-indexed account - errors the same way. Coverage currently extends back to July 2025 - and grows over time. - - code: -32602 - message: >- - Invalid params. Common causes include combining more than one of - `slot`, `lastUpdateBeforeSlot`, and `firstUpdateAfterSlot`, - combining any of those three with `minContextSlot`, a slot value - that is not a non-negative integer, or a malformed Pubkey. - - code: -32601 - message: Method not found. - - code: -32603 - message: >- - Internal error. Retry the request; if it persists, contact support. - -components: - schemas: - GetAccountInfoArchiveConfig: - title: getAccountInfo (Archive) Configuration - type: object - description: >- - Configuration for `getAccountInfo` with archive extensions. `slot`, - `lastUpdateBeforeSlot`, and `firstUpdateAfterSlot` are mutually - exclusive with each other and with `minContextSlot`; omit all three - for a normal latest-state read. - properties: - commitment: - $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/Commitment - encoding: - $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/Encoding - description: Encoding format for account data. - dataSlice: - $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/DataSlice - minContextSlot: - $ref: ../../chains/_components/solana/base-types.yaml#/components/schemas/MinContextSlot - slot: - type: integer - minimum: 0 - description: >- - Return the account's state as of slot `S` (inclusive). The - response is the latest write with `slot <= S`. Mutually - exclusive with `lastUpdateBeforeSlot`, `firstUpdateAfterSlot`, - and `minContextSlot`. - lastUpdateBeforeSlot: - type: integer - minimum: 0 - description: >- - Return the most recent write to this account strictly before - slot `S`. The response's `context.slot` is the actual slot at - which that write landed and can be used as the cursor for the - next call. Mutually exclusive with `slot`, - `firstUpdateAfterSlot`, and `minContextSlot`. - firstUpdateAfterSlot: - type: integer - minimum: 0 - description: >- - Return the first write to this account strictly after slot `S`. - The response's `context.slot` is the actual slot at which that - write landed and can be used as the cursor for the next call. - Mutually exclusive with `slot`, `lastUpdateBeforeSlot`, and - `minContextSlot`. - - AccountInfoArchiveResult: - title: getAccountInfo (Archive) Result - type: object - properties: - context: - type: object - properties: - apiVersion: - type: string - description: RPC node API version. - slot: - type: integer - description: >- - The slot at which the returned state was written. For - point-in-time reads (`slot` parameter set), this is the - requested slot. For cursor reads - (`lastUpdateBeforeSlot` / `firstUpdateAfterSlot`), this is - the actual slot of the write that was found and can be used - as the next cursor. - value: - oneOf: - - $ref: ../../chains/_components/solana/account.yaml#/components/schemas/AccountInfo - - type: "null" - description: >- - The account's state, or `null` when the account did not exist as - of the resolved slot (for cursor reads, the write found at - `context.slot` deleted the account). diff --git a/src/openrpc/chains/_components/solana/account.yaml b/src/openrpc/chains/_components/solana/account.yaml index 8164d8023..9d32590d7 100644 --- a/src/openrpc/chains/_components/solana/account.yaml +++ b/src/openrpc/chains/_components/solana/account.yaml @@ -40,6 +40,34 @@ components: $ref: "./base-types.yaml#/components/schemas/DataSlice" minContextSlot: $ref: "./base-types.yaml#/components/schemas/MinContextSlot" + slot: + type: integer + minimum: 0 + description: >- + [Account Archive](/docs/solana/account-archive) parameter. Return + the account's state as of slot `S` (inclusive): the latest write + with `slot <= S`. Mutually exclusive with `lastUpdateBeforeSlot`, + `firstUpdateAfterSlot`, and `minContextSlot`. + lastUpdateBeforeSlot: + type: integer + minimum: 0 + description: >- + [Account Archive](/docs/solana/account-archive) parameter. Return + the most recent write to this account strictly before slot `S`. + The response's `context.slot` is the actual slot at which that + write landed and can be used as the cursor for the next call. + Mutually exclusive with `slot`, `firstUpdateAfterSlot`, and + `minContextSlot`. + firstUpdateAfterSlot: + type: integer + minimum: 0 + description: >- + [Account Archive](/docs/solana/account-archive) parameter. Return + the first write to this account strictly after slot `S`. The + response's `context.slot` is the actual slot at which that write + landed and can be used as the cursor for the next call. Mutually + exclusive with `slot`, `lastUpdateBeforeSlot`, and + `minContextSlot`. GetBalanceConfig: title: GetBalance Configuration type: object diff --git a/src/openrpc/chains/_components/solana/methods.yaml b/src/openrpc/chains/_components/solana/methods.yaml index 0f19698ba..d8e09595b 100644 --- a/src/openrpc/chains/_components/solana/methods.yaml +++ b/src/openrpc/chains/_components/solana/methods.yaml @@ -2,7 +2,18 @@ components: methods: getAccountInfo: name: getAccountInfo - description: Returns all information associated with the account of provided Pubkey. + description: |- + Returns all information associated with the account of provided Pubkey. + + Supports historical lookups via the + [Solana Account Archive](/docs/solana/account-archive): set `slot` in + the configuration object for the account's state as of that slot, or + `lastUpdateBeforeSlot` / `firstUpdateAfterSlot` to walk the account's + update history (the response's `context.slot` carries the located + write's slot, which is the cursor for the next call). The three are + mutually exclusive with each other and with `minContextSlot`; omit + all three for a normal latest-state read. A cursor that steps past + the edge of archive coverage returns error `-32020`. x-compute-units: 10 params: - name: Pubkey @@ -20,6 +31,14 @@ components: params: - name: Pubkey value: "GwsPP9HHhCvEQeu3HTFzsVL6DEtnnYw4ALEtA3fMBC9Q" + - name: Historical read at slot 400,000,000 (Account Archive) + params: + - name: Pubkey + value: "3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa" + - name: Configuration + value: + encoding: jsonParsed + slot: 400000000 result: name: Account information description: Returns details of the account including balance, ownership, and other relevant data. From 3b95a68411b68a343aef10857e0050d1061b71a3 Mon Sep 17 00:00:00 2001 From: deepak <96074752+deepakbnsl@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:13:19 -0500 Subject: [PATCH 5/8] [docs-agent] Remove historical-account-state guide, superseded by Account Archive The page taught workarounds for the absence of a historical getAccountInfo primitive, which the Account Archive now provides directly. Its slug redirects to the archive guide, and the still-unique guidance (program-wide capture via Yellowstone, pre-coverage balance reconstruction) moves into the archive guide's FAQ. Co-authored-by: Cursor --- .../api-reference/solana/account-archive.mdx | 4 + .../solana/historical-account-state.mdx | 268 ------------------ content/docs.yml | 2 - content/redirects.yml | 4 + 4 files changed, 8 insertions(+), 270 deletions(-) delete mode 100644 content/api-reference/solana/historical-account-state.mdx diff --git a/content/api-reference/solana/account-archive.mdx b/content/api-reference/solana/account-archive.mdx index 75d134ce1..2d3d0b830 100644 --- a/content/api-reference/solana/account-archive.mdx +++ b/content/api-reference/solana/account-archive.mdx @@ -212,6 +212,10 @@ All accounts except pure per-slot chain bookkeeping: vote accounts and the three No. Any Solana JSON-RPC client works. The archive implements standard `getAccountInfo`, and the historical parameters are just extra fields in the existing config object. +### What if I need program-wide history, or history before archive coverage? + +The archive answers per-account reads. To capture every account owned by a program (or accounts matching byte-level filters), stream them forward with [Yellowstone gRPC](/docs/reference/yellowstone-grpc-overview) account subscriptions, enumerating the starting set with paginated [`getProgramAccounts`](/docs/chains/solana/solana-api-endpoints/get-program-accounts) via [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) — then backfill each account's history within coverage using `lastUpdateBeforeSlot`. For periods before archive coverage, SOL and SPL token *balance* history can be reconstructed from transaction history (`getSignaturesForAddress` plus `getTransaction` pre/post balances), but full account `data` history is generally not recoverable from transactions alone. + ### What error do I get when I query past the archive's coverage? Error code `-32020`, and it is the only terminal for a cursor walk — a never-created account errors the same way. A `value: null` response means something different: the account did not exist as of the resolved slot. For point-in-time reads that covers both "not created yet" and "already closed"; for cursor reads it means the write found at `context.slot` deleted the account, and the walk can continue from that slot. diff --git a/content/api-reference/solana/historical-account-state.mdx b/content/api-reference/solana/historical-account-state.mdx deleted file mode 100644 index edf2f55ab..000000000 --- a/content/api-reference/solana/historical-account-state.mdx +++ /dev/null @@ -1,268 +0,0 @@ ---- -title: Snapshotting historical Solana account state -description: Patterns for reading and capturing historical Solana account state using the Solana Account Archive, Yellowstone gRPC streams, and archival JSON-RPC methods. -subtitle: Read point-in-time Solana account state directly with the Account Archive, or capture it at any cadence with gRPC subscriptions and archival JSON-RPC -slug: docs/solana/historical-account-state ---- - -Many Solana workloads need to know what an account looked like at a previous point in time: portfolio analytics, TVL history, vault accounting, oracle audits, treasury reporting, ML feature stores. For reading a specific account at a specific slot, the direct answer is the [Solana Account Archive](/docs/solana/account-archive): pass a `slot` parameter to `getAccountInfo` for point-in-time state, or page with `lastUpdateBeforeSlot` / `firstUpdateAfterSlot` to walk an account's full update history. This page covers when the archive is all you need, and the two capture-it-yourself patterns for the cases it does not answer directly: program-wide account sets with custom filters, and history older than archive coverage. - -## Start here: the Account Archive - -On a standard Solana node, `getAccountInfo` returns only the **current** state of an account (the `minContextSlot` parameter is a freshness floor, not a historical lookup), and validators do not retain prior versions of an account's data. Alchemy's [Account Archive](/docs/solana/account-archive) removes that limitation: `getAccountInfo(pubkey, { "slot": S })` returns the account's state as of slot `S`, and the cursor parameters iterate every state transition, full `data` bytes included. Coverage currently extends back to July 2025, is never pruned, and grows over time. - -If your workload is "read known accounts at points in time" — snapshots at an hourly cadence, an oracle value at a specific slot, a token account's balance timeline — use the archive and skip the pipelines below entirely. Sampling at a cadence is one `getAccountInfo` call per boundary slot, and walking every update is the cursor loop described in the [archive guide](/docs/solana/account-archive#an-iterator-over-an-accounts-history). - -You still need the workflows on this page when: - -1. **The account set is dynamic or program-wide.** Archive reads are per-pubkey. To capture every account owned by a program, or accounts matching memcmp/data-size filters, stream them with Yellowstone gRPC (workflow A). -2. **You need history from before archive coverage began** (currently July 2025). Balance history can be reconstructed from transactions (workflow B); full account `data` history generally cannot. -3. **You want the data resident in your own store** for joins, aggregations, or query patterns an RPC read-path does not serve. - -For those cases, the two patterns are: - -1. **Capturing state forward** as the chain advances, persisting periodic snapshots to your own store. -2. **Reconstructing it backward** by replaying the transactions that wrote to the account. - -Most production indexers combine both: stream forward from "now" while backfilling history for the period before the stream started. - -## Workflow A: forward snapshotting via Yellowstone gRPC - -[Yellowstone gRPC](/docs/reference/yellowstone-grpc-overview) is the recommended path for ongoing state capture. A single subscription receives every account update for the addresses or programs you care about, with the slot number attached to each update, and persists at minimal latency. - -### How it works - -1. Open a Yellowstone gRPC subscription with an account filter ([`accounts`](/docs/reference/yellowstone-grpc-subscribe-accounts) in the [`SubscribeRequest`](/docs/reference/yellowstone-grpc-subscribe-request)). -2. On every `SubscribeUpdateAccount` your handler receives, write a row containing `(pubkey, slot, write_version, data, lamports, owner, txn_signature)` to your store. -3. Index the resulting table by `(pubkey, slot, write_version)` so you can query the latest version at or before any target slot. - -A single Solana slot can contain multiple transactions that write to the same account. The `write_version` field is a monotonically increasing counter that disambiguates those intra-slot updates: the final state for a slot is the row with the highest `write_version` at that slot. Always tie-break by `(slot, write_version)` rather than `slot` alone. - -To produce hourly snapshots, you do not need a separate sampling job. Solana blocks land at roughly 400 ms, so an active account may have many writes per hour and a quiet account may have none. Both cases are handled the same way: a query for "the state of this account at hour H" returns the row with the largest `(slot, write_version)` pair where `slot` is less than or equal to the slot corresponding to the timestamp at the end of hour H. In SQL that is `ORDER BY slot DESC, write_version DESC LIMIT 1`. Use `getBlockTime` or `getBlocks` to map wall-clock timestamps to slots. - -### Filter options - -The Yellowstone account filter supports three styles, in order of selectivity: - -* **Specific addresses** ([`account`](/docs/reference/yellowstone-grpc-subscribe-accounts#account-address-filter)): pass a list of pubkeys to watch. Best when you know the accounts upfront. -* **Program owner** ([`owner`](/docs/reference/yellowstone-grpc-subscribe-accounts#owner-filter)): receive every account owned by a given program. Use this when the account set is dynamic (for example, all vault accounts under a single program). -* **Memcmp / data-size / lamports / `token_account_state`** ([`filters`](/docs/reference/yellowstone-grpc-subscribe-accounts#memcmp-filter)): narrow further by byte patterns, account discriminator, or balance ranges. Combine with `owner` to watch a specific subset of a program's accounts. - -If you need to enumerate the initial set of accounts before subscribing (for example, all program-derived accounts under a vault program), use `getProgramAccounts` paginated via Alchemy's [AccountsDB Infrastructure](/docs/solana/accounts-db-infra). The `pageKey` and `order` parameters let you scan large account sets without timing out. - -### Minimal Rust example - -```rust -use anyhow::Result; -use futures::{sink::SinkExt, stream::StreamExt}; -use std::collections::HashMap; -use yellowstone_grpc_client::{ClientTlsConfig, GeyserGrpcClient}; -use yellowstone_grpc_proto::geyser::{ - CommitmentLevel, SubscribeRequest, SubscribeRequestFilterAccounts, - subscribe_update::UpdateOneof, -}; - -#[tokio::main] -async fn main() -> Result<()> { - let endpoint = "https://solana-mainnet.g.alchemy.com"; - let x_token = "YOUR_ALCHEMY_API_KEY"; - - let mut client = GeyserGrpcClient::build_from_shared(endpoint)? - .tls_config(ClientTlsConfig::new().with_native_roots())? - .x_token(Some(x_token))? - .connect() - .await?; - - let (mut tx, mut stream) = client.subscribe().await?; - - // Subscribe to a specific set of account pubkeys - let mut accounts = HashMap::new(); - accounts.insert( - "accounts_to_snapshot".to_string(), - SubscribeRequestFilterAccounts { - account: vec![ - "AccountPubkey1...".to_string(), - "AccountPubkey2...".to_string(), - ], - owner: vec![], - filters: vec![], - nonempty_txn_signature: Some(true), - }, - ); - - tx.send(SubscribeRequest { - accounts, - commitment: Some(CommitmentLevel::Confirmed as i32), - ..Default::default() - }) - .await?; - - while let Some(Ok(msg)) = stream.next().await { - if let Some(UpdateOneof::Account(update)) = msg.update_oneof { - if let Some(info) = update.account { - // Persist this snapshot. Index by (pubkey, slot, write_version). - println!( - "slot={} pubkey={} lamports={} data_len={}", - update.slot, - bs58::encode(&info.pubkey).into_string(), - info.lamports, - info.data.len() - ); - } - } - } - - Ok(()) -} -``` - -For client setup, authentication details, and language samples in TypeScript and Go, see the [Yellowstone gRPC Quickstart](/docs/reference/yellowstone-grpc-quickstart). - -### Recovering from disconnects - -Yellowstone supports replaying historical updates by setting `from_slot` on the `SubscribeRequest`. The replay window is up to **6000 slots (~40 minutes)** of history. Persist the slot of the last update you successfully wrote, and on reconnect resubscribe with `from_slot` set to that slot. If your downtime exceeds the replay window, fall back to the backfill workflow below for the gap. - -## Workflow B: historical backfill via transaction replay - -Snapshotting forward only captures state from the moment your subscription starts. For periods before that, the available primitive is the transaction history exposed through `getTransaction`. This workflow is best understood by what each `getTransaction` response actually contains and what it does not. - -### What `getTransaction` provides - -For each transaction, `getTransaction` returns: - -* `meta.preBalances` and `meta.postBalances`: SOL lamport balances per account (indexed against `transaction.message.accountKeys`). -* `meta.preTokenBalances` and `meta.postTokenBalances`: SPL token balances per account. -* `meta.innerInstructions`: CPIs invoked by the top-level instructions. -* `meta.logMessages`: program log output. -* `transaction.message.instructions` and `transaction.message.accountKeys`: the instructions and the accounts they touched. - -What `getTransaction` does **not** return: pre or post account `data` blobs. There is no `preAccountData` / `postAccountData` field. That is the central constraint on this workflow. - - - Transaction replay can reliably reconstruct three things: **SOL lamport balances** (from pre/post balances), **SPL token balances** (from pre/post token balances), and **events emitted via program logs** (Anchor `emit!` macros, custom `msg!` lines). - - It **cannot** reconstruct arbitrary program account `data` blobs from `getTransaction` alone. For slots within [Account Archive](/docs/solana/account-archive) coverage (July 2025 onward), full `data` history is available directly via `getAccountInfo` with a `slot` or cursor parameter — use that instead of replay. For earlier periods, you need either (a) a starting `data` snapshot from before the period of interest, plus program-specific decoding of each touching instruction to derive the new state, or (b) the program emitting comprehensive event logs that cover every state mutation. - - -### Pattern - -1. Call [`getSignaturesForAddress`](/docs/chains/solana/solana-api-endpoints/get-signatures-for-address) for the target account, paging through history with `before` and `until` cursors. Returns signatures plus block times. -2. For each signature, call [`getTransaction`](/docs/chains/solana/solana-api-endpoints/get-transaction) and locate the target account's index in `transaction.message.accountKeys`. -3. Read `meta.postBalances[i]` (and `meta.postTokenBalances` for token accounts) for the state immediately after this transaction's writes. Persist a row keyed by `(pubkey, slot, signature)` so you can later answer "balance at hour H" as the row with the largest slot less than or equal to `block_at(H)`. - -`meta.preBalances` and `meta.postBalances` are consistent across adjacent transactions: the `preBalances[i]` of the transaction at slot T+1 equals the `postBalances[i]` of the most recent earlier transaction that touched account `i`. Use the post-side of each transaction as the canonical value for its slot. - -Alchemy's Solana archival surface is optimized for this kind of historical scan. Per the [Built for Solana](https://www.alchemy.com/blog/solana-infrastructure) release, `getTransaction` is up to 20x faster than other providers on historical calls, and `getSignaturesForAddress` supports recency-first ordering so you can walk from the present backward without scanning from genesis. - -### When you need program-account state, not just balances - -For lending position values, vault share prices, oracle values, market state, and any other field that lives inside a program account's `data` blob, workflow B is not sufficient on its own (see the warning above). - -If the accounts are known and the period is within [Account Archive](/docs/solana/account-archive) coverage, no pipeline is needed: read each account's `data` at the slots you care about with `getAccountInfo` plus `slot`, or walk its transitions with the cursor parameters. For dynamic account sets, or history before archive coverage, the supported path is: - -1. Enumerate the account set with paginated [`getProgramAccounts`](/docs/chains/solana/solana-api-endpoints/get-program-accounts) calls using Alchemy's [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) (`pageKey` + `order`). This gives you every account and its **current** `data`. -2. Backfill each enumerated account's `data` history within archive coverage using `lastUpdateBeforeSlot`, and/or start a Yellowstone gRPC subscription (workflow A) filtered by program owner to capture every state change going forward. -3. For history older than archive coverage, the practical options are to bootstrap from an external historical-snapshot source for the program or to model the program's instructions program-side and replay them against a starting snapshot. Both are out of scope for this guide. - -### Minimal TypeScript example: lamport balance backfill - -This example walks one account's signature history and persists post-balance per transaction, which is what workflow B reliably supports. - -```typescript -import { Connection, PublicKey } from "@solana/web3.js"; - -const connection = new Connection( - "https://solana-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY", - "confirmed" -); - -type BalanceRow = { - pubkey: string; - slot: number; - signature: string; - lamports: number; -}; - -async function backfillLamportHistory( - address: string, - untilSlot: number -): Promise { - const pubkey = new PublicKey(address); - const rows: BalanceRow[] = []; - let before: string | undefined = undefined; - - while (true) { - const sigs = await connection.getSignaturesForAddress(pubkey, { - before, - limit: 1000, - }); - if (sigs.length === 0) break; - - for (const sig of sigs) { - if (sig.slot < untilSlot) return rows; - - const tx = await connection.getTransaction(sig.signature, { - maxSupportedTransactionVersion: 0, - }); - if (!tx || !tx.meta) continue; - - // Locate the target account's index in the message account keys. - const keys = tx.transaction.message.getAccountKeys({ - accountKeysFromLookups: tx.meta.loadedAddresses, - }); - const index = keys - .keySegments() - .flat() - .findIndex((k) => k.equals(pubkey)); - if (index === -1) continue; - - // Post-balance is the canonical lamport value after this transaction's writes. - rows.push({ - pubkey: address, - slot: sig.slot, - signature: sig.signature, - lamports: tx.meta.postBalances[index], - }); - } - - before = sigs[sigs.length - 1].signature; - } - - return rows; -} -``` - -For SPL token balances, swap `meta.postBalances[index]` for the matching entry in `meta.postTokenBalances` (which is indexed by `accountIndex` and includes `mint`, `owner`, and `uiTokenAmount`). For program log events, parse `meta.logMessages` against your program's known event format. - -## Choosing a sampling cadence - -Within [Account Archive](/docs/solana/account-archive) coverage, cadence is purely a query-time concern with no ingest at all: map each boundary timestamp to a slot and issue one `getAccountInfo` with `slot` per boundary. For self-hosted stores fed by the workflows above, you have per-update granularity, and picking a coarser cadence (hourly, daily) is likewise a query-time concern, not an ingest-time one. Two common approaches: - -* **Store every update, derive snapshots at query time.** Recommended when the account set is small or update volume is moderate. Lets you change cadence later without re-ingesting. -* **Materialize fixed-cadence rollups.** Run a periodic job that, for each tracked account, writes the latest value at or before each cadence boundary into a separate table. Reduces query cost when you only ever read at fixed intervals. - -To map wall-clock timestamps to slots for the boundaries, call [`getBlockTime`](/docs/chains/solana/solana-api-endpoints/get-block-time) on a candidate slot, or use [`getBlocks`](/docs/chains/solana/solana-api-endpoints/get-blocks) to find the slot range that bounds a target timestamp. - -## Workflow comparison - -For single-account historical reads within coverage (July 2025 onward), prefer the [Account Archive](/docs/solana/account-archive) over either workflow: no pipeline, no store, full `data` bytes at any covered slot. The table below compares the two capture-it-yourself workflows for the cases the archive does not cover. - -| Aspect | Yellowstone gRPC (Workflow A) | Transaction replay (Workflow B) | -| --------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------- | -| Time horizon | From subscription start, forward | From any point in history, backward | -| Latency to new data | Real-time (sub-second) | As fast as you can page archival history | -| Filter granularity | Address, owner, memcmp, data size, lamports | Per-address (via `getSignaturesForAddress`) | -| Data captured | Full account bytes (data, lamports, owner) | SOL balance, SPL token balance, program log events only (no account `data` blobs) | -| Replay after gap | `from_slot`, up to 6000 slots (~40 min) | Unbounded, limited only by archival depth | -| Best for | Ongoing capture of a known account or program set | Backfilling SOL/token balance history; not sufficient for arbitrary program state | -| Plan requirement | PAYG or Enterprise | Available on all paid plans | - -Combine the two: turn on the gRPC subscription first, persist its slot as your "snapshot start", then run workflow B in parallel to backfill SOL and SPL token balance history before that slot. For full account `data` history before the subscription start, backfill each account from the [Account Archive](/docs/solana/account-archive) with `lastUpdateBeforeSlot` (coverage extends back to July 2025); for earlier periods, see the warning under [Workflow B](#workflow-b-historical-backfill-via-transaction-replay). - -## Related references - -* [Solana Account Archive](/docs/solana/account-archive) — historical `getAccountInfo`: point-in-time reads with `slot` and full update-history iteration with `lastUpdateBeforeSlot` / `firstUpdateAfterSlot`. -* [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) — paginated `getProgramAccounts` and `getTokenLargestAccounts` for enumerating large account sets. -* [Yellowstone gRPC Overview](/docs/reference/yellowstone-grpc-overview) and [Subscribe to Accounts](/docs/reference/yellowstone-grpc-subscribe-accounts) — full filter reference and protobuf definitions. -* [Solana API FAQ — Historical Data (Archival)](/docs/solana-api-faq#historical-data-archival) — the full set of archival JSON-RPC methods. -* [Built for Solana](https://www.alchemy.com/blog/solana-infrastructure) and [How Alchemy Built the Fastest Archival Methods on Solana](https://www.alchemy.com/blog/how-alchemy-built-the-fastest-archival-methods-on-solana) — background on the archival stack powering the methods above. diff --git a/content/docs.yml b/content/docs.yml index efd3382ff..6471775a0 100644 --- a/content/docs.yml +++ b/content/docs.yml @@ -326,8 +326,6 @@ navigation: href: https://solana-demo-sigma.vercel.app/ - page: Accounts DB Infrastructure path: api-reference/solana/accounts-db-infra.mdx - - page: Historical account state - path: api-reference/solana/historical-account-state.mdx - page: Solana API Overview path: api-reference/solana/solana-api-overview.mdx - api: Solana API Endpoints diff --git a/content/redirects.yml b/content/redirects.yml index 36260316e..533f54b05 100644 --- a/content/redirects.yml +++ b/content/redirects.yml @@ -16,6 +16,10 @@ redirects: permanent: true # ========================================= Removed Page Redirects ========================================== + - source: /docs/solana/historical-account-state + destination: /docs/solana/account-archive + permanent: true + - source: /docs/reference/new-pricing-for-existing-scale-and-growth-customers destination: /docs/reference/pay-as-you-go-pricing-faq permanent: true From 930590ecd41331be7031de94642b28b58d79d765 Mon Sep 17 00:00:00 2001 From: deepak <96074752+deepakbnsl@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:22:54 -0500 Subject: [PATCH 6/8] Move getAccountInfo archive details into an errors section; trim archive-guide FAQ --- .../api-reference/solana/account-archive.mdx | 4 --- .../chains/_components/solana/methods.yaml | 30 +++++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/content/api-reference/solana/account-archive.mdx b/content/api-reference/solana/account-archive.mdx index 2d3d0b830..75d134ce1 100644 --- a/content/api-reference/solana/account-archive.mdx +++ b/content/api-reference/solana/account-archive.mdx @@ -212,10 +212,6 @@ All accounts except pure per-slot chain bookkeeping: vote accounts and the three No. Any Solana JSON-RPC client works. The archive implements standard `getAccountInfo`, and the historical parameters are just extra fields in the existing config object. -### What if I need program-wide history, or history before archive coverage? - -The archive answers per-account reads. To capture every account owned by a program (or accounts matching byte-level filters), stream them forward with [Yellowstone gRPC](/docs/reference/yellowstone-grpc-overview) account subscriptions, enumerating the starting set with paginated [`getProgramAccounts`](/docs/chains/solana/solana-api-endpoints/get-program-accounts) via [AccountsDB Infrastructure](/docs/solana/accounts-db-infra) — then backfill each account's history within coverage using `lastUpdateBeforeSlot`. For periods before archive coverage, SOL and SPL token *balance* history can be reconstructed from transaction history (`getSignaturesForAddress` plus `getTransaction` pre/post balances), but full account `data` history is generally not recoverable from transactions alone. - ### What error do I get when I query past the archive's coverage? Error code `-32020`, and it is the only terminal for a cursor walk — a never-created account errors the same way. A `value: null` response means something different: the account did not exist as of the resolved slot. For point-in-time reads that covers both "not created yet" and "already closed"; for cursor reads it means the write found at `context.slot` deleted the account, and the walk can continue from that slot. diff --git a/src/openrpc/chains/_components/solana/methods.yaml b/src/openrpc/chains/_components/solana/methods.yaml index d8e09595b..b7ed5bcb7 100644 --- a/src/openrpc/chains/_components/solana/methods.yaml +++ b/src/openrpc/chains/_components/solana/methods.yaml @@ -2,18 +2,7 @@ components: methods: getAccountInfo: name: getAccountInfo - description: |- - Returns all information associated with the account of provided Pubkey. - - Supports historical lookups via the - [Solana Account Archive](/docs/solana/account-archive): set `slot` in - the configuration object for the account's state as of that slot, or - `lastUpdateBeforeSlot` / `firstUpdateAfterSlot` to walk the account's - update history (the response's `context.slot` carries the located - write's slot, which is the cursor for the next call). The three are - mutually exclusive with each other and with `minContextSlot`; omit - all three for a normal latest-state read. A cursor that steps past - the edge of archive coverage returns error `-32020`. + description: Returns all information associated with the account of provided Pubkey. x-compute-units: 10 params: - name: Pubkey @@ -44,6 +33,23 @@ components: description: Returns details of the account including balance, ownership, and other relevant data. schema: $ref: "./account.yaml#/components/schemas/AccountInfo" + errors: + - code: -32020 + message: >- + Out of coverage. The requested historical slot is outside the + [Account Archive](/docs/solana/account-archive)'s coverage + window: below the earliest indexed slot, above the requested + commitment's tip, or a `lastUpdateBeforeSlot` / + `firstUpdateAfterSlot` cursor stepped past the edge of coverage + (the terminal for a history walk). + - code: -32602 + message: >- + Invalid params. `slot`, `lastUpdateBeforeSlot`, + `firstUpdateAfterSlot`, and `minContextSlot` are mutually + exclusive; specifying more than one returns "More than one of + slot / lastUpdateBeforeSlot / firstUpdateAfterSlot / + minContextSlot specified". Also returned for a malformed Pubkey + or configuration value. getBalance: name: getBalance From c53fce7c1a097a508a9b3be56db035b73f382e4d Mon Sep 17 00:00:00 2001 From: deepak <96074752+deepakbnsl@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:29:52 -0500 Subject: [PATCH 7/8] Document finalized-only commitment for Account Archive historical params Co-authored-by: Cursor --- content/api-reference/solana/account-archive.mdx | 2 +- src/openrpc/chains/_components/solana/account.yaml | 7 ++++--- src/openrpc/chains/_components/solana/methods.yaml | 10 ++++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/content/api-reference/solana/account-archive.mdx b/content/api-reference/solana/account-archive.mdx index 75d134ce1..f51621912 100644 --- a/content/api-reference/solana/account-archive.mdx +++ b/content/api-reference/solana/account-archive.mdx @@ -32,7 +32,7 @@ We deliberately did not invent a new method. The archive speaks standard JSON-RP | `lastUpdateBeforeSlot` | The most recent write *strictly before* `S` | Walking an account's history backward | | `firstUpdateAfterSlot` | The first write *strictly after* `S` | Walking an account's history forward | -These three are also mutually exclusive with `minContextSlot`; pairing it with any of them is rejected. Omit all three and you get a normal, latest-state `getAccountInfo`; the archive is a drop-in superset. Here is a complete point-in-time read, a token account's state as of slot 400,000,000: +These three are also mutually exclusive with `minContextSlot`; pairing it with any of them is rejected. They work only with `finalized` commitment (the default): combining a historical parameter with `processed` or `confirmed` is rejected as invalid params. Omit all three and you get a normal, latest-state `getAccountInfo`; the archive is a drop-in superset. Here is a complete point-in-time read, a token account's state as of slot 400,000,000: ```bash curl https://solana-mainnet.g.alchemy.com/v2/{apiKey} \ diff --git a/src/openrpc/chains/_components/solana/account.yaml b/src/openrpc/chains/_components/solana/account.yaml index 9d32590d7..35b7b8f9d 100644 --- a/src/openrpc/chains/_components/solana/account.yaml +++ b/src/openrpc/chains/_components/solana/account.yaml @@ -47,7 +47,8 @@ components: [Account Archive](/docs/solana/account-archive) parameter. Return the account's state as of slot `S` (inclusive): the latest write with `slot <= S`. Mutually exclusive with `lastUpdateBeforeSlot`, - `firstUpdateAfterSlot`, and `minContextSlot`. + `firstUpdateAfterSlot`, and `minContextSlot`; requires + `finalized` commitment (the default). lastUpdateBeforeSlot: type: integer minimum: 0 @@ -57,7 +58,7 @@ components: The response's `context.slot` is the actual slot at which that write landed and can be used as the cursor for the next call. Mutually exclusive with `slot`, `firstUpdateAfterSlot`, and - `minContextSlot`. + `minContextSlot`; requires `finalized` commitment (the default). firstUpdateAfterSlot: type: integer minimum: 0 @@ -67,7 +68,7 @@ components: response's `context.slot` is the actual slot at which that write landed and can be used as the cursor for the next call. Mutually exclusive with `slot`, `lastUpdateBeforeSlot`, and - `minContextSlot`. + `minContextSlot`; requires `finalized` commitment (the default). GetBalanceConfig: title: GetBalance Configuration type: object diff --git a/src/openrpc/chains/_components/solana/methods.yaml b/src/openrpc/chains/_components/solana/methods.yaml index b7ed5bcb7..397668719 100644 --- a/src/openrpc/chains/_components/solana/methods.yaml +++ b/src/openrpc/chains/_components/solana/methods.yaml @@ -38,8 +38,8 @@ components: message: >- Out of coverage. The requested historical slot is outside the [Account Archive](/docs/solana/account-archive)'s coverage - window: below the earliest indexed slot, above the requested - commitment's tip, or a `lastUpdateBeforeSlot` / + window: below the earliest indexed slot, above the latest + finalized slot, or a `lastUpdateBeforeSlot` / `firstUpdateAfterSlot` cursor stepped past the edge of coverage (the terminal for a history walk). - code: -32602 @@ -48,8 +48,10 @@ components: `firstUpdateAfterSlot`, and `minContextSlot` are mutually exclusive; specifying more than one returns "More than one of slot / lastUpdateBeforeSlot / firstUpdateAfterSlot / - minContextSlot specified". Also returned for a malformed Pubkey - or configuration value. + minContextSlot specified". The historical parameters require + `finalized` commitment (the default); combining them with + `processed` or `confirmed` is rejected. Also returned for a + malformed Pubkey or configuration value. getBalance: name: getBalance From 7da8ab9721c7e3ac640abefc1356414f8c03bfb2 Mon Sep 17 00:00:00 2001 From: deepak <96074752+deepakbnsl@users.noreply.github.com> Date: Mon, 3 Aug 2026 08:35:25 -0500 Subject: [PATCH 8/8] Trim exact error string from getAccountInfo -32602 entry Co-authored-by: Cursor --- src/openrpc/chains/_components/solana/methods.yaml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/openrpc/chains/_components/solana/methods.yaml b/src/openrpc/chains/_components/solana/methods.yaml index 397668719..07ba1ae47 100644 --- a/src/openrpc/chains/_components/solana/methods.yaml +++ b/src/openrpc/chains/_components/solana/methods.yaml @@ -46,12 +46,10 @@ components: message: >- Invalid params. `slot`, `lastUpdateBeforeSlot`, `firstUpdateAfterSlot`, and `minContextSlot` are mutually - exclusive; specifying more than one returns "More than one of - slot / lastUpdateBeforeSlot / firstUpdateAfterSlot / - minContextSlot specified". The historical parameters require - `finalized` commitment (the default); combining them with - `processed` or `confirmed` is rejected. Also returned for a - malformed Pubkey or configuration value. + exclusive. The historical parameters require `finalized` + commitment (the default); combining them with `processed` or + `confirmed` is rejected. Also returned for a malformed Pubkey + or configuration value. getBalance: name: getBalance