This article explains how to query Solana historical data via JSON-RPC, covering the difference between full nodes and archive nodes, key methods like getSignaturesForAddress and getTransaction, a runnable pagination pattern, practical limits, and production considerations.
Direct Answer: How to Query Solana Historical Data
To query Solana historical data over RPC, you use a set of JSON-RPC methods that return transaction signatures, transaction details, and block data. The primary method is getSignaturesForAddress, which returns a list of transaction signatures for a given address, paginated with before and limit parameters. You then fetch each transaction's full details with getTransaction. For block-level data, use getBlock and getBlocks. However, the availability of historical data depends on the node type: a standard full node retains only a bounded window of recent slots (typically a few days), while an archive node stores the entire ledger from genesis, bounded only by disk space. Public RPC endpoints often run full nodes, so they may return errors for older slots. For sustained production access, you need a dedicated archive endpoint or an indexer.
This guide walks through the mechanics, provides a runnable Node.js script, and discusses practical limits and production patterns. For a quick reference, see the Solana API guide and the Solana network page.
What 'Historical' Means on Solana: Full Nodes vs. Archive Nodes
On Solana, 'historical' data is not defined by time but by slot number. A full node (also called a validator or RPC node) retains a rolling window of recent slots, typically the last few days, to serve live queries. This window is configurable via the --limit-ledger-size parameter, but by default, nodes prune older slots to save disk space. When you query a pruned slot, the node returns an error like "Slot X was skipped, or missing due to ledger jump to recent snapshot".
An archive node, on the other hand, stores the entire ledger from genesis, bounded only by RocksDB disk capacity. Archive nodes are essential for querying data older than the full node window. The Solana documentation states that archive nodes are 'a node that stores all blocks from genesis' and are used for historical queries. The JSON-RPC methods that return historical data include: getSignaturesForAddress, getTransaction, getBlock, getBlockTime, getBlocks, getTransactionCount, and getHighestSnapshotSlot. These methods work on both full and archive nodes, but the depth of history they can access depends on the node's retention.
For a deeper dive into how Solana nodes manage ledger storage, refer to the Solana validator documentation.
- Full node: retains a bounded window of recent slots (e.g., last 2 days).
- Archive node: stores the entire ledger from genesis, bounded by disk.
- Pruned slots return errors like 'Slot was skipped, or missing due to ledger jump to recent snapshot'.
Key RPC Methods for Historical Data
The following JSON-RPC methods are your primary tools for querying historical data on Solana:
getSignaturesForAddress returns a list of transaction signatures for a given address, ordered from newest to oldest. It accepts before (a signature to start from, exclusive) and limit (max 1000) parameters for pagination. Each item includes signature, slot, err, memo, blockTime, and confirmationStatus.
getTransaction fetches a single transaction by signature. It accepts an encoding parameter (e.g., jsonParsed) and a commitment (e.g., confirmed). The response includes slot, blockTime, meta (with err, fee, preBalances, postBalances, innerInstructions, logMessages), and transaction (with message and signatures).
getBlock returns a block by slot number, including transactions and rewards. getBlocks returns a list of block slots within a range. getBlockTime returns the timestamp for a given slot. getTransactionCount returns the total number of transactions processed by the node (not historical per se, but useful for context). getHighestSnapshotSlot returns the highest slot for which a snapshot is available, which can indicate the earliest point a full node can serve without archive data.
For a complete reference, see the Solana RPC API documentation.
Runnable Example: Paginating Through an Address's History
Below is a self-contained Node.js script that pages backwards through an address's transaction history and fetches parsed transaction details. It uses the fetch API (Node 18+) and a public RPC endpoint (replace with your own endpoint). The script prints the signature, slot, block time, and any error for each transaction.
To run it, save as solana-history.js and execute with node solana-history.js. It will fetch up to 1000 signatures per page and then fetch each transaction, with a small delay to avoid rate limits. The script stops when fewer than the limit are returned or when an error occurs.
const endpoint = 'https://api.mainnet-beta.solana.com'; // Replace with your RPC endpoint
const address = 'YourBase58AddressHere'; // Replace with the address to query
async function rpcCall(method, params) {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params })
});
const json = await response.json();
if (json.error) throw new Error(json.error.message);
return json.result;
}
async function getHistory() {
let before = undefined;
let total = 0;
while (true) {
const params = [address, { limit: 1000, before: before }];
const signatures = await rpcCall('getSignaturesForAddress', params);
if (signatures.length === 0) break;
for (const sigInfo of signatures) {
const tx = await rpcCall('getTransaction', [sigInfo.signature, { encoding: 'jsonParsed' }]);
console.log(`Signature: ${sigInfo.signature}`);
console.log(`Slot: ${sigInfo.slot}, BlockTime: ${sigInfo.blockTime}`);
if (tx && tx.meta) {
console.log(`Error: ${tx.meta.err || 'None'}`);
console.log(`Fee: ${tx.meta.fee}`);
}
total++;
await new Promise(resolve => setTimeout(resolve, 200)); // Rate limit courtesy
}
before = signatures[signatures.length - 1].signature;
if (signatures.length < 1000) break;
}
console.log(`Total transactions fetched: ${total}`);
}
getHistory().catch(err => console.error(err));Expected JSON Shape and Field Meanings
When you call getSignaturesForAddress, each item in the result array looks like this:
The signature is the base58-encoded transaction signature. slot is the slot number in which the transaction was included. err is null if the transaction succeeded, or an object describing the error. memo is an optional memo string. blockTime is the Unix timestamp of the block. confirmationStatus indicates the level of confirmation (e.g., 'finalized').
When you call getTransaction, the response includes a slot, blockTime, and meta object. The meta contains err (null if success), fee (in lamports), preBalances and postBalances (arrays of balances before and after), innerInstructions, and logMessages. The transaction object contains message (with accountKeys, instructions, etc.) and signatures array.
To verify the data, you can cross-check the blockTime with the slot using getBlockTime, or compare the preBalances and postBalances with the fee to ensure consistency.
- getSignaturesForAddress result item: { signature, slot, err, memo, blockTime, confirmationStatus }
- getTransaction result: { slot, blockTime, meta: { err, fee, preBalances, postBalances, innerInstructions, logMessages }, transaction: { message, signatures } }
Common Failures and How to Fix Them
When querying historical data, you may encounter several common errors:
Slot was skipped, or missing due to ledger jump to recent snapshot: This occurs when the node has pruned the slot. Fix: use an archive node or a dedicated historical data provider. Public RPCs often have limited history.
- Rate limiting: Public endpoints may return HTTP 429 or JSON-RPC errors like
"Too many requests". Fix: implement exponential backoff, reduce request frequency, or use a dedicated endpoint with higher limits. OnFinality offers dedicated API services with scalable rate limits.
- Response size:
getTransactionwithjsonParsedcan be large for complex transactions. Fix: usejsonencoding if you don't need parsed instructions, or fetch only specific fields by usinggetSignaturesForAddressand thengetTransactionselectively.
- Slot gaps:
getBlocksmay return gaps if blocks are skipped. Fix: handle missing slots gracefully by checking for null responses.
- Non-archive endpoints: If you need data older than a few days, ensure your endpoint is an archive node. Check with
getHighestSnapshotSlotto see the earliest slot available.
Tradeoffs and Limitations
Querying historical data over RPC has inherent tradeoffs. First, the retention window of a full node is not a benchmark; it depends on node configuration, disk space, and pruning settings. OnFinality's endpoints, for example, may have different retention policies, so always verify the actual availability for your use case.
Second, paginating through an address's entire history can be slow and resource-intensive, especially for high-activity addresses. Each page of 1000 signatures requires 1000 separate getTransaction calls, which can hit rate limits and take significant time. For production, consider using an indexer or a data service that provides bulk access.
Third, public RPC endpoints are not designed for heavy historical queries. They are shared and rate-limited. For sustained production history, you should use a dedicated or archive endpoint, or an indexer like OnFinality's general historical blockchain data access.
Finally, the JSON-RPC methods return raw data; you must handle parsing and storage yourself. For large-scale analysis, a data warehouse or indexer is more efficient.
- Full node retention is configurable and not a fixed benchmark.
- Pagination is slow for large histories; consider indexers.
- Public RPCs are rate-limited; use dedicated endpoints for production.
- Raw RPC data requires additional processing for analysis.
Production Patterns and Next Steps
For production applications that need reliable historical data, consider the following patterns:
- Use an archive endpoint: If you need full history, subscribe to an archive RPC service. OnFinality provides Solana RPC endpoints with configurable retention and high availability.
- Cache and index: Instead of querying RPC repeatedly, build a local index of transactions using
getSignaturesForAddressandgetTransaction, and store them in a database. This reduces RPC load and speeds up queries.
- Use webhooks or streaming: For real-time data, use Solana's WebSocket subscriptions to capture transactions as they happen, and store them for later analysis.
- Consider third-party indexers: Services like Helius or QuickNode offer historical data APIs that abstract away the complexity. However, they may have their own limitations and costs.
- Monitor rate limits: Implement robust retry logic with exponential backoff and respect the
Retry-Afterheader if present.
For more guidance, explore the Solana API guide and the pricing page to choose a plan that fits your needs. Also, read about accessing historical blockchain data for a broader perspective.