Multichain balances
Usesdk.wallets.getMultichainBalances to fetch token balances across multiple chains and networks in a single call. Use it for portfolio-style views instead of calling getBalance per wallet.
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BalanceRequestItem
import com.dynamic.sdk.android.Models.ChainEnum
import com.dynamic.sdk.android.Models.MultichainBalanceRequest
val sdk = DynamicSDK.getInstance()
val evmWallet = sdk.wallets.userWallets.first { it.chain == "EVM" }
val request = MultichainBalanceRequest(
balanceRequests = listOf(
BalanceRequestItem(
address = evmWallet.address,
chain = ChainEnum.EVM,
networkIds = listOf(1, 137, 56) // Ethereum, Polygon, BNB
)
),
filterSpamTokens = true
)
// Inside a coroutine
val response = sdk.wallets.getMultichainBalances(request)
response.balances.forEach { token ->
println("${token.symbol}: ${token.balanceDecimal}")
}
response.balances is a flat list of TokenBalance items. Each item exposes balance, balanceDecimal, symbol, name, decimals, networkId, chain, logoUrl, and isNative.
Get Native Token Balance
getBalance returns the native asset only (for example ETH on Ethereum, SOL on Solana) for the wallet’s active network. For token balances across chains and networks, use getMultichainBalances above.import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
val sdk = DynamicSDK.getInstance()
// Get EVM wallet balance
val evmWallet: BaseWallet = sdk.wallets.userWallets.first { it.chain == "EVM" }
val ethBalance = sdk.wallets.getBalance(evmWallet)
println("ETH Balance: $ethBalance")
// Get Solana wallet balance
val solanaWallet: BaseWallet = sdk.wallets.userWallets.first { it.chain == "SOL" }
val solBalance = sdk.wallets.getBalance(solanaWallet)
println("SOL Balance: $solBalance")
Balance Display with UI
Create a balance display component:import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class BalanceViewModel(private val wallet: BaseWallet) : ViewModel() {
private val sdk = DynamicSDK.getInstance()
private val _balance = MutableStateFlow<String?>(null)
val balance: StateFlow<String?> = _balance
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
private val _errorMessage = MutableStateFlow<String?>(null)
val errorMessage: StateFlow<String?> = _errorMessage
init {
loadBalance()
}
fun loadBalance() {
viewModelScope.launch {
_isLoading.value = true
_errorMessage.value = null
try {
val balanceValue = sdk.wallets.getBalance(wallet)
_balance.value = balanceValue
} catch (e: Exception) {
_errorMessage.value = "Failed to load balance: ${e.message}"
}
_isLoading.value = false
}
}
fun refresh() {
loadBalance()
}
}
Jetpack Compose UI
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun BalanceScreen(viewModel: BalanceViewModel, wallet: BaseWallet) {
val balance by viewModel.balance.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
val errorMessage by viewModel.errorMessage.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Wallet Balance",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(8.dp))
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.size(32.dp))
} else if (balance != null) {
Text(
text = formatBalance(balance!!, wallet.chain),
style = MaterialTheme.typography.headlineLarge
)
Text(
text = getNativeTokenSymbol(wallet.chain),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary
)
} else {
Text(
text = "No balance",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(modifier = Modifier.height(16.dp))
// Wallet address
Text(
text = wallet.address,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1
)
Spacer(modifier = Modifier.height(16.dp))
// Refresh button
Button(
onClick = { viewModel.refresh() },
enabled = !isLoading
) {
Icon(Icons.Default.Refresh, contentDescription = "Refresh")
Spacer(modifier = Modifier.width(8.dp))
Text("Refresh Balance")
}
}
}
errorMessage?.let { error ->
Spacer(modifier = Modifier.height(16.dp))
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
)
) {
Text(
text = error,
modifier = Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.onErrorContainer
)
}
}
}
}
fun formatBalance(balance: String, chain: String): String {
return try {
val balanceValue = balance.toDoubleOrNull() ?: 0.0
String.format("%.4f", balanceValue)
} catch (e: Exception) {
balance
}
}
fun getNativeTokenSymbol(chain: String): String {
return when (chain.uppercase()) {
"EVM" -> "ETH"
"SOL" -> "SOL"
else -> "TOKEN"
}
}
Multiple Wallet Balances
Display balances for all user wallets:import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dynamic.sdk.android.DynamicSDK
import com.dynamic.sdk.android.Models.BaseWallet
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
data class WalletWithBalance(
val wallet: BaseWallet,
val balance: String?,
val isLoading: Boolean = false
)
class WalletBalancesViewModel : ViewModel() {
private val sdk = DynamicSDK.getInstance()
private val _walletBalances = MutableStateFlow<List<WalletWithBalance>>(emptyList())
val walletBalances: StateFlow<List<WalletWithBalance>> = _walletBalances
init {
loadAllBalances()
}
private fun loadAllBalances() {
viewModelScope.launch {
val wallets = sdk.wallets.userWallets
_walletBalances.value = wallets.map { wallet ->
WalletWithBalance(wallet, null, true)
}
// Load each balance
wallets.forEach { wallet ->
launch {
try {
val balance = sdk.wallets.getBalance(wallet)
updateWalletBalance(wallet.address, balance, false)
} catch (e: Exception) {
updateWalletBalance(wallet.address, null, false)
}
}
}
}
}
private fun updateWalletBalance(address: String, balance: String?, isLoading: Boolean) {
_walletBalances.value = _walletBalances.value.map { item ->
if (item.wallet.address == address) {
item.copy(balance = balance, isLoading = isLoading)
} else {
item
}
}
}
fun refresh() {
loadAllBalances()
}
}
@Composable
fun AllWalletsBalanceScreen(viewModel: WalletBalancesViewModel) {
val walletBalances by viewModel.walletBalances.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "My Wallets",
style = MaterialTheme.typography.headlineMedium
)
IconButton(onClick = { viewModel.refresh() }) {
Icon(Icons.Default.Refresh, contentDescription = "Refresh")
}
}
Spacer(modifier = Modifier.height(16.dp))
walletBalances.forEach { item ->
WalletBalanceCard(item)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
@Composable
fun WalletBalanceCard(item: WalletWithBalance) {
Card(
modifier = Modifier.fillMaxWidth()
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = item.wallet.chain.uppercase(),
style = MaterialTheme.typography.titleMedium
)
if (item.isLoading) {
CircularProgressIndicator(modifier = Modifier.size(24.dp))
} else if (item.balance != null) {
Text(
text = "${formatBalance(item.balance, item.wallet.chain)} ${getNativeTokenSymbol(item.wallet.chain)}",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary
)
} else {
Text(
text = "Error",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.error
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = item.wallet.address,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1
)
}
}
}
Converting Wei to ETH
For EVM wallets, balances are often returned in Wei. Convert to ETH:import java.math.BigDecimal
import java.math.BigInteger
fun weiToEth(wei: String): String {
return try {
val weiValue = BigInteger(wei)
val ethValue = BigDecimal(weiValue).divide(BigDecimal("1000000000000000000"))
String.format("%.4f", ethValue.toDouble())
} catch (e: Exception) {
"0.0000"
}
}
// Usage
val balanceWei = sdk.wallets.getBalance(wallet)
val balanceEth = weiToEth(balanceWei)
println("Balance: $balanceEth ETH")
Best Practices
1. Cache Balances
Avoid excessive API calls by caching balances:private var lastBalanceUpdate = 0L
private var cachedBalance: String? = null
fun getBalanceWithCache(wallet: BaseWallet): String? {
val now = System.currentTimeMillis()
if (cachedBalance != null && now - lastBalanceUpdate < 30000) {
return cachedBalance
}
cachedBalance = sdk.wallets.getBalance(wallet)
lastBalanceUpdate = now
return cachedBalance
}
2. Handle Loading States
Always show loading indicators when fetching balances:_isLoading.value = true
try {
val balance = sdk.wallets.getBalance(wallet)
_balance.value = balance
} catch (e: Exception) {
_error.value = e.message
} finally {
_isLoading.value = false
}
3. Refresh on Network Switch
Update balances when switching networks:fun switchNetwork(chainId: Int) {
viewModelScope.launch {
sdk.wallets.switchNetwork(wallet, Network(JsonPrimitive(chainId)))
loadBalance() // Refresh balance after network switch
}
}
What’s Next
- Network Management - Switch between networks
- Send ETH - Send transactions
- ERC-20 Transfers - Transfer tokens