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 iOS SDK. For the cross-platform error code reference, see Error Handling.

SDKError

All SDK errors are thrown as SDKError enum cases:
enum SDKError: Error {
    case notConnected                              // Wallet not connected
    case notAuthenticated                          // Not logged in
    case invalidConfig                             // Invalid SDK configuration
    case networkError(String)                      // Network/connectivity issues
    case walletError(String)                       // Wallet operation failed
    case signatureRejected                         // User rejected signing
    case insufficientBalance                       // Not enough balance
    case invalidAddress                            // Invalid address format
    case invalidAmount                             // Invalid amount format
    case serializationError(String)                // Data encoding/decoding failed
    case authServerError(code: String, message: String)
    case shieldError(code: String, message: String)
    case transferError(code: String, message: String)
    case noteError(code: String, message: String)
    case merkleError(code: String, message: String)
    case apiError(code: String, message: String, retryable: Bool)
    case rateLimited(retryAfterMs: UInt64)         // Too many requests
    case forbidden(String)                         // Access denied
    case resourceNotFound(String)                  // Resource not found
    case internalError(String)                     // Internal SDK error
}

Type-Safe Error Handling

Use Swift’s pattern matching to handle specific error cases:
do {
    let result = try await sdk.shield(
        tokenAddress: tokenAddress,
        amount: amount
    )
} catch SDKError.signatureRejected {
    // User cancelled — do nothing
    return
} catch SDKError.insufficientBalance {
    showAlert("Not enough balance")
} catch SDKError.walletError(let message) {
    showAlert("Wallet error: \(message)")
} catch SDKError.networkError(let message) {
    showAlert("Network error. Please try again.")
} catch SDKError.rateLimited(let retryAfterMs) {
    let seconds = retryAfterMs / 1000
    showAlert("Too many requests. Try again in \(seconds) seconds.")
} catch SDKError.apiError(_, let message, let retryable) {
    if retryable {
        // Retry the operation
    } else {
        showAlert(message)
    }
} catch {
    showAlert("An unexpected error occurred")
}

Retry with Exponential Backoff

For retryable errors, use exponential backoff:
func withRetry<T>(
    maxRetries: Int = 3,
    operation: () async throws -> T
) async throws -> T {
    for attempt in 1...maxRetries {
        do {
            return try await operation()
        } catch SDKError.networkError(_) where attempt < maxRetries {
            let delay = UInt64(1_000_000_000 * attempt) // 1s, 2s, 3s
            try await Task.sleep(nanoseconds: delay)
            continue
        } catch SDKError.apiError(_, _, let retryable) where retryable && attempt < maxRetries {
            let delay = UInt64(1_000_000_000 * attempt)
            try await Task.sleep(nanoseconds: delay)
            continue
        } catch SDKError.rateLimited(let retryAfterMs) where attempt < maxRetries {
            try await Task.sleep(nanoseconds: retryAfterMs * 1_000_000)
            continue
        }
    }
    // Last attempt — let errors propagate
    return try await operation()
}

// Usage
let balance = try await withRetry {
    try await sdk.getBalance(tokenAddress: token)
}

Auto Re-Authentication

Handle expired sessions by re-authenticating automatically:
func withAuth<T>(
    wallet: WalletDelegate,
    operation: () async throws -> T
) async throws -> T {
    do {
        return try await operation()
    } catch SDKError.notAuthenticated {
        // Session expired — re-authenticate
        _ = try await sdk.authenticate(wallet: wallet)
        return try await operation()
    }
}

// Usage
let balance = try await withAuth(wallet: myWallet) {
    try await sdk.getBalance(tokenAddress: token)
}

User-Friendly Error Messages

Map SDK errors to messages suitable for showing to users:
func userMessage(for error: Error) -> String {
    guard let sdkError = error as? SDKError else {
        return "An unexpected error occurred"
    }

    switch sdkError {
    case .notConnected:
        return "Please connect your wallet"
    case .notAuthenticated:
        return "Session expired. Please log in again."
    case .signatureRejected:
        return "Request cancelled"
    case .insufficientBalance:
        return "Not enough balance"
    case .invalidAddress:
        return "Invalid address"
    case .invalidAmount:
        return "Invalid amount"
    case .networkError:
        return "Connection failed. Please check your internet and try again."
    case .rateLimited:
        return "Too many requests. Please wait a moment."
    case .walletError(let message):
        return "Wallet error: \(message)"
    default:
        return "Something went wrong. Please try again."
    }
}

Categorizing Errors

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

func categorize(_ error: SDKError) -> ErrorCategory {
    switch error {
    case .notConnected, .notAuthenticated, .signatureRejected,
         .insufficientBalance, .invalidAddress, .invalidAmount:
        return .userAction
    case .networkError, .rateLimited:
        return .retryable
    case .apiError(_, _, let retryable):
        return retryable ? .retryable : .fatal
    default:
        return .fatal
    }
}

SwiftUI Error Handling Pattern

class VaultViewModel: ObservableObject {
    @Published var errorMessage: String?
    @Published var isLoading = false

    func deposit(tokenAddress: String, amount: String) async {
        isLoading = true
        errorMessage = nil

        do {
            let result = try await withRetry {
                try await sdk.shield(
                    tokenAddress: tokenAddress,
                    amount: amount
                )
            }
            print("Deposit successful: \(result.txHash)")
        } catch {
            errorMessage = userMessage(for: error)
        }

        isLoading = false
    }
}

Best Practices

1. Always Handle signatureRejected Silently

When a user cancels a wallet signature, don’t show an error — just return to the previous state:
do {
    let result = try await sdk.shield(tokenAddress: token, amount: amount)
} catch SDKError.signatureRejected {
    return // User cancelled, nothing to report
} catch {
    showAlert(userMessage(for: error))
}

2. Log Errors for Debugging

do {
    let result = try await sdk.shield(tokenAddress: token, amount: amount)
} catch {
    // Log full error for debugging
    print("[PrivacyBoost] Deposit failed: \(error)")

    // Show user-friendly message
    showAlert(userMessage(for: error))
}

3. Use Error Categories for UI Decisions

do {
    try await someOperation()
} catch let error as SDKError {
    switch categorize(error) {
    case .userAction:
        showAlert(userMessage(for: error))
    case .retryable:
        showRetryButton()
    case .fatal:
        showContactSupport()
    }
} catch {
    showContactSupport()
}

Next Steps