Реализация отображения баланса токенов (ERC-20/BEP-20/SPL) в мобильном кошельке
Показать баланс ETH — один вызов eth_getBalance. Показать балансы двадцати ERC-20 токенов одновременно — уже архитектурный вопрос. Последовательные RPC-запросы на каждый токен дают неприемлемую задержку и нагружают ноду. Здесь нужны Multicall и грамотное кэширование.
Пакетная загрузка балансов через Multicall3
Multicall3 — контракт 0xcA11bde05977b3631167028862bE2a173976CA11, задеплоенный в большинстве EVM-сетей (Ethereum, Polygon, BNB Chain, Arbitrum, Optimism, Base). Один вызов aggregate3 возвращает балансы всех токенов за одну транзакцию чтения.
// 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 + ручная сборка Multicall
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()
Для сетей без Multicall3 (некоторые L2 или приватные EVM-сети) — JSON-RPC batch request: массив вызовов в одном HTTP-теле. Большинство нод поддерживают до 100 запросов в batch.
SPL-токены на Solana
Solana принципиально отличается: каждый SPL-токен хранится на отдельном Associated Token Account (ATA). Список ATA для кошелька получают через getTokenAccountsByOwner с программой TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.
// 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
}
У пользователя может быть 50+ ATA, включая нулевые балансы от старых airdrop'ов. Нулевые нужно скрывать по умолчанию, но давать опцию «показать все».
Курсы токенов и фиатный эквивалент
Отображение баланса без фиатного эквивалента — половина работы. Для получения курсов: CoinGecko API (/simple/price?ids=...&vs_currencies=usd) или CoinMarketCap. CoinGecko Free tier — 30 запросов в минуту, достаточно для большинства кошельков.
Ключи токенов для CoinGecko — это contract address, не тикер. Для Ethereum: https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=0x...&vs_currencies=usd. Запрос до 100 контрактов за раз — пакетируй.
Кэширование и частота обновления
| Данные | Частота обновления | Хранение |
|---|---|---|
| Балансы токенов | Каждые 30 сек / pull-to-refresh | In-memory |
| Список токенов пользователя | При каждом запуске | SQLite / UserDefaults |
| Курсы токенов | Каждые 60 сек | In-memory + disk cache |
| Метаданные токена (имя, decimals) | Однократно | SQLite |
Decimals токена критически важны: ERC-20 возвращает баланс в минимальных единицах. USDC — 6 decimals, ETH — 18. Отображаемый баланс: rawBalance / 10^decimals. Ошибка в decimals — баланс покажет астрономическую или нулевую сумму.
Сроки: 3–5 дней: Multicall3 для EVM, SPL-аккаунты для Solana, интеграция курсов, кэширование, UI списка с pull-to-refresh. Если требуется мультисеть (ETH + BSC + Polygon + Solana одновременно) — 5–7 дней.







