Skip to main content
Check wallet balances for native tokens (ETH, SOL) and tokens (ERC-20, SPL).

Get Native Token Balance

Retrieve the native token balance (ETH for EVM, SOL for Solana):
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