Transaction History Implementation in Mobile Crypto Wallet
Transaction history in a crypto wallet is not just a list of records from a database. It is aggregation of data from multiple blockchains, parsing raw transactions through RPC nodes or indexers, decoding smart contract input data, calculating fiat equivalents at transaction date. And everything must load quickly and work offline.
Transaction Data Sources
Getting transactions directly via RPC node (eth_getTransactionsByAddress doesn't exist in standard JSON-RPC) — impossible. Ethereum node doesn't index transactions by address. Options:
Indexer API — Moralis, Alchemy, Etherscan API, The Graph. Moralis getWalletTransactions returns all transactions by address with pagination. Etherscan API — free limit 5 req/s, sufficient for MVP.
Own indexer — if privacy needed or specific logic. The Graph allows writing subgraph with needed data schema and querying via GraphQL.
Multicall — to get token balances in one request instead of N requests.
In practice for multi-chain wallet: Alchemy/Moralis for EVM networks, Solana RPC with getSignaturesForAddress for Solana, custom modules for other networks.
Data Structure and Normalization
Transactions from different networks have different formats. Normalize to single model:
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 transaction time rate
final String? swapFromToken; // for DEX swaps
final String? swapToToken;
}
fiatValueAtTime — separate task. CoinGecko Historical API (/coins/{id}/history?date=DD-MM-YYYY) gives token price on specific date. For USD equivalent on transaction creation — request and cache in local DB, prices change.
Decoding Transaction Type
Simple ETH transfer — input: "0x", to is recipient address. But ERC-20 transfer looks like contract call with input: "0xa9059cbb...". Need to decode:
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;
}
For decoding ERC-20 transfer parameters — parse input: recipient address in bytes 4-35, amount — bytes 36-67.
Caching and Pagination
Transaction history is append-only data (old transactions don't change). Caching strategy: save all loaded transactions in SQLite via drift, on update request only new ones (from last known block).
UI pagination: LazyColumn (Android Jetpack Compose) or ListView.builder (Flutter) with pagination_controller. On scroll down — load next page via cursor-based pagination (by block number or timestamp), not offset-based.
// Flutter — cursor-based pagination
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 (send/receive/swap), by token, by date, by network. Implement locally on cached data — fast and no network requests. drift supports complex WHERE queries with indexes.
Search by address or transaction hash — full-text search in SQLite via LIKE or FTS5 extension.
Pending Transaction Status
Sent transactions first go to mempool with pending status. Need to track confirmation: periodically check eth_getTransactionReceipt for each pending transaction, or subscribe via WebSocket eth_subscribe("newHeads") and check pending list on each new block.
Scope of Work
- Indexer API integration (Moralis / Alchemy / Etherscan) or GraphQL subgraph
- Transaction normalization with multiple network support
- Transaction type decoding (transfer, approve, swap)
- Local SQLite cache with pagination
- Fiat equivalents at transaction date
- Filtering and search
- Pending transaction tracking
Timeframe
One blockchain (EVM), basic list with cache: 1–2 weeks. Multi-chain with DEX decoding, fiat equivalents and full filtering: 3–5 weeks. Cost calculated individually.







