Multi-Exchange Balance Aggregation Implementation in Mobile App
User holds assets on Binance, Bybit and OKX simultaneously. Each exchange is separate app, separate login, separate interface. Balance aggregation solves a simple task: one screen with combined position per asset and breakdown by exchanges.
Architecture for Multiple Exchanges
Each exchange is separate adapter implementing unified protocol. This allows adding new exchanges without business logic changes:
// Android, Kotlin
interface ExchangeAdapter {
suspend fun fetchSpotBalances(): Result<List<Balance>>
suspend fun fetchFuturesBalances(): Result<List<Balance>>
val exchangeId: String
}
data class Balance(
val ticker: String,
val available: BigDecimal,
val locked: BigDecimal,
val exchangeId: String
)
class BinanceAdapter(private val apiKey: String, private val secret: String) : ExchangeAdapter {
override val exchangeId = "binance"
override suspend fun fetchSpotBalances(): Result<List<Balance>> = runCatching {
val timestamp = System.currentTimeMillis()
val queryString = "timestamp=$timestamp&recvWindow=5000"
val signature = HmacSHA256.sign(queryString, secret)
val response = apiClient.get("/api/v3/account?$queryString&signature=$signature")
response.balances
.filter { it.free.toBigDecimal() > BigDecimal.ZERO || it.locked.toBigDecimal() > BigDecimal.ZERO }
.map { Balance(it.asset, it.free.toBigDecimal(), it.locked.toBigDecimal(), exchangeId) }
}
}
Aggregator runs all adapters in parallel via async/await (Kotlin coroutines or Swift TaskGroup), collects results and collapses positions by ticker:
class BalanceAggregator(private val adapters: List<ExchangeAdapter>) {
suspend fun aggregate(): AggregatedPortfolio = coroutineScope {
val results = adapters.map { adapter ->
async { adapter.fetchSpotBalances() }
}.awaitAll()
val allBalances = results.flatMap { it.getOrElse { emptyList() } }
// Group by ticker, sum
val byTicker = allBalances.groupBy { it.ticker }
val aggregated = byTicker.map { (ticker, positions) ->
AggregatedPosition(
ticker = ticker,
totalAvailable = positions.sumOf { it.available },
byExchange = positions.associateBy { it.exchangeId }
)
}
AggregatedPortfolio(positions = aggregated, fetchedAt = Instant.now())
}
}
What Complicates the Task
Different ticker formats. Binance calls Tether USDT, some exchanges USDT, others USDt. OKX for TON uses TON-USDT as trading pair, but base asset is TON. Need normalization: alias table and canonical ticker per asset.
One exchange error shouldn't break everything. If Bybit returns 503, user should see Binance and OKX data with "Bybit: unavailable" note. So Result<> is not optional but required return type. On UI: each exchange source shows status (green/red/gray).
API keys and security. User adds multiple key pairs. Each pair encrypted separately via Android Keystore / iOS Keychain with biometry binding. On exchange request key decrypted in memory, used, not stored on heap longer than necessary.
Clock drift. Binance and Bybit require timestamp close to server time (±5 seconds). On first request to each exchange sync time via their /time endpoint and keep offset.
UI: What to Show
Main screen — asset list with total position. Tap asset — expand by exchanges. Additional screen — breakdown by exchanges: how much on each in USD. Update on pull-to-refresh and automatically every 5 minutes while app active.
Small table for understanding scope:
| Exchange | Endpoint | Authorization | Features |
|---|---|---|---|
| Binance | /api/v3/account |
HMAC-SHA256 | recvWindow, clock sync |
| Bybit V5 | /v5/account/wallet-balance |
HMAC-SHA256 | accountType: UNIFIED |
| OKX | /api/v5/account/balance |
HMAC + passphrase | 3 auth headers |
| Gate.io | /api/v4/spot/accounts |
HMAC-SHA512 | — |
Timeframe
Three exchange integration with aggregation and UI: 5–8 work days. Each additional exchange — 1 day. Cost calculated individually after requirements analysis.







