Balance Management
This guide covers querying and managing token balances in the Privacy Boost Android SDK.Understanding Balances
Privacy Boost tracks two types of balances:| Balance Type | Description |
|---|---|
| Shielded | Private balance in the privacy pool — only you can see it |
| Wallet | Public balance in your connected wallet on-chain |
Get Single Token Balance
val balance = withContext(Dispatchers.IO) {
sdk.getBalance(tokenAddress = "0x...")
}
println("Token: ${balance.tokenAddress}")
println("Shielded: ${balance.shieldedBalance}")
println("Wallet: ${balance.walletBalance}")
println("Symbol: ${balance.symbol}")
println("Decimals: ${balance.decimals}")
Get All Balances
val balances = withContext(Dispatchers.IO) {
sdk.getAllBalances()
}
for (balance in balances) {
println("${balance.symbol ?: balance.tokenAddress}:")
println(" Shielded: ${balance.shieldedBalance}")
println(" Wallet: ${balance.walletBalance}")
}
Balance Type
data class TokenBalance(
val tokenAddress: String, // Token contract address
val shieldedBalance: String, // Private balance in wei
val walletBalance: String, // Public balance in wei
val symbol: String?, // Token symbol (e.g., "USDC")
val decimals: UByte // Token decimals (e.g., 18)
)
Formatting Amounts
Convert between wei strings and human-readable format:// Parse human-readable to wei
val weiAmount = sdk.parseAmount("1.5", decimals = 18u)
// Returns: "1500000000000000000"
// Format wei to human-readable
val humanAmount = sdk.formatAmount("1500000000000000000", decimals = 18u)
// Returns: "1.5"
// Format a balance for display
val balance = withContext(Dispatchers.IO) {
sdk.getBalance(tokenAddress = tokenAddress)
}
val formatted = sdk.formatAmount(balance.shieldedBalance, decimals = balance.decimals)
println("Shielded: $formatted ${balance.symbol ?: ""}")
Checking Sufficient Balance
Before performing operations, verify the user has enough balance:suspend fun hasSufficientBalance(
tokenAddress: String,
requiredAmount: String
): Boolean {
val balance = withContext(Dispatchers.IO) {
sdk.getBalance(tokenAddress = tokenAddress)
}
val shielded = balance.shieldedBalance.toBigIntegerOrNull() ?: return false
val required = requiredAmount.toBigIntegerOrNull() ?: return false
return shielded >= required
}
// Usage
if (hasSufficientBalance(token, amount)) {
val result = withContext(Dispatchers.IO) {
sdk.unshield(
tokenAddress = token,
amount = amount,
recipient = recipient
)
}
}
ViewModel Integration
class BalanceViewModel(private val sdk: PrivacyBoost) : ViewModel() {
private val _balances = MutableStateFlow<List<TokenBalance>>(emptyList())
val balances: StateFlow<List<TokenBalance>> = _balances.asStateFlow()
private val _isLoading = MutableStateFlow(true)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
fun loadBalances() {
viewModelScope.launch {
_isLoading.value = true
try {
val result = withContext(Dispatchers.IO) {
sdk.getAllBalances()
}
_balances.value = result
} catch (e: SDKError) {
println("Failed to load balances: $e")
}
_isLoading.value = false
}
}
fun formatBalance(balance: TokenBalance): String {
return try {
sdk.formatAmount(balance.shieldedBalance, decimals = balance.decimals)
} catch (e: Exception) {
"0"
}
}
}
Displaying Balances in Compose
@Composable
fun BalanceList(viewModel: BalanceViewModel) {
val balances by viewModel.balances.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
LaunchedEffect(Unit) {
viewModel.loadBalances()
}
if (isLoading) {
CircularProgressIndicator()
} else if (balances.isEmpty()) {
Text("No balances found", color = Color.Gray)
} else {
LazyColumn {
items(balances) { balance ->
BalanceRow(balance = balance, viewModel = viewModel)
}
}
}
}
@Composable
fun BalanceRow(balance: TokenBalance, viewModel: BalanceViewModel) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = balance.symbol ?: balance.tokenAddress.take(10),
style = MaterialTheme.typography.titleMedium
)
Column(horizontalAlignment = Alignment.End) {
Text("Shielded: ${viewModel.formatBalance(balance)}")
Text(
text = "Wallet: ${sdk.formatAmount(balance.walletBalance, balance.decimals)}",
color = Color.Gray
)
}
}
}
Error Handling
try {
val balance = withContext(Dispatchers.IO) {
sdk.getBalance(tokenAddress = tokenAddress)
}
} catch (e: SDKError) {
when (e) {
is SDKError.NotAuthenticated ->
println("Please log in first")
is SDKError.NetworkError ->
println("Network error: ${e.message}")
else ->
println("Balance error: $e")
}
}
Best Practices
1. Refresh After Operations
// Refresh balance after a deposit
val shieldResult = withContext(Dispatchers.IO) {
sdk.shield(tokenAddress = tokenAddress, amount = amount)
}
val updatedBalance = withContext(Dispatchers.IO) {
sdk.getBalance(tokenAddress = tokenAddress)
}
2. Handle Zero Balances
fun displayBalance(balance: TokenBalance): String {
if (balance.shieldedBalance == "0" && balance.walletBalance == "0") {
return "0"
}
return try {
sdk.formatAmount(balance.shieldedBalance, decimals = balance.decimals)
} catch (e: Exception) {
"0"
}
}
3. Filter Non-Zero Balances
val allBalances = withContext(Dispatchers.IO) { sdk.getAllBalances() }
val nonZero = allBalances.filter { balance ->
balance.shieldedBalance != "0" || balance.walletBalance != "0"
}