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 React Native
SDK. For the cross-platform error code reference, see
Error Handling Concepts.
SdkError
All SDK errors are instances of SdkError, a tagged union where each variant
is a constructable class exported under SdkError.*. Use .instanceOf(err)
to check the variant, or inspect err.tag (values come from the
SdkError_Tags enum).
import {
SdkError,
SdkError_Tags,
} from '@sunnyside-io/privacy-boost-react-native';
Available variants:
| Tag | Data | Meaning |
|---|
NotConnected | — | Wallet not connected |
NotAuthenticated | — | SDK is not logged in |
NetworkError | { message } | Transport-level failure |
WalletError | { message } | Wallet delegate returned an error |
SignatureRejected | — | User cancelled a wallet signature |
InsufficientBalance | — | Not enough funds to cover amount + fees |
InvalidAddress | — | Address failed validation |
InvalidAmount | — | Amount couldn’t be parsed or is out of range |
SerializationError | { message } | Encoding/decoding failure |
AuthServerError | { message } | Auth backend rejected the request |
ShieldError | { message } | Shield operation failed |
TransferError | { message } | Transfer operation failed |
NoteError | { message } | Shielded-note bookkeeping failed |
MerkleError | { message } | Merkle-tree proof construction failed |
ApiError | { code, message, retryable } | Indexer/prover returned an error |
RateLimited | { retryAfterMs } | Server requested backoff |
Forbidden | { message } | Authenticated but not authorized |
ResourceNotFound | — | Requested entity doesn’t exist |
InternalError | { message } | Unclassified failure |
Basic Error Handling
Prefer instanceOf over manual tag checks — it’s safer against variant
renames:
try {
const result = await sdk.shield(tokenAddress, amount);
} catch (error) {
if (SdkError.SignatureRejected.instanceOf(error)) {
return; // User cancelled — do nothing
}
if (SdkError.InsufficientBalance.instanceOf(error)) {
showAlert('Not enough balance');
return;
}
if (SdkError.NetworkError.instanceOf(error)) {
showAlert('Network error. Please try again.');
return;
}
if (SdkError.RateLimited.instanceOf(error)) {
const seconds = Number((error as any).inner.retryAfterMs) / 1000;
showAlert(`Too many requests. Try again in ${seconds} seconds.`);
return;
}
if (SdkError.ApiError.instanceOf(error)) {
const inner = (error as any).inner;
if (inner.retryable) {
// Retry the operation
} else {
showAlert(inner.message);
}
return;
}
showAlert('An unexpected error occurred');
}
Accessing variant data: errors expose their payload under .inner
(e.g. error.inner.message, error.inner.retryAfterMs). The cast to any
is needed because the generated union widens the type.
Switch on error.tag
If you prefer a single switch block over cascading ifs:
try {
await sdk.shield(tokenAddress, amount);
} catch (error: any) {
switch (error.tag) {
case SdkError_Tags.SignatureRejected:
return;
case SdkError_Tags.InsufficientBalance:
showAlert('Not enough balance');
break;
case SdkError_Tags.NetworkError:
showAlert(`Network error: ${error.inner.message}`);
break;
case SdkError_Tags.RateLimited: {
const seconds = Number(error.inner.retryAfterMs) / 1000;
showAlert(`Wait ${seconds}s`);
break;
}
case SdkError_Tags.ApiError:
showAlert(error.inner.message);
break;
default:
showAlert('An unexpected error occurred');
}
}
Retry with Exponential Backoff
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error: any) {
const isLastAttempt = attempt === maxRetries;
if (SdkError.NetworkError.instanceOf(error) && !isLastAttempt) {
await sleep(1000 * attempt);
continue;
}
if (
SdkError.ApiError.instanceOf(error) &&
error.inner.retryable &&
!isLastAttempt
) {
await sleep(1000 * attempt);
continue;
}
if (SdkError.RateLimited.instanceOf(error) && !isLastAttempt) {
await sleep(Number(error.inner.retryAfterMs));
continue;
}
throw error;
}
}
throw new Error('Unreachable');
}
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
// Usage
const balance = await withRetry(() => sdk.getBalance(tokenAddress));
User-Friendly Error Messages
function userMessage(error: any): string {
if (SdkError.NotConnected.instanceOf(error)) return 'Please connect your wallet';
if (SdkError.NotAuthenticated.instanceOf(error)) return 'Session expired. Please log in again.';
if (SdkError.SignatureRejected.instanceOf(error)) return 'Request cancelled';
if (SdkError.InsufficientBalance.instanceOf(error)) return 'Not enough balance';
if (SdkError.InvalidAddress.instanceOf(error)) return 'Invalid address';
if (SdkError.InvalidAmount.instanceOf(error)) return 'Invalid amount';
if (SdkError.NetworkError.instanceOf(error)) return 'Connection failed. Please try again.';
if (SdkError.RateLimited.instanceOf(error)) return 'Too many requests. Please wait.';
if (SdkError.Forbidden.instanceOf(error)) return 'You are not allowed to perform this action.';
if (SdkError.ResourceNotFound.instanceOf(error)) return 'Not found.';
if (SdkError.WalletError.instanceOf(error)) return `Wallet error: ${error.inner.message}`;
return 'Something went wrong. Please try again.';
}
Categorizing Errors
type ErrorCategory = 'userAction' | 'retryable' | 'fatal';
function categorize(error: any): ErrorCategory {
if (
SdkError.NotConnected.instanceOf(error) ||
SdkError.NotAuthenticated.instanceOf(error) ||
SdkError.SignatureRejected.instanceOf(error) ||
SdkError.InsufficientBalance.instanceOf(error) ||
SdkError.InvalidAddress.instanceOf(error) ||
SdkError.InvalidAmount.instanceOf(error) ||
SdkError.Forbidden.instanceOf(error)
) {
return 'userAction';
}
if (
SdkError.NetworkError.instanceOf(error) ||
SdkError.RateLimited.instanceOf(error)
) {
return 'retryable';
}
if (SdkError.ApiError.instanceOf(error)) {
return error.inner.retryable ? 'retryable' : 'fatal';
}
return 'fatal';
}
Best Practices
1. Always handle SignatureRejected silently
When a user cancels a wallet signature, don’t show an error:
try {
await sdk.shield(tokenAddress, amount);
} catch (error) {
if (SdkError.SignatureRejected.instanceOf(error)) return;
showAlert(userMessage(error));
}
2. Log the full error for debugging
try {
await sdk.shield(tokenAddress, amount);
} catch (error) {
console.error('[PrivacyBoost] Deposit failed:', error);
showAlert(userMessage(error));
}
3. Use categories for UI decisions
try {
await someOperation();
} catch (error) {
switch (categorize(error)) {
case 'userAction':
showAlert(userMessage(error));
break;
case 'retryable':
showRetryButton();
break;
case 'fatal':
showContactSupport();
break;
}
}
Next Steps