Skip to content
0%

Introducing historical Solana token balances

Author: Alchemy

Last updated: July 21, 20264 min read
getTokenAccountsByOwnerAtSlot — historical Solana token balances

Ask a Solana wallet what it holds today and any node can answer in one call. Ask what it held last spring, or at the exact moment right before it got drained, and the easy path disappears. Nodes serve current balances. They do not keep a running record of who held what, when. So the honest answer to "what did this wallet hold back then" has been: rebuild it yourself.

Today we are changing that. getTokenAccountsByOwnerAtSlot returns everything a wallet held at any past point in time, with exact balances, in a single call.

The problem with reconstructing history

Standard getTokenAccountsByOwner only answers one question: what does this wallet hold right now. That covers most live app flows, but it leaves a whole class of questions unanswered.

To reconstruct a past balance yourself, you replay the account's life. You pull the wallet's full signature history with getSignaturesForAddress, fetch every transaction with getTransaction, then re-derive balances slot by slot up to your target. For an active wallet that is thousands of calls, a lot of parsing, and plenty of room to get the math wrong. Archival nodes help you read old state, but they still do not answer "list this owner's token accounts at slot X" directly.

How it works

The method keeps the syntax you already know from getTokenAccountsByOwner and adds one thing: a slot. A slot is Solana's clock, roughly one block, so passing a slot means "give me this wallet as it stood at that exact point in history."

When you call it, you get back the wallet's token accounts, the individual records that hold each token a wallet owns. On Solana a wallet does not hold balances directly, so listing these accounts is how you read holdings. Each one comes with its balance and its lamports, the small amount of SOL (fractions of a coin, 1 SOL is 1 billion lamports) every account carries to exist on-chain. The method covers both of Solana's token standards: SPL Token, the original one behind effectively every well-known token like USDC, and Token-2022, the newer version with extras like transfer fees and confidential balances. Those standards live under different program IDs, so a single programId filter only returns accounts for that program.

Behind the call sits a continuously built historical index, a versioned record of every token account over time: who held which account, how much, and between which slots, kept current as new blocks land. You can scope a query two ways. By mint, to read a single token's balance at a past slot. Or by program, to list every token account owned under that program at the slot. For a full portfolio across both standards, call once with the SPL Token program ID and once with the Token-2022 program ID, then combine the results. The example below scopes to SPL Token only.

javascript
Copied
const res = await fetch("https://solana-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "getTokenAccountsByOwnerAtSlot", params: [ "89zoESAF7JqYQz5uhf5eYwg3QsKXQy5QFs4WZQ4hMPnb", { programId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" }, { encoding: "jsonParsed", slot: 80000000 } ] }) }); console.log(await res.json());

Paging stays consistent because the pageKey pins the slot you asked for. The tip of the chain can advance while you page, and your results still describe the same point in time, up to 10,000 accounts per page.

Why it matters

Point-in-time holdings become a first-class query. The indexing pipeline you would otherwise build and maintain collapses into one RPC call, which frees your team to work on the product instead of the plumbing.

That opens up work that used to be painful:

  • Portfolio and PnL snapshots at any historical moment
  • Cost basis and tax reporting tied to specific slots or dates
  • Dispute, forensics, and incident work, like showing what a wallet held the block before it was drained
  • Analytics and indexing pipelines that need accurate historical state without replay

We verified results against on-chain ground truth during testing. Sampled historical queries matched chain balances and lamports exactly, including deep history such as a KIN balance at slot 80,000,000.

Getting started

The method is available on the standard Solana mainnet endpoint, so if you already send Solana RPC through Alchemy you can call it with your existing API key. Point at https://solana-mainnet.g.alchemy.com/v2/{ALCHEMY_API_KEY}, add a slot, and read the holdings back.

See the Solana API quickstart to set up a key and send your first request.

FAQ

What does getTokenAccountsByOwnerAtSlot do?

It returns a Solana wallet's token accounts, with balances and lamports, as of a past slot you specify. It has the same shape as getTokenAccountsByOwner, plus a slot parameter.

What are SPL Token and Token-2022?

They are Solana's two token standards. SPL Token is the original one that covers effectively every well-known token on the network. Token-2022 is the newer version that adds optional features like transfer fees and confidential balances. The method covers both.

How is this different from getTokenAccountsByOwner?

getTokenAccountsByOwner returns current holdings only. getTokenAccountsByOwnerAtSlot answers the same question at any past slot, reading from a historical index rather than live node state.

Do I need an archival node or a custom indexer?

No. The method reads a prebuilt historical index, so you do not run archival infrastructure or replay transaction history yourself.

Can I query a wallet's full portfolio, not just one token?

Yes, but not in one programId call if the wallet holds both standards. Scope by mint for a single token, or by programId for every account under that program at a slot. SPL Token and Token-2022 use different program IDs, so for a complete portfolio query both and merge the responses.

How does pagination stay accurate as the chain moves?

The pageKey pins your query slot, so results stay consistent across pages even as new blocks arrive, up to 10,000 accounts per page.

Alchemy Newsletter

Be the first to know about releases

Sign up for our newsletter

Get the latest product updates and resources from Alchemy

A
O
D
+
Over 80,000 subscribers

By entering your email address, you agree to receive our marketing communications and product updates. You acknowledge that Alchemy processes the information we receive in accordance with our Privacy Notice. You can unsubscribe anytime.