On-Chain Data Parsing (Transactions, Balances, Contracts)
You are trying to fetch historical Ethereum wallet balances, but eth_getBalance only returns the current state? Or you need to track internal contract transactions that are invisible in regular transactions? We have faced these tasks hundreds of times and know how to solve them efficiently. Instead of relying on Dune or Etherscan with their limitations, you can deploy your own parser that gives full control over data, speed, and cost. We have been building such solutions for over 10 years (50+ projects completed), and in this article we'll cover the key aspects of on-chain data parsing: from source selection to storage optimization. Infrastructure savings with this approach can reach 60%, translating to savings of up to $2,000 per month for high-volume projects.
Ethereum JSON-RPC API — official documentation the examples below are based on.
What Data Types and Sources Are Available?
Block-level transactions (eth_getBlockByNumber with fullTx: true): from/to/value/gas/gasPrice/nonce, input data (calldata in hex), receipt (status, gasUsed, logs).
Internal transactions — calls between contracts, not visible in regular transactions. Requires debug_traceTransaction or trace_block (Erigon/OpenEthereum trace namespace).
Events (logs) — emitted via emit Event(...) in Solidity, accessible through eth_getLogs. The most performant way is filtering by address + topic at the node level.
Storage state — storage variables of a contract via eth_getStorageAt(address, slot, blockNumber). With an archive node, on any historical block.
ERC-20 balances — via balanceOf(address) view call or through Transfer event history.
Source Comparison Table
| Source | What It Provides | Limitations |
|---|---|---|
| Public RPC (Infura/Alchemy) | Standard JSON-RPC | Rate limits, no traces |
| Self-hosted Geth | Full JSON-RPC | No traces without --gcmode=archive |
| Self-hosted Erigon | JSON-RPC + trace namespace | ~2.5 TB, 3-5 days sync |
| Alchemy/QuickNode (paid plans) | Extended API + traces | Cost at high RPS |
| Firehose (StreamingFast) | Binary streaming, full data | Complex setup |
| Dune Analytics / Flipside | SQL interface to indexed data | Lag, schema limitations |
Self-hosted Erigon is 10x faster than Geth for parsing traces and better suited for high loads.
How to Parse Transactions, Events, and Balances?
Parsing Transactions — basic block parser with receipt retrieval:
import { createPublicClient, http } from 'viem'; const client = createPublicClient({ transport: http(RPC_URL) }); async function processBlock(blockNumber: bigint) { const block = await client.getBlock({ blockNumber, includeTransactions: true }); for (const tx of block.transactions) { if (typeof tx === 'string') continue; const receipt = await client.getTransactionReceipt({ hash: tx.hash }); await db.insertTransaction({ hash: tx.hash, blockNumber: Number(tx.blockNumber), blockTimestamp: Number(block.timestamp), from: tx.from, to: tx.to, value: tx.value.toString(), gasPrice: tx.gasPrice?.toString(), gasLimit: tx.gas.toString(), input: tx.input, nonce: tx.nonce, status: receipt.status === 'success', gasUsed: receipt.gasUsed.toString(), }); } } Parsing Events (Logs) — example for ERC-20 Transfer:
const logs = await client.getLogs({ address: TOKEN_ADDRESS, event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'), fromBlock: 19_000_000n, toBlock: 19_100_000n, }); for (const log of logs) { await db.insertTransfer({ txHash: log.transactionHash, blockNumber: Number(log.blockNumber), from: log.args.from, to: log.args.to, value: log.args.value.toString(), }); } Limitation: eth_getLogs range is limited to 2000 blocks on most public nodes. We implement automatic chunking.
Getting Historical Balances — use archive node with readContract:
const historicalBalance = await client.readContract({ address: TOKEN_ADDRESS, abi: erc20Abi, functionName: 'balanceOf', args: [walletAddress], blockNumber: 18_500_000n, }); For mass queries, use multicall to combine up to 100 balance calls in one RPC request, reducing time by up to 90%.
Performance, Storage, and Multi-Chain Support
For Ethereum full parsing: ~6500 blocks/day × ~6000 TX/block = ~40M transactions/day. Each with receipts: ~1-5 KB. Total: ~40-200 GB/day. Our parsers handle up to 100,000 transactions per second with optimized batch processing.
Storage recommendation: PostgreSQL + TimescaleDB for time-series + S3 for raw archive. Key indexes: CREATE INDEX ON transactions (from_address, block_number DESC), CREATE INDEX ON transfers (token_address, block_number DESC).
Multi-chain configuration example:
const CHAIN_CONFIGS = { ethereum: { rpc: INFURA_ETH, chunkSize: 1000, blockTime: 12 }, bsc: { rpc: BSC_RPC, chunkSize: 2000, blockTime: 3 }, polygon: { rpc: POLYGON_RPC, chunkSize: 1500, blockTime: 2 }, arbitrum: { rpc: ARB_RPC, chunkSize: 5000, blockTime: 0.25 }, }; We support over 20 EVM-compatible chains out of the box.
Our Development Process
- Requirements analysis and data schema design.
- Parser architecture and node configuration.
- Implementation with automated testing.
- Deployment to your infrastructure (cloud or on-prem).
- Monitoring and performance tuning.
What's Included in the Deliverables
- Architectural documentation
- Full source code with comments
- Deployed infrastructure
- Team training
- 3 months of post-deployment support
With over 10 years in blockchain development and 50+ successful projects, our team brings unmatched expertise. We guarantee data accuracy with full audit logs. Pricing starts from $3,000 for a basic parser, with ongoing savings of up to 60% compared to third-party indexing services. Typical monthly savings range from $1,000 to $5,000 depending on volume. Contact us for a custom quote.







