Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Network & Protocol Guides12 min read

BNB Smart Chain Historical RPC and Archive Data: Querying Past Balances, Logs, and State

Learn how to query historical BNB Smart Chain (BSC) data over RPC, when you need an archive node, and how to avoid common pitfalls with eth_getBalance, eth_getLogs, and more.

TL;DR

This guide explains how to query historical BNB Smart Chain (BSC) data via RPC, focusing on archive node requirements. It covers BSC's block time and client ecosystem, what archive nodes store, and provides runnable scripts for eth_getBalance and eth_getLogs with troubleshooting tips.

Direct Answer: How to Query BSC Historical Data

To query historical BNB Smart Chain (BSC) data—such as past balances, logs, or state—you need an RPC endpoint that has archive data. Standard full nodes prune historical state, so methods like eth_getBalance at an old block or eth_getLogs over a long range will return incomplete or incorrect results. Use an archive-capable BSC endpoint and specify the historical block as a tag or in the fromBlock/toBlock parameters.

For example, to get a balance at block 10,000,000, you call eth_getBalance with the address and the block tag '0x989680' (hex). This only returns a meaningful value if the node has archive state for that block. Similarly, eth_getLogs with a wide block range requires the node to retain logs and often archive data to process efficiently. See the BNB Smart Chain node types (RPC Assistant) for a comparison of full vs archive nodes.

  • Use an archive RPC endpoint for historical queries.
  • Specify the block number as a hex string or tag (e.g., 'earliest', '0x...').
  • For logs, set fromBlock and toBlock to the desired range.
  • Check your provider's archive availability and retention policy.

BSC Fundamentals: Block Time, Clients, and Node Types

BNB Smart Chain is an EVM-compatible blockchain with a target block time of approximately 3 seconds, which results in a high transaction throughput and a large volume of historical data. This high cadence means that full nodes that prune state can only serve recent data, while archive nodes store the entire state history to answer any historical query.

BSC's client ecosystem has evolved. Originally a fork of go-ethereum (geth), BSC now has multiple client implementations. The official documentation at docs.bnbchain.org describes node types: full nodes (pruned), archive nodes, and validator nodes. Community clients like reth-bsc and bsc-erigon are also referenced in searches, offering different performance and storage tradeoffs. For this guide, we focus on the RPC methods that work across these clients, as they follow the Ethereum JSON-RPC standard.

Archive nodes keep all historical state trie data, enabling queries like eth_getBalance at any past block. Full nodes typically prune state older than a certain number of blocks (e.g., 128 blocks) and only keep recent state. Validator nodes are full nodes that participate in consensus and may not serve RPC requests publicly.

  • BSC block time: ~3 seconds (documented by BNB Chain).
  • Node types: full (pruned), archive, validator.
  • Clients: geth-based, reth-bsc, bsc-erigon (community).
  • Archive nodes store full historical state; full nodes prune.

What an Archive Node Stores Beyond a Full Node

An archive BSC node retains every historical state change, including account balances, contract code, and storage at every block. This is in contrast to a pruned full node, which only keeps the latest state and a limited history of blocks and receipts. The official BNB Chain documentation on Archive Node - BSC Develop states that archive nodes are necessary for querying historical data.

Without archive state, eth_getBalance at an old block will return the current balance or an error, because the node cannot reconstruct the past state. Similarly, eth_getLogs over a long range may fail or return incomplete results if the node has pruned logs or cannot process the range efficiently. Archive nodes also enable advanced debugging and tracing features, such as reth/erigon structured traces, which are useful for deep analysis.

The storage requirements for archive nodes are significantly higher than full nodes, but exact numbers vary by client and configuration. We do not provide specific GB figures because they are not standardized; refer to your node provider or client documentation for current estimates.

  • Archive nodes store full state history, enabling any historical query.
  • Full nodes prune state, limiting historical access.
  • Archive nodes support tracing and advanced debugging.
  • Storage requirements vary; check provider documentation.

BSC RPC Methods for Historical Data

The following JSON-RPC methods are essential for querying historical BSC data. They follow the Ethereum standard and are supported by BSC clients.

eth_getBlockByNumber: Retrieve a block by number, with full transaction objects if requested. This works on full nodes for any block, as block headers are not pruned.

eth_getLogs: Filter logs by address and topics within a block range. This can be resource-intensive on BSC due to high transaction volume; archive nodes handle larger ranges better.

eth_call, eth_getBalance, eth_getCode, eth_getProof: These methods accept a block parameter. When querying historical state, you must pass the block number or tag. They require archive state for blocks older than the pruning window.

For tracing, clients like reth-bsc and bsc-erigon provide structured traces via debug_traceTransaction or trace_* methods, but these are not part of the standard RPC and may require specific client support.

  • eth_getBlockByNumber: works on full nodes for any block.
  • eth_getLogs: needs archive for large ranges.
  • eth_getBalance, eth_call, etc.: require archive for historical blocks.
  • Tracing methods: client-specific, not standard.

Runnable Example: Querying Historical Balance and Logs

Below is a Node.js script using ethers v6 to query a historical balance and page through logs. It is self-contained and requires you to set an RPC URL (e.g., your archive endpoint). The script demonstrates how to handle timeouts and backoff for eth_getLogs.

To run, install ethers: npm install ethers. Then set the RPC_URL environment variable to your archive endpoint. The script first checks the balance at a specific block (e.g., block 10000000) and then fetches logs for a small range to avoid overwhelming the node.

Expected output: For a non-archive endpoint, the balance query may return the current balance or an error. For an archive endpoint, it returns the historical balance. The logs query returns an array of log objects.

const { ethers } = require('ethers');

const RPC_URL = process.env.RPC_URL || 'https://your-archive-endpoint.example';
const provider = new ethers.JsonRpcProvider(RPC_URL);

async function getHistoricalBalance(address, blockNumber) {
  const balance = await provider.getBalance(address, blockNumber);
  console.log(`Balance at block ${blockNumber}: ${ethers.formatEther(balance)} BNB`);
}

async function getLogsPaged(contractAddress, fromBlock, toBlock, pageSize = 1000) {
  let logs = [];
  let currentFrom = fromBlock;
  while (currentFrom <= toBlock) {
    const currentTo = Math.min(currentFrom + pageSize - 1, toBlock);
    const filter = {
      address: contractAddress,
      fromBlock: currentFrom,
      toBlock: currentTo
    };
    try {
      const batch = await provider.getLogs(filter);
      logs = logs.concat(batch);
      console.log(`Fetched ${batch.length} logs from ${currentFrom} to ${currentTo}`);
    } catch (error) {
      console.error(`Error fetching logs from ${currentFrom} to ${currentTo}:`, error.message);
      // Implement backoff: wait 1 second and retry
      await new Promise(resolve => setTimeout(resolve, 1000));
      continue;
    }
    currentFrom = currentTo + 1;
  }
  return logs;
}

async function main() {
  const address = '0x0000000000000000000000000000000000001000'; // example
  const block = 10000000; // example historical block
  await getHistoricalBalance(address, block);

  const contract = '0x...'; // replace with contract address
  const logs = await getLogsPaged(contract, 10000000, 10001000);
  console.log(`Total logs: ${logs.length}`);
}

main().catch(console.error);

Verification and Results Table

To verify that your endpoint is archive-capable, run the script above with a known historical balance. For example, you can check the balance of a well-known address at a block before a major transfer. If the returned balance matches the expected value from a block explorer, your endpoint has archive data.

Fill in the table below with your results to document the behavior of your endpoint. This method is reproducible and helps you understand the limitations of your RPC provider.

  • Endpoint type (full/archive)
  • Block number queried
  • Balance returned (BNB)
  • Logs fetched (count)
  • Errors encountered
| Endpoint Type | Block Number | Balance (BNB) | Logs Fetched | Errors |
|---------------|--------------|---------------|--------------|--------|
| Archive       | 10000000     | 123.45        | 500          | None   |
| Full          | 10000000     | 0.00 (or error) | 0          | 'missing trie node' |

Common Failures and Fixes

When querying historical BSC data, you may encounter several common errors. Here are typical failures and how to resolve them.

Error: 'missing trie node' or 'header not found' – This indicates the node does not have archive state for the requested block. Solution: Use an archive endpoint or reduce the historical depth.

Error: 'query returned more than 10000 results' – eth_getLogs has a limit on the number of results per call. Solution: Page through smaller block ranges, as shown in the script.

Error: 'rate limit exceeded' – BSC RPC providers enforce rate limits. Solution: Implement backoff and retry, and check the BNB Chain RPC rate limits and 429s guide.

Incomplete logs: If you use a full node, logs older than the pruning window may be missing. Solution: Use an archive node or a provider that retains logs longer.

  • Missing trie node: use archive endpoint.
  • Too many results: page through ranges.
  • Rate limits: implement backoff.
  • Incomplete logs: use archive node.

Tradeoffs and Limitations

Using an archive node for historical queries has tradeoffs. Archive nodes are more expensive to run and have higher latency for certain queries due to the large state. Providers may offer archive endpoints at a premium or with different rate limits. Always check your provider's documentation for specific retention and performance characteristics.

For BSC, the high transaction volume means that eth_getLogs over a wide range can be slow even on archive nodes. It's advisable to narrow your search with address and topic filters. Additionally, not all RPC providers offer archive data for BSC; some may only provide full nodes. The OnFinality API service and RPC pricing pages can help you understand options.

Finally, note that BSC's client ecosystem is evolving. While the methods described are standard, tracing and debug methods may vary. Always test your queries against your specific endpoint.

  • Archive nodes cost more and may have higher latency.
  • eth_getLogs over wide ranges can be slow; use filters.
  • Provider archive availability varies; check documentation.
  • Client-specific methods may differ.

Next Steps and Further Reading

Now that you understand how to query historical BSC data, explore these related resources to deepen your knowledge.

For a broader understanding of historical data queries across EVM chains, see the Querying historical blockchain data (EVM cookbook). To compare node types, read Archive node vs full node. If you're new to BSC, start with the BNB Smart Chain network overview.

For practical RPC usage, check the BNB Smart Chain node types (RPC Assistant) and the OnFinality Learn hub for more guides. Also, review the BNB Chain RPC rate limits and 429s to avoid hitting caps.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started