Crypto Portfolio Tracking Implementation in Mobile App
User holds BTC on Binance, ETH in MetaMask and some altcoins on Bybit. Wants to see total portfolio value, 24-hour change and asset distribution — in one screen, without manual input. That's portfolio tracking: data aggregation from different sources with current prices.
Data Sources
Three sources, each with nuances:
Exchange APIs. Binance /api/v3/account returns spot wallet balances but requires HMAC-SHA256 signature with timestamp and recvWindow. Bybit V5 /v5/account/wallet-balance — likewise. Keys stored encrypted on device (iOS Keychain, Android Keystore), private keys never transmitted — only read-only API keys with IP restriction if exchange supports.
On-chain wallets. For Ethereum wallets — Alchemy or Infura /eth_getBalance plus alchemy_getTokenBalances for ERC-20. For Solana — getTokenAccountsByOwner via Helius or QuickNode. Address is public, no keys needed.
Manual input. Some assets users add manually (staking, p2p, hardware wallets). Simple form: ticker + quantity.
Current prices — via CoinGecko API /simple/price (up to 30 tickers per request) or CoinMarketCap Pro API. CoinGecko free plan limited to 30 requests/minute — sufficient for app with background update if prices cached with 60-second TTL.
// iOS, Swift — portfolio aggregation
actor PortfolioAggregator {
private let exchangeService: ExchangeService
private let priceService: PriceService
private let walletService: WalletService
func aggregate() async throws -> Portfolio {
async let exchangeBalances = exchangeService.fetchAll()
async let onchainBalances = walletService.fetchAll()
let (exchange, onchain) = try await (exchangeBalances, onchainBalances)
let allTickers = (exchange + onchain).map(\.ticker)
let prices = try await priceService.getPrices(tickers: allTickers)
let positions = (exchange + onchain).map { balance in
PortfolioPosition(
ticker: balance.ticker,
amount: balance.amount,
priceUsd: prices[balance.ticker] ?? 0,
source: balance.source
)
}
return Portfolio(positions: positions, updatedAt: .now)
}
}
async let allows requesting exchanges and on-chain balances in parallel — aggregation fits in 1–2 seconds instead of sequential 4–5.
Storage and Updates
Balances cached locally — SQLite via drift (Flutter) or CoreData/SwiftData (iOS). Important for offline mode: user opens app without network and sees last actual data with timestamp.
Background update on iOS — via BGAppRefreshTask. Scheduled when backgrounding, system calls task every 15–30 minutes (exact interval determined by iOS based on usage patterns). On Android — WorkManager with PeriodicWorkRequest(15, TimeUnit.MINUTES).
Real-time price updates — WebSocket. Binance provides wss://stream.binance.com:9443/stream?streams=btcusdt@ticker/ethusdt@ticker. On app open subscribe to stream, on backgrounding disconnect and rely on cache.
Typical Pitfalls
Clock skew on exchange requests. Binance requires timestamp in request differs from server by no more than recvWindow (default 5000 ms). On Android device clocks sometimes drift. Solution: sync time via /api/v3/time before first request and apply offset.
Rate limits with many exchanges. With 5 exchanges added, simultaneous requests may exhaust limits. Need queue with frequency control: one exchange — one queue slot, 200ms delay between same endpoint requests.
Different ticker formats. Binance returns BTC, Bybit returns BTC, CoinGecko accepts bitcoin (id), not symbol. Need ticker → CoinGecko id mapping table, periodically synced via /coins/list.
Scope of Work
- Binance / Bybit / OKX API integration with HMAC signing
- On-chain balance loading (EVM via Alchemy, Solana optional)
- Local DB caching with offline support
- Background updates (BGAppRefreshTask / WorkManager)
- Dashboard: total value, 24h change, pie chart by assets
- API key storage in Keychain / Keystore
Timeframe
Two exchanges + on-chain ETH + dashboard: 5–8 work days. Each additional exchange — plus 1–2 days. Cost calculated individually after requirements analysis.







