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 PrivacyBoostError:
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;     // Check if this is an auth-related error
  isWalletError(): boolean;   // Check if this is a wallet-related error
  hasCode(): boolean;         // Check if a specific error code is set
  static from(error: unknown): PrivacyBoostError; // Wrap unknown errors
}

Error Categories

Configuration Errors

CodeDescription
INVALID_CONFIGInvalid SDK configuration
try {
  await PrivacyBoost.create({
    indexerUrl: 'invalid-url', // Will throw
    // ...
  });
} catch (error) {
  if (error.code === 'INVALID_CONFIG') {
    console.log('Please provide valid configuration');
  }
}

Wallet Errors

CodeDescription
TRANSACTION_REJECTEDUser rejected the request
WRONG_NETWORKWrong network
try {
  await sdk.auth.authenticate(adapter);
} catch (error) {
  switch (error.code) {
    case 'TRANSACTION_REJECTED':
      showToast('Authentication cancelled');
      break;
    case 'WRONG_NETWORK':
      showToast('Please switch to the correct network');
      break;
    default:
      showToast('Authentication failed');
  }
}

Authentication Errors

CodeDescription
NOT_AUTHENTICATEDNot authenticated
AUTH_NONCE_EXPIREDAuthentication nonce expired
AUTH_INVALID_SIGNATUREInvalid signature
SESSION_EXPIREDJWT token expired
try {
  await sdk.auth.authenticate(adapter);
} catch (error) {
  if (error.code === 'AUTH_NONCE_EXPIRED') {
    // Retry authentication
    await sdk.auth.authenticate(adapter);
  }
}

Operation Errors

CodeDescription
TRANSFER_PROOF_FAILEDTransfer proof generation failed
try {
  await sdk.vault.shield(params);
} catch (error) {
  switch (error.code) {
    case 'TRANSACTION_REJECTED':
      showError('Transaction cancelled');
      break;
    case 'TRANSFER_PROOF_FAILED':
      showError('Proof generation failed');
      break;
    default:
      showError('Deposit failed: ' + error.message);
  }
}

Network Errors

CodeDescription
TIMEOUTRequest timed out
HTTP_ERRORHTTP request failed
RATE_LIMITEDRate limited
try {
  await sdk.vault.getBalance(tokenAddress);
} catch (error) {
  if (error.code === 'TIMEOUT') {
    // Retry after delay
    await delay(2000);
    await sdk.vault.getBalance(tokenAddress);
  }
}

Proof Errors

CodeDescription
TRANSFER_PROOF_FAILEDFailed to generate transfer proof

Error Handling Patterns

Basic Try-Catch

try {
  await sdk.vault.shield(params);
} catch (error) {
  if (error instanceof PrivacyBoostError) {
    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 PrivacyBoostError)) {
    return 'An unexpected error occurred';
  }

  const messages: Record<string, string> = {
    TRANSACTION_REJECTED: 'Request cancelled',
    SESSION_EXPIRED: 'Session expired, please login again',
    TIMEOUT: 'Request timed out, please try again',
    WRONG_NETWORK: 'Please switch to the correct network',
  };

  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 === 'TRANSACTION_REJECTED') {
        throw error;
      }

      // Retry retryable errors
      if (error.retryable) {
        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 === 'SESSION_EXPIRED') {
      await sdk.auth.authenticate(adapter);
      return await operation();
    }
    throw error;
  }
}

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

React Error Boundary

class PrivacyBoostErrorBoundary 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<PrivacyBoostError | 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 PrivacyBoostError ? err : PrivacyBoostError.from(err);
      setError(sdkError);
      throw sdkError;
    } finally {
      setLoading(false);
    }
  }, [operation]);

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

// Usage
function ShieldButton({ tokenAddress, amount }: { tokenAddress: Hex; amount: bigint }) {
  const { execute, loading, error } = useSDKOperation(() =>
    sdk.vault.shield({ tokenAddress, amount })
  );

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

Logging Errors

function logError(error: unknown, context: string) {
  if (error instanceof PrivacyBoostError) {
    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.shield(params);
} catch (error) {
  logError(error, 'deposit');
  throw error;
}

Best Practices

1. Always Handle User Rejections Gracefully

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

2. Provide Actionable Error Messages

const actionableMessages: Record<string, string> = {
  WRONG_NETWORK: 'Click here to switch networks',
  SESSION_EXPIRED: 'Click to reconnect your wallet',
  TRANSACTION_REJECTED: 'Transaction was cancelled',
};

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('rejected')) {
  // ...
}

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

Next Steps