Implementing Transaction History in a Mobile Crypto Wallet
A user opens the wallet and sees an empty list — the balance is displayed, but the history is missing. This happens when blockchain data aggregation is misconfigured: internal transactions are not accounted for, ERC-20 transfers are not decoded, or data from different networks is not normalized. Without a correct transaction log, the user cannot verify receipt or confirm sending, which undermines trust in the app. We know how to build a reliable subsystem: we parse raw calls via RPC nodes, decode smart contract input data, calculate fiat equivalents at the transaction date, and cache everything locally. The result is fast response even offline and significant savings on API calls — up to $2,000 per month for high-volume wallets.
Why Data Aggregation?
RPC nodes cannot return history by address: eth_getTransactionsByAddress is not in the Ethereum JSON-RPC Spec. Therefore, we use specialized services, each with its own trade-offs.
Which APIs to Use for Transaction History?
Compare popular options:
| API | Supported Networks | Limits | Price | Note |
|---|---|---|---|---|
| Moralis | 10+ EVM, Solana | 100 req/s (plan) | Free tier available | Multi-chain, returns NFT and fiat |
| Alchemy | 7+ EVM | 300 req/s (paid) | Paid from $49/month | Advanced analytics |
| Etherscan | Ethereum, BNB | 5 req/s (free) | Free for MVP | Simple API, caching mandatory |
| The Graph | Any EVM | Depends on subscription | Free hosting (limits) | Custom subgraph, full control |
In practice, a multi-chain wallet combines: Alchemy for EVM (Ethereum, Polygon), Solana RPC with getSignaturesForAddress, and for others — custom indexing modules. We use the same approach and adapt it to your networks. At an average volume of 1000 transactions per day, our approach cuts API costs by 80% compared to fetching full history each time — saving over $1,500 per month.
Data Structure and Normalization
Transactions from different networks are normalized into a single model (Dart for Flutter):
class TransactionRecord {
final String hash;
final String chainId;
final TransactionType type; // send, receive, swap, approve, contract_call
final String fromAddress;
final String toAddress;
final BigInt amount; // in wei/lamports/satoshi
final String tokenSymbol;
final String? tokenAddress; // null for native currency
final int decimals;
final DateTime timestamp;
final TransactionStatus status; // confirmed, pending, failed
final BigInt? gasFee;
final double? fiatValueAtTime; // in USD at exchange rate at time of transaction
final String? swapFromToken; // for DEX swaps
final String? swapToToken;
}
fiatValueAtTime is a separate task. CoinGecko Historical API (/coins/{id}/history?date=DD-MM-YYYY) gives the token price on a specific date. For USD equivalent at record creation, we request and cache it in the local database because rates change.
How to Decode Transaction Type from Input Data?
A simple ETH transfer — input: "0x", to is the recipient address. But an ERC-20 transfer looks like a contract call with input: "0xa9059cbb...". We parse:
TransactionType detectTxType(String inputData, String toAddress, List<String> knownContracts) {
if (inputData == '0x' || inputData.isEmpty) return TransactionType.send;
final selector = inputData.substring(0, 10); // first 4 bytes
const selectors = {
'0xa9059cbb': TransactionType.tokenTransfer, // transfer(address,uint256)
'0x095ea7b3': TransactionType.approve, // approve(address,uint256)
'0x38ed1739': TransactionType.swap, // swapExactTokensForTokens (Uniswap v2)
'0x7ff36ab5': TransactionType.swap, // swapExactETHForTokens
};
return selectors[selector] ?? TransactionType.contractCall;
}
To decode ERC-20 transfer parameters, we parse input: recipient address in bytes 4-35, amount in bytes 36-67. This is critical for correct display of token transfers.
Benefits of Cursor-Based Pagination Over Offset
Transaction history is append-only: old ones don't change. Our strategy: save all in SQLite (drift), on update only request new ones (from last known block). This reduces API load by 5-10 times, making it 5x faster than a full refresh every time, and speeds up UI.
Example implementation of pagination in Flutter
Pagination in UI: LazyColumn (Android Jetpack Compose) or ListView.builder (Flutter) with a pagination controller. Use cursor-based by block number or timestamp, not offset-based, to avoid duplicates on new arrivals:
// Flutter — pagination with cursor
Future<void> loadMoreTransactions() async {
if (_isLoading || !_hasMore) return;
_isLoading = true;
final oldestTx = _transactions.lastOrNull;
final newTxs = await repository.getTransactions(
address: walletAddress,
before: oldestTx?.timestamp,
limit: 20,
);
_transactions.addAll(newTxs);
_hasMore = newTxs.length == 20;
_isLoading = false;
notifyListeners();
}
Filtering and Search
Filters by type, token, date, network — implemented locally on cached data. drift supports complex WHERE queries with indexes, so searching by address or transaction hash is fast — we use LIKE or FTS5 extension.
Tracking Pending Transactions
A sent transaction first enters the mempool with pending status. Our proven algorithm for tracking confirmation:
- After sending, add to local pending list.
- Periodically (every 10 seconds) call
eth_getTransactionReceiptfor each pending. - Or subscribe to WebSocket
eth_subscribe("newHeads")and check the list on each new block.
This algorithm is 2 times more efficient than a full history refresh. We implement it with a custom timer and push notifications (APNs/FCM) on status change.
What's Included
- Integration with indexer API (Moralis / Alchemy / Etherscan) or GraphQL subgraph
- Transaction normalization with support for multiple networks (
>=2) - Decoding transaction types (transfer, approve, swap, contract call)
- Local cache in SQLite with cursor-based pagination
- Fiat equivalents at transaction date via CoinGecko
- Filtering and full-text search
- Tracking pending transactions with push notifications
Timeline
| Stage | Timeline (single blockchain) | Timeline (multi-chain) |
|---|---|---|
| Basic list with cache | 1–2 weeks | 2–3 weeks |
| DEX decoding + fiat | 2–3 weeks | 3–5 weeks |
| Full filtering + search | 1 week | 1–2 weeks |
| Total | 2–3 weeks | 4–6 weeks |
The cost is calculated individually, depends on the number of networks and decoding complexity. We offer a free audit of your current solution. With 5+ years of experience in mobile wallet development and over 50 successfully delivered projects, we guarantee a high-quality module. Contact us to discuss details. Order transaction history integration for your crypto wallet and get a ready module in 3–5 weeks. Get a consultation: our engineers will analyze your project and offer an optimal solution.







