Skip to main content

Balance Management

This guide covers querying and managing token balances in the Privacy Boost iOS 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

do {
    let balance = try await sdk.getBalance(tokenAddress: "0x...")

    print("Token: \(balance.tokenAddress)")
    print("Shielded: \(balance.shieldedBalance)")
    print("Wallet: \(balance.walletBalance)")
    print("Symbol: \(balance.symbol ?? "Unknown")")
    print("Decimals: \(balance.decimals)")
} catch {
    print("Failed to get balance: \(error)")
}

Get All Balances

do {
    let balances = try await sdk.getAllBalances()

    for balance in balances {
        print("\(balance.symbol ?? balance.tokenAddress):")
        print("  Shielded: \(balance.shieldedBalance)")
        print("  Wallet: \(balance.walletBalance)")
    }
} catch {
    print("Failed to get balances: \(error)")
}

Balance Type

struct TokenBalance {
    let tokenAddress: String   // Token contract address
    let shieldedBalance: String // Private balance in wei
    let walletBalance: String   // Public balance in wei
    let symbol: String?         // Token symbol (e.g., "USDC")
    let decimals: UInt8         // Token decimals (e.g., 18)
}

Formatting Amounts

Convert between wei strings and human-readable format:
// Parse human-readable to wei
let weiAmount = try sdk.parseAmount("1.5", decimals: 18)
// Returns: "1500000000000000000"

// Format wei to human-readable
let humanAmount = try sdk.formatAmount("1500000000000000000", decimals: 18)
// Returns: "1.5"

// Format a balance for display
let balance = try await sdk.getBalance(tokenAddress: tokenAddress)
let formatted = try sdk.formatAmount(balance.shieldedBalance, decimals: balance.decimals)
print("Shielded: \(formatted) \(balance.symbol ?? "")")

Checking Sufficient Balance

Before performing operations, verify the user has enough balance:
func hasSufficientBalance(
    tokenAddress: String,
    requiredAmount: String
) async throws -> Bool {
    let balance = try await sdk.getBalance(tokenAddress: tokenAddress)

    guard let shielded = UInt64(balance.shieldedBalance),
          let required = UInt64(requiredAmount) else {
        return false
    }

    return shielded >= required
}

// Usage
if try await hasSufficientBalance(tokenAddress: token, requiredAmount: amount) {
    let result = try await sdk.unshield(
        tokenAddress: token,
        amount: amount,
        recipient: recipient
    )
}

Displaying Balances in SwiftUI

struct BalanceListView: View {
    @State private var balances: [TokenBalance] = []
    @State private var isLoading = true

    var body: some View {
        List {
            if isLoading {
                ProgressView("Loading balances...")
            } else if balances.isEmpty {
                Text("No balances found")
                    .foregroundColor(.secondary)
            } else {
                ForEach(balances, id: \.tokenAddress) { balance in
                    BalanceRow(balance: balance)
                }
            }
        }
        .task {
            await loadBalances()
        }
        .refreshable {
            await loadBalances()
        }
    }

    private func loadBalances() async {
        isLoading = true
        do {
            balances = try await sdk.getAllBalances()
        } catch {
            print("Failed to load balances: \(error)")
        }
        isLoading = false
    }
}

struct BalanceRow: View {
    let balance: TokenBalance

    var formattedShielded: String {
        (try? sdk.formatAmount(balance.shieldedBalance, decimals: balance.decimals)) ?? "0"
    }

    var formattedWallet: String {
        (try? sdk.formatAmount(balance.walletBalance, decimals: balance.decimals)) ?? "0"
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(balance.symbol ?? balance.tokenAddress)
                .font(.headline)
            HStack {
                Label("Shielded: \(formattedShielded)", systemImage: "lock.shield")
                Spacer()
                Label("Wallet: \(formattedWallet)", systemImage: "wallet.pass")
            }
            .font(.subheadline)
            .foregroundColor(.secondary)
        }
        .padding(.vertical, 4)
    }
}

Error Handling

do {
    let balance = try await sdk.getBalance(tokenAddress: tokenAddress)
} catch SDKError.notAuthenticated {
    print("Please log in first")
} catch SDKError.networkError(let message) {
    print("Network error: \(message)")
} catch {
    print("Balance error: \(error)")
}

Best Practices

1. Refresh After Operations

// Refresh balance after a deposit
let shieldResult = try await sdk.shield(
    tokenAddress: tokenAddress,
    amount: amount
)

let updatedBalance = try await sdk.getBalance(tokenAddress: tokenAddress)

2. Handle Zero Balances

func displayBalance(_ balance: TokenBalance) -> String {
    if balance.shieldedBalance == "0" && balance.walletBalance == "0" {
        return "0"
    }
    return (try? sdk.formatAmount(
        balance.shieldedBalance,
        decimals: balance.decimals
    )) ?? "0"
}

3. Filter Non-Zero Balances

let allBalances = try await sdk.getAllBalances()
let nonZero = allBalances.filter { balance in
    balance.shieldedBalance != "0" || balance.walletBalance != "0"
}

Next Steps