Skip to main content

Error Handling

This guide covers error types and handling strategies in the Privacy Boost TypeScript SDK.

Error Types

All SDK errors extend from SDKError:
class SDKError extends Error {
  code: string;        // Error code for programmatic handling
  message: string;     // Human-readable message
  cause?: Error;       // Original error if wrapped
}

Error Categories

Configuration Errors

CodeDescription
CONFIG_INVALIDInvalid SDK configuration
CONFIG_MISSING_REQUIREDRequired config field missing
CONFIG_INVALID_URLInvalid URL format
CONFIG_INVALID_ADDRESSInvalid contract address
try {
  await PrivacyBoost.create({
    indexerUrl: 'invalid-url', // Will throw
    // ...
  });
} catch (error) {
  if (error.code === 'CONFIG_INVALID_URL') {
    console.log('Please provide a valid URL');
  }
}

Wallet Errors

CodeDescription
WALLET_NOT_CONNECTEDWallet not connected
WALLET_USER_REJECTEDUser rejected the request
WALLET_CHAIN_MISMATCHWrong network
WALLET_SIGNATURE_FAILEDSignature failed
WALLET_TRANSACTION_FAILEDTransaction failed
try {
  await sdk.auth.connect(adapter);
} catch (error) {
  switch (error.code) {
    case 'WALLET_USER_REJECTED':
      showToast('Connection cancelled');
      break;
    case 'WALLET_CHAIN_MISMATCH':
      showToast('Please switch to the correct network');
      break;
    default:
      showToast('Connection failed');
  }
}

Authentication Errors

CodeDescription
AUTH_NOT_AUTHENTICATEDNot logged in
AUTH_CHALLENGE_EXPIREDLogin challenge expired
AUTH_SIGNATURE_INVALIDInvalid signature
AUTH_SESSION_EXPIREDJWT token expired
AUTH_REGISTRATION_FAILEDIndexer registration failed
try {
  await sdk.auth.login();
} catch (error) {
  if (error.code === 'AUTH_CHALLENGE_EXPIRED') {
    // Retry login
    await sdk.auth.login();
  }
}

Operation Errors

CodeDescription
INSUFFICIENT_BALANCENot enough balance
INVALID_AMOUNTInvalid amount (zero, negative)
INVALID_RECIPIENTInvalid recipient address
INVALID_TOKENToken not supported
DEPOSIT_FAILEDDeposit transaction failed
WITHDRAW_FAILEDWithdrawal failed
TRANSFER_FAILEDTransfer failed
APPROVAL_FAILEDToken approval failed
try {
  await sdk.vault.deposit(params);
} catch (error) {
  switch (error.code) {
    case 'INSUFFICIENT_BALANCE':
      showError('Not enough tokens in your wallet');
      break;
    case 'WALLET_USER_REJECTED':
      showError('Transaction cancelled');
      break;
    case 'APPROVAL_FAILED':
      showError('Token approval failed');
      break;
    default:
      showError('Deposit failed: ' + error.message);
  }
}

Network Errors

CodeDescription
NETWORK_TIMEOUTRequest timed out
NETWORK_CONNECTION_FAILEDConnection failed
NETWORK_SERVER_ERRORServer returned error
NETWORK_RATE_LIMITEDRate limited
try {
  await sdk.vault.getBalance(tokenAddress);
} catch (error) {
  if (error.code === 'NETWORK_TIMEOUT') {
    // Retry after delay
    await delay(2000);
    await sdk.vault.getBalance(tokenAddress);
  }
}

Proof Errors

CodeDescription
PROOF_GENERATION_FAILEDFailed to generate proof
PROOF_VERIFICATION_FAILEDProof verification failed
PROOF_TIMEOUTProof generation timed out

Error Handling Patterns

Basic Try-Catch

try {
  await sdk.vault.deposit(params);
} catch (error) {
  if (error instanceof SDKError) {
    console.log('SDK Error:', error.code, error.message);
  } else {
    console.log('Unknown error:', error);
  }
}

Error Handler Function

function handleError(error: unknown): string {
  if (!(error instanceof SDKError)) {
    return 'An unexpected error occurred';
  }

  const messages: Record<string, string> = {
    INSUFFICIENT_BALANCE: 'Not enough balance',
    WALLET_USER_REJECTED: 'Request cancelled',
    AUTH_SESSION_EXPIRED: 'Session expired, please login again',
    NETWORK_TIMEOUT: 'Request timed out, please try again',
    INVALID_RECIPIENT: 'Invalid recipient address',
  };

  return messages[error.code] || error.message;
}

// Usage
try {
  await sdk.vault.transfer(params);
} catch (error) {
  showToast(handleError(error));
}

Retry Logic

async function withRetry<T>(
  operation: () => Promise<T>,
  maxRetries = 3,
  delay = 1000
): Promise<T> {
  let lastError: Error;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error;

      // Don't retry user rejections
      if (error.code === 'WALLET_USER_REJECTED') {
        throw error;
      }

      // Retry network errors
      if (error.code?.startsWith('NETWORK_')) {
        await new Promise((r) => setTimeout(r, delay * attempt));
        continue;
      }

      throw error;
    }
  }

  throw lastError!;
}

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

Session Refresh

async function withSessionRefresh<T>(
  operation: () => Promise<T>
): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    if (error.code === 'AUTH_SESSION_EXPIRED') {
      await sdk.auth.login();
      return await operation();
    }
    throw error;
  }
}

// Usage
const history = await withSessionRefresh(() =>
  sdk.transactions.fetchHistory()
);

React Error Boundary

class SDKErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { error: Error | null }
> {
  state = { error: null };

  static getDerivedStateFromError(error: Error) {
    return { error };
  }

  render() {
    if (this.state.error) {
      return (
        <div>
          <h2>Something went wrong</h2>
          <p>{this.state.error.message}</p>
          <button onClick={() => this.setState({ error: null })}>
            Try again
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

React Hook for Error Handling

function useSDKOperation<T>(operation: () => Promise<T>) {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<SDKError | null>(null);
  const [data, setData] = useState<T | null>(null);

  const execute = useCallback(async () => {
    setLoading(true);
    setError(null);

    try {
      const result = await operation();
      setData(result);
      return result;
    } catch (err) {
      const sdkError = err instanceof SDKError ? err : new SDKError(err.message);
      setError(sdkError);
      throw sdkError;
    } finally {
      setLoading(false);
    }
  }, [operation]);

  return { execute, loading, error, data };
}

// Usage
function DepositButton() {
  const { execute, loading, error } = useSDKOperation(() =>
    sdk.vault.deposit({ tokenAddress, amount })
  );

  return (
    <>
      <button onClick={execute} disabled={loading}>
        {loading ? 'Processing...' : 'Deposit'}
      </button>
      {error && <p className="error">{handleError(error)}</p>}
    </>
  );
}

Logging Errors

function logError(error: unknown, context: string) {
  if (error instanceof SDKError) {
    console.error(`[${context}] SDK Error:`, {
      code: error.code,
      message: error.message,
      cause: error.cause,
    });
  } else {
    console.error(`[${context}] Unknown error:`, error);
  }
}

// Usage
try {
  await sdk.vault.deposit(params);
} catch (error) {
  logError(error, 'deposit');
  throw error;
}

Best Practices

1. Always Handle User Rejections Gracefully

if (error.code === 'WALLET_USER_REJECTED') {
  // Don't show error, user intentionally cancelled
  return;
}

2. Provide Actionable Error Messages

const actionableMessages: Record<string, string> = {
  INSUFFICIENT_BALANCE: 'Please deposit more tokens to continue',
  WALLET_CHAIN_MISMATCH: 'Click here to switch networks',
  AUTH_SESSION_EXPIRED: 'Click to reconnect your wallet',
};

3. Log Errors for Debugging

catch (error) {
  // Log for developers
  console.error('Operation failed:', error);

  // Show user-friendly message
  showToast(getUserMessage(error));
}

4. Use Error Codes, Not Messages

// BAD - Message might change
if (error.message.includes('insufficient')) {
  // ...
}

// GOOD - Code is stable
if (error.code === 'INSUFFICIENT_BALANCE') {
  // ...
}

Next Steps