Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt

Use this file to discover all available pages before exploring further.

Error Handling

This guide covers error handling patterns for the Privacy Boost Android SDK. For the cross-platform error code reference, see Error Handling.

SDKError

All SDK errors are thrown as SDKError sealed class instances:
sealed class SDKError : Exception() {
    object NotConnected : SDKError()                    // Wallet not connected
    object NotAuthenticated : SDKError()                // Not logged in
    object InvalidConfig : SDKError()                   // Invalid SDK configuration
    data class NetworkError(override val message: String) : SDKError()
    data class WalletError(override val message: String) : SDKError()
    object SignatureRejected : SDKError()                // User rejected signing
    object InsufficientBalance : SDKError()              // Not enough balance
    object InvalidAddress : SDKError()                   // Invalid address format
    object InvalidAmount : SDKError()                    // Invalid amount format
    data class ApiError(val code: String, override val message: String, val retryable: Boolean) : SDKError()
    data class RateLimited(val retryAfterMs: ULong) : SDKError()
    data class DepositError(val code: String, override val message: String) : SDKError()
    data class TransferError(val code: String, override val message: String) : SDKError()
    data class InternalError(override val message: String) : SDKError()
    // ... and more
}

Type-Safe Error Handling

Use Kotlin’s when expression for exhaustive error matching:
try {
    val result = withContext(Dispatchers.IO) {
        sdk.shield(tokenAddress = tokenAddress, amount = amount)
    }
} catch (e: SDKError) {
    when (e) {
        is SDKError.SignatureRejected -> {
            // User cancelled — do nothing
            return
        }
        is SDKError.InsufficientBalance -> {
            showToast("Not enough balance")
        }
        is SDKError.WalletError -> {
            showToast("Wallet error: ${e.message}")
        }
        is SDKError.NetworkError -> {
            showToast("Network error. Please try again.")
        }
        is SDKError.RateLimited -> {
            val seconds = e.retryAfterMs / 1000u
            showToast("Too many requests. Try again in $seconds seconds.")
        }
        is SDKError.ApiError -> {
            if (e.retryable) {
                // Retry the operation
            } else {
                showToast(e.message)
            }
        }
        else -> {
            showToast("An unexpected error occurred")
        }
    }
}

Retry with Exponential Backoff

For retryable errors, use exponential backoff:
suspend fun <T> withRetry(
    maxRetries: Int = 3,
    operation: suspend () -> T
): T {
    for (attempt in 1..maxRetries) {
        try {
            return operation()
        } catch (e: SDKError.NetworkError) {
            if (attempt == maxRetries) throw e
            delay(1000L * attempt)
        } catch (e: SDKError.ApiError) {
            if (!e.retryable || attempt == maxRetries) throw e
            delay(1000L * attempt)
        } catch (e: SDKError.RateLimited) {
            if (attempt == maxRetries) throw e
            delay(e.retryAfterMs.toLong())
        }
    }
    throw SDKError.InternalError("Unreachable")
}

// Usage
val balance = withRetry {
    withContext(Dispatchers.IO) {
        sdk.getBalance(tokenAddress = token)
    }
}

Auto Re-Authentication

Handle expired sessions by re-authenticating automatically:
suspend fun <T> withAuth(
    wallet: WalletDelegate,
    operation: suspend () -> T
): T {
    return try {
        operation()
    } catch (e: SDKError.NotAuthenticated) {
        // Session expired — re-authenticate
        withContext(Dispatchers.IO) {
            sdk.authenticate(wallet = wallet)
        }
        operation()
    }
}

// Usage
val balance = withAuth(myWallet) {
    withContext(Dispatchers.IO) {
        sdk.getBalance(tokenAddress = token)
    }
}

User-Friendly Error Messages

Map SDK errors to messages suitable for showing to users:
fun userMessage(error: SDKError): String = when (error) {
    is SDKError.NotConnected -> "Please connect your wallet"
    is SDKError.NotAuthenticated -> "Session expired. Please log in again."
    is SDKError.SignatureRejected -> "Request cancelled"
    is SDKError.InsufficientBalance -> "Not enough balance"
    is SDKError.InvalidAddress -> "Invalid address"
    is SDKError.InvalidAmount -> "Invalid amount"
    is SDKError.NetworkError -> "Connection failed. Please check your internet and try again."
    is SDKError.RateLimited -> "Too many requests. Please wait a moment."
    is SDKError.WalletError -> "Wallet error: ${error.message}"
    else -> "Something went wrong. Please try again."
}

Categorizing Errors

Group errors by whether user action can resolve them:
enum class ErrorCategory {
    USER_ACTION,  // User can fix (e.g., connect wallet, add funds)
    RETRYABLE,    // Transient, retry may work
    FATAL         // Requires developer attention
}

fun categorize(error: SDKError): ErrorCategory = when (error) {
    is SDKError.NotConnected,
    is SDKError.NotAuthenticated,
    is SDKError.SignatureRejected,
    is SDKError.InsufficientBalance,
    is SDKError.InvalidAddress,
    is SDKError.InvalidAmount -> ErrorCategory.USER_ACTION

    is SDKError.NetworkError,
    is SDKError.RateLimited -> ErrorCategory.RETRYABLE

    is SDKError.ApiError -> if (error.retryable) ErrorCategory.RETRYABLE else ErrorCategory.FATAL

    else -> ErrorCategory.FATAL
}

ViewModel Error Handling Pattern

class VaultViewModel(
    private val sdk: PrivacyBoost,
    private val wallet: WalletDelegate
) : ViewModel() {

    private val _error = MutableStateFlow<String?>(null)
    val error: StateFlow<String?> = _error.asStateFlow()

    private val _isLoading = MutableStateFlow(false)
    val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()

    fun deposit(tokenAddress: String, amount: String) {
        viewModelScope.launch {
            _isLoading.value = true
            _error.value = null

            try {
                val result = withRetry {
                    withContext(Dispatchers.IO) {
                        sdk.shield(tokenAddress = tokenAddress, amount = amount)
                    }
                }
                println("Deposit successful: ${result.txHash}")
            } catch (e: SDKError.SignatureRejected) {
                // User cancelled — no error to show
            } catch (e: SDKError) {
                _error.value = userMessage(e)
            }

            _isLoading.value = false
        }
    }
}

Best Practices

1. Always Handle SignatureRejected Silently

When a user cancels a wallet signature, don’t show an error:
try {
    val result = withContext(Dispatchers.IO) {
        sdk.shield(tokenAddress = token, amount = amount)
    }
} catch (e: SDKError.SignatureRejected) {
    return // User cancelled, nothing to report
} catch (e: SDKError) {
    showToast(userMessage(e))
}

2. Log Errors for Debugging

try {
    val result = withContext(Dispatchers.IO) {
        sdk.shield(tokenAddress = token, amount = amount)
    }
} catch (e: SDKError) {
    // Log full error for debugging
    Log.e("PrivacyBoost", "Deposit failed", e)

    // Show user-friendly message
    showToast(userMessage(e))
}

3. Use Error Categories for UI Decisions

try {
    // some operation
} catch (e: SDKError) {
    when (categorize(e)) {
        ErrorCategory.USER_ACTION -> showToast(userMessage(e))
        ErrorCategory.RETRYABLE -> showRetryButton()
        ErrorCategory.FATAL -> showContactSupport()
    }
}

Next Steps