Concepts · 8 min read
What is a wallet history API?
A wallet history API answers one question: given an address, what has it done? Transactions, token transfers, balances, and holdings, keyed by the account rather than by block. That re-keying is the whole problem, because the chain is not stored that way. This guide shows why a plain RPC endpoint cannot produce it, then the real queries that assemble a complete history from logs and traces.
1. What is a wallet history API?
A wallet history API is a read interface keyed by address. The unit of the query is an account, and the response is everything that account was involved in: outbound and inbound transactions, token transfers, the balances it holds now, what it held at a past block, and the tokens and NFTs it owns. The activity feed and portfolio total in any wallet app are rendered from one.
The defining property is the access pattern. Blockchains record data by block and by transaction; a wallet history API re-organizes that same data so it can be retrieved by address. Everything below follows from that one mismatch between how the chain stores data and how the product needs to read it.
2. Why an RPC endpoint cannot serve it
A node's JSON-RPC interface is keyed by block and transaction. There is no standard eth_getTransactionsByAddress method, because the node does not maintain an address-to-transaction index. To answer "everything this address did" from RPC alone, you would have to scan every block since genesis and filter, which is not something you can do inside a single request.
eth_getLogs helps a little: you can filter event logs by topic, which surfaces ERC-20 and ERC-721 transfers that reference an address. But logs do not include native value transfers, do not include internal transactions, and still leave you assembling a coherent timeline yourself. The practical answer is to build the index ahead of time: ingest the chain once, store it keyed by address, and serve lookups in milliseconds. That is the indexer pattern, and the trade-off against calling RPC live is covered in RPC vs indexed data.
3. The queries that assemble a history
A complete history is the union of three data types, each answering part of "what touched this address." Here is how each looks against SQD's Portal, which lets you filter all three by address.
Token transfers. ERC-20 and ERC-721 both emit a Transfer event with the sender in topic1 and the recipient in topic2, each padded to 32 bytes. To capture both directions, run two log filters (an array of filters is OR logic) and leave out the contract address so it spans every token:
POST https://portal.sqd.dev/datasets/ethereum-mainnet/stream
Accept: application/x-ndjson
{
"type": "evm",
"fromBlock": 0,
"toBlock": 21000000,
"logs": [
{ "topic0": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
"topic1": ["0x000000000000000000000000ABCDEF...the address...padded"] },
{ "topic0": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
"topic2": ["0x000000000000000000000000ABCDEF...the address...padded"] }
],
"fields": {
"block": { "number": true, "timestamp": true },
"log": { "address": true, "topics": true, "data": true, "transactionHash": true }
}
}
The log's address in the response is the token contract; the amount is in data for ERC-20 and is the indexed tokenId for ERC-721. The two standards share a topic0 and are told apart by topic count, a detail covered in what an EVM indexer handles. ERC-1155 uses separate events and needs its own filters.
Native and internal value. This is the part logs cannot give you. ETH moved to or from the address, including value a contract forwarded mid-execution, lives in traces, not logs. Query the traces data type filtered by callFrom and callTo:
{
"type": "evm",
"fromBlock": 0,
"toBlock": 21000000,
"traces": [
{ "type": ["call"], "callFrom": ["0x...the address..."] },
{ "type": ["call"], "callTo": ["0x...the address..."] }
],
"fields": {
"block": { "number": true, "timestamp": true },
"trace": { "type": true, "transactionIndex": true, "callFrom": true, "callTo": true, "callValue": true }
}
} callValue is the native value moved at each hop. An address that only ever received ETH through a contract (a withdrawal, a disbursement) shows nothing in the logs but appears here. The third piece, top-level transactions where the address is from or to, is a transactions query with the same address filter. Union the three by transaction hash and you have the complete timeline.
4. Balances, current and historical
Once the transfer history above is indexed, balances fall out of it. A token balance is the sum of every transfer in minus every transfer out for that address and token; the native balance is the same arithmetic over the value-bearing traces and transactions. Because the history is already keyed by address, this is a query against your own store, not a fresh scan of the chain.
Historical balances ("what did this address hold last March") are the same sum truncated at a block height, which is why a transfer-derived balance is more flexible than a live balanceOf call: the call only tells you the balance now. Reading balanceOf directly is still useful as a reconciliation check, but doing it for every token an address might hold is expensive, so production wallet APIs compute from transfers and verify against state selectively.
5. Where it gets hard
- Trace coverage. The native-and-internal query above is the one most data sources skip. A feed built on logs alone returns a history that silently omits contract-forwarded value, which is a correctness bug, not a gap in coverage.
- Multi-chain aggregation. Users hold assets across many networks but want one feed. That means running the same log and trace queries against each dataset and normalizing the results, the problem described in multi-chain indexing.
- Token metadata and spam. There are thousands of tokens, many airdropped purely to appear in wallets. Useful history needs metadata resolution and a way to separate real holdings from spam.
- High-activity addresses. Exchange and contract addresses can have millions of transfers. Pagination, ordering, and query performance stop being trivial at that volume.
- Standard sprawl. ERC-20, ERC-721, and ERC-1155 differ in event shape, and some tokens deviate from the standards. A complete feed handles all three plus the exceptions.
6. Wallet history with SQD
The three queries above are the actual interface. SQD's Portal lets you filter logs, traces, and transactions by address in the same request shape, so the data a complete history needs (including the internal value a log-only source misses) is reachable in one place rather than stitched from several providers.
Multi-chain is a one-line change: point the same queries at base-mainnet or arbitrum-one instead of ethereum-mainnet, across the networks listed at sqd.dev/chains. For a product you would not query the Portal per request; the Squid and Pipes SDKs stream this data into your own address-indexed store so lookups return in milliseconds. The wallets and payments solution page walks through that setup.
Frequently asked questions
Can I get wallet history from an RPC endpoint?
How do I get all token transfers for an address?
How do internal transactions show up in wallet history?
How do I get an address's token balances?
Can one API return history across multiple chains?
Related guides
Building a wallet or payments product?
See how full address history, balances, and transfers come together across networks on the wallets and payments solution page.