Skip to main content

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 TypeDescription
ShieldedPrivate balance in the privacy pool — only you can see it
WalletPublic 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"
}

Next Steps