Skip to main content

Error Handling & Performance

This guide covers TypeScript-specific error handling patterns and performance optimization. For the cross-platform error code reference, see Error Handling.

PrivacyBoostError

All SDK errors extend PrivacyBoostError:
import { PrivacyBoostError } from '@testinprod-io/privacy-boost';

class PrivacyBoostError extends Error {
  code: string;          // Error code for programmatic handling
  message: string;       // Human-readable message
  retryable: boolean;    // Whether the operation can be retried
  cause?: Error;         // Original error if wrapped

  isAuthError(): boolean;     // Is this an auth/session error?
  isWalletError(): boolean;   // Is this a wallet-related error?
}

Type-Safe Error Handling

import { PrivacyBoostError, ErrorCodes } from '@testinprod-io/privacy-boost';

try {
  await sdk.vault.shield({ tokenAddress, amount });
} catch (error) {
  if (error instanceof PrivacyBoostError) {
    switch (error.code) {
      case ErrorCodes.TRANSACTION_REJECTED:
        return; // User cancelled — do nothing
      case ErrorCodes.INSUFFICIENT_BALANCE:
        showToast('Not enough balance');
        return;
      default:
        if (error.retryable) {
          await retryLater();
        } else {
          showError(error.message);
        }
    }
  }
  // Non-SDK errors
  throw error;
}

Retry with Exponential Backoff

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (!(error instanceof PrivacyBoostError) || !error.retryable || attempt === maxRetries) {
        throw error;
      }
      await new Promise(r => setTimeout(r, 1000 * attempt));
    }
  }
  throw new Error('Unreachable');
}

const balance = await withRetry(() => sdk.vault.getBalance(token));

Auto Re-Authentication

async function withAuth<T>(fn: () => Promise<T>): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (error instanceof PrivacyBoostError && error.code === 'SESSION_EXPIRED') {
      await sdk.auth.authenticate(adapter);
      return await fn();
    }
    throw error;
  }
}

User-Friendly Error Messages

function userMessage(error: PrivacyBoostError): string {
  const messages: Record<string, string> = {
    WALLET_NOT_CONNECTED: 'Please connect your wallet',
    TRANSACTION_REJECTED: 'Request cancelled',
    WRONG_NETWORK: 'Please switch to the correct network',
    SESSION_EXPIRED: 'Session expired, please log in again',
    INSUFFICIENT_BALANCE: 'Not enough balance',
    NETWORK_ERROR: 'Connection failed, please try again',
    RATE_LIMITED: 'Too many requests, please wait',
  };
  return messages[error.code] || error.message;
}

WASM Loading

The SDK includes a WebAssembly module for ZK proof generation. Managing how and when it loads affects initial page load time.

Lazy Loading

Don’t import the SDK at the top level in routes that don’t need it:
async function initPrivacy() {
  const { PrivacyBoost } = await import('@testinprod-io/privacy-boost');
  return await PrivacyBoost.create(config);
}

Preloading

For routes that will need the SDK, preload in the background:
function preloadSDK() {
  import('@testinprod-io/privacy-boost').then(({ loadWasm }) => {
    loadWasm().catch(() => {
      // Ignore preload errors
    });
  });
}

// Call early in your app
useEffect(() => {
  preloadSDK();
}, []);

Code Splitting

// React Router example
const PrivacyWallet = React.lazy(() => import('./PrivacyWallet'));

function App() {
  return (
    <Routes>
      <Route path="/wallet" element={
        <Suspense fallback={<Loading />}>
          <PrivacyWallet />
        </Suspense>
      } />
    </Routes>
  );
}

Tree Shaking

Import only what you need:
// Imports entire package
import * as SDK from '@testinprod-io/privacy-boost';

// Tree-shakeable imports
import { PrivacyBoost, PrivacyBoostError } from '@testinprod-io/privacy-boost';

Caching & Network

Balance Caching

The SDK caches balances locally. Control refresh behavior:
// Get cached balance (fast, no network call)
const cached = await sdk.vault.getBalance(tokenAddress);

// Force refresh from indexer
await sdk.vault.syncBalance(tokenAddress);
const fresh = await sdk.vault.getBalance(tokenAddress);

// Sync all balances at once (more efficient than individual syncs)
await sdk.vault.syncAllBalances();

Request Deduplication

Avoid redundant concurrent requests:
const pendingRequests = new Map<string, Promise<unknown>>();

async function deduplicated<T>(key: string, request: () => Promise<T>): Promise<T> {
  if (pendingRequests.has(key)) {
    return pendingRequests.get(key) as Promise<T>;
  }
  const promise = request().finally(() => pendingRequests.delete(key));
  pendingRequests.set(key, promise);
  return promise;
}

const balance = await deduplicated(
  `balance:${tokenAddress}`,
  () => sdk.vault.getBalance(tokenAddress)
);

Parallel Requests

// Sequential — slow
const b1 = await sdk.vault.getBalance(token1);
const b2 = await sdk.vault.getBalance(token2);

// Parallel — fast
const [b1, b2] = await Promise.all([
  sdk.vault.getBalance(token1),
  sdk.vault.getBalance(token2),
]);

Next Steps