Implementing Token Balance Display (ERC-20/BEP-20/SPL) in Mobile Wallet
Showing ETH balance: one eth_getBalance call. Showing twenty ERC-20 balances simultaneously: already an architectural question. Sequential RPC requests per token cause unacceptable delay and load the node. You need Multicall and smart caching.
Batch Token Loading via Multicall3
Multicall3—contract 0xcA11bde05977b3631167028862bE2a173976CA11, deployed on most EVM networks (Ethereum, Polygon, BNB Chain, Arbitrum, Optimism, Base). One aggregate3 call returns all token balances in one read transaction.
// iOS — web3swift + Multicall3
let multicallAddress = EthereumAddress("0xcA11bde05977b3631167028862bE2a173976CA11")!
var calls: [Multicall3.Call3] = []
for tokenAddress in tokenAddresses {
let callData = ERC20.balanceOf(owner: walletAddress).encodeABI()
calls.append(.init(target: tokenAddress, allowFailure: true, callData: callData))
}
let results = try await multicall3.aggregate3(calls: calls)
// Android — web3j + manual Multicall assembly
val multicallEncoder = Function("aggregate3", listOf(DynamicArray(calls)), listOf())
val encodedCall = FunctionEncoder.encode(multicallEncoder)
val response = web3j.ethCall(Transaction.createEthCallTransaction(null, multicallAddress, encodedCall), DefaultBlockParameterName.LATEST).send()
For networks without Multicall3 (some L2s or private EVM chains)—JSON-RPC batch request: array of calls in one HTTP body. Most nodes support up to 100 requests per batch.
SPL Tokens on Solana
Solana differs fundamentally: each SPL token lives on a separate Associated Token Account (ATA). Get ATA list for the wallet via getTokenAccountsByOwner with the TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA program.
// iOS — SolanaSwift
let tokenAccounts = try await solana.action.getTokenAccountsByOwner(
pubkey: walletPublicKey,
params: .init(programId: TokenProgram.publicKey),
configs: nil
)
for account in tokenAccounts {
let mint = account.account.data.parsed.info.mint
let amount = account.account.data.parsed.info.tokenAmount.uiAmount
}
Users can have 50+ ATAs, including zero balances from old airdrops. Hide zero balances by default, but offer "show all."
Token Prices and Fiat Equivalent
Balance without fiat equivalent: half the job. For prices: CoinGecko API (/simple/price?ids=...&vs_currencies=usd) or CoinMarketCap. CoinGecko Free tier: 30 requests per minute, sufficient for most wallets.
Token keys for CoinGecko: contract address, not ticker. For Ethereum: https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0x...&vs_currencies=usd. Batch up to 100 contracts per request.
Caching and Update Frequency
| Data | Update Frequency | Storage |
|---|---|---|
| Token balances | Every 30 sec / pull-to-refresh | In-memory |
| User token list | On each launch | SQLite / UserDefaults |
| Token prices | Every 60 sec | In-memory + disk cache |
| Token metadata (name, decimals) | Once | SQLite |
Token decimals are critical: ERC-20 returns balance in minimum units. USDC is 6 decimals, ETH is 18. Displayed balance: rawBalance / 10^decimals. Decimal error: balance shows astronomically high or zero.
Timeline: 3–5 days: Multicall3 for EVM, SPL accounts for Solana, price integration, caching, pull-to-refresh UI. Multi-chain (ETH + BSC + Polygon + Solana simultaneously): 5–7 days.







