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 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:
TagDataMeaning
NotConnectedWallet not connected
NotAuthenticatedSDK is not logged in
NetworkError{ message }Transport-level failure
WalletError{ message }Wallet delegate returned an error
SignatureRejectedUser cancelled a wallet signature
InsufficientBalanceNot enough funds to cover amount + fees
InvalidAddressAddress failed validation
InvalidAmountAmount 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
ResourceNotFoundRequested 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