Skip to main content

Error Handling

The Privacy Boost SDK uses structured error codes so you can handle errors programmatically. Every error has a stable code string that won’t change between SDK versions.

Quick Example

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:
        // User cancelled — do nothing
        return;
      case ErrorCodes.INSUFFICIENT_BALANCE:
        showToast('Not enough balance');
        return;
      default:
        if (error.retryable) {
          // Network issue — retry after a moment
          await retryLater();
        } else {
          showError(error.message);
        }
    }
  }
}

Error Types by Platform

PlatformError TypeHow to check the code
TypeScriptPrivacyBoostErrorerror.code, error.retryable, error.isAuthError()
iOS (Swift)SDKErrorcatch SDKError.networkError(let msg) { ... }
Android (Kotlin)SDKErrorcatch (e: SDKError.NetworkError) { ... }

TypeScript: PrivacyBoostError

class PrivacyBoostError extends Error {
  code: string;          // e.g. 'WALLET_NOT_CONNECTED'
  message: string;       // Human-readable description
  cause?: Error;         // Original error if wrapped
  retryable: boolean;    // Can this operation be retried?

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

Error Codes

Wallet Errors

These occur when interacting with the user’s wallet.
CodeWhat happenedUser action
WALLET_NOT_CONNECTEDNo wallet connectedShow “Connect Wallet” button
WALLET_CONNECTION_FAILEDWallet refused or failed to connectRetry or try a different wallet
WRONG_NETWORKWallet is on the wrong chainShow “Switch Network” prompt
TRANSACTION_REJECTEDUser rejected a signature or transactionDo nothing (intentional)
TRANSACTION_FAILEDOn-chain transaction failedShow error, check gas/params
INSUFFICIENT_GASNot enough gas for the transactionPrompt user to add funds

Authentication Errors

These occur during login or when a session expires.
CodeWhat happenedUser action
NOT_AUTHENTICATEDTried to use SDK before logging inCall authenticate() first
SESSION_EXPIREDJWT expiredRe-authenticate (can be automatic)
AUTHENTICATION_FAILEDLogin failedRetry or check configuration

Network Errors

These are transient and usually retryable.
CodeWhat happenedRetryable?
NETWORK_ERRORConnection failed (DNS, timeout, refused)Yes
TIMEOUTRequest took too longYes
RATE_LIMITEDToo many requests (429)Yes (with backoff)
SERVICE_UNAVAILABLEBackend is temporarily downYes (with backoff)

Operation Errors

These occur during deposits, transfers, or withdrawals.
CodeWhat happenedUser action
INSUFFICIENT_BALANCENot enough shielded balanceShow balance and suggest depositing more
INVALID_ADDRESSMalformed Ethereum or privacy addressPrompt user to check the address
INVALID_AMOUNTAmount is zero, negative, or malformedValidate input before calling the SDK
INVALID_CONFIGSDK configuration is invalidCheck appId, indexerUrl, and other config values

Compliance Errors

These occur when a transaction fails compliance screening.
CodeWhat happenedUser action
COMPLIANCE_BLOCKEDAddress or transaction blocked by compliance screeningContact support
TRANSFER_COMPLIANCE_FAILEDTransfer failed compliance checkContact support

Crypto Errors

These indicate failures in the cryptographic layer.
CodeWhat happenedUser action
CRYPTO_ERRORGeneral cryptographic operation failedReport the issue
ENCRYPTION_ERRORFailed to encrypt dataRetry or re-authenticate
DECRYPTION_ERRORFailed to decrypt data (wrong keys or corrupted)Re-authenticate or restore keys
KEY_DERIVATION_ERRORFailed to derive privacy keysRe-authenticate

Shield Errors

These occur during shield (deposit) operations.
CodeWhat happenedUser action
SHIELD_NOT_FOUNDShield request not found on-chainCheck transaction hash
SHIELD_ALREADY_CLAIMEDShield has already been claimedNo action needed
SHIELD_PROOF_EXPIREDShield proof has expiredRe-initiate the shield
SHIELD_COMMITMENT_NOT_FOUNDOn-chain commitment not foundWait for indexer to sync

Transfer Errors

These occur during private transfers.
CodeWhat happenedUser action
TRANSFER_NOT_FOUNDTransfer not foundCheck request ID
TRANSFER_PROOF_FAILEDZK proof generation or verification failedRetry the transfer
TRANSFER_SUBMISSION_FAILEDTransfer could not be submitted to the serverRetry
BELOW_MIN_TRANSFER_AMOUNTAmount is below minimum transfer thresholdIncrease amount

Merkle Tree Errors

These occur during proof construction.
CodeWhat happenedRetryable?
MERKLE_LEAF_NOT_FOUNDNote’s leaf not found in the Merkle treeYes (indexer may be syncing)
MERKLE_PROOF_FAILEDFailed to construct Merkle proofYes
MERKLE_ROOT_STALEMerkle root is outdatedYes (auto-retried by SDK)

Note Errors

These occur when accessing private notes.
CodeWhat happenedUser action
NOTE_NOT_FOUNDNote does not existVerify the transaction
NOTE_ALREADY_SPENTNote has already been spent (nullifier exists)No action — note was used
NOTE_ACCESS_DENIEDCannot decrypt this note (wrong keys)Verify correct account

Internal Errors

These indicate SDK bugs or unexpected server responses.
CodeWhat happenedUser action
SERIALIZATION_ERRORFailed to serialize/deserialize dataReport the issue
OPERATION_FAILEDGeneric operation failureCheck the error message for details
INTERNAL_ERRORUnexpected internal errorReport the issue with full error details

Recovery Patterns

Handle user rejections gracefully

When a user cancels a wallet popup, don’t show an error dialog — it’s intentional.
try {
  await sdk.auth.authenticate(adapter, { type: 'walletDerived' });
} catch (error) {
  if (error.code === 'TRANSACTION_REJECTED') {
    return; // User cancelled
  }
  showError(error.message);
}

Retry on network errors

Use error.retryable 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.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));

Re-authenticate on session expiry

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

Map errors to user-friendly 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;
}

Best Practices

  1. Match on error.code, not error.message. Codes are stable across versions; messages may change.
  2. Never show raw error messages to users. Map codes to context-appropriate messages.
  3. Handle TRANSACTION_REJECTED silently. The user intentionally cancelled.
  4. Use error.retryable for retry logic. The SDK marks transient errors as retryable.
  5. Log the full error internally. Capture code, message, and cause for debugging, even when showing a simple message to the user.

Next Steps

You’ve completed the Setup section. Now dive into your platform SDK:

TypeScript

Full web SDK guide

React

Hooks and components

iOS

Native Swift SDK

Android

Native Kotlin SDK

React Native

React Native SDK

CLI

Command-line tool
For deeper TypeScript error handling patterns (retry logic, WASM loading, caching), see the TypeScript Error Handling guide.