> ## 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

# 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](/sdk/concepts/error-handling).

## 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).

```typescript theme={null}
import {
  SdkError,
  SdkError_Tags,
} from '@sunnyside-io/privacy-boost-react-native';
```

Available variants:

| Tag                   | Data                           | Meaning                                      |
| --------------------- | ------------------------------ | -------------------------------------------- |
| `NotConnected`        | —                              | Wallet not connected                         |
| `NotAuthenticated`    | —                              | SDK is not logged in                         |
| `NetworkError`        | `{ message }`                  | Transport-level failure                      |
| `WalletError`         | `{ message }`                  | Wallet delegate returned an error            |
| `SignatureRejected`   | —                              | User cancelled a wallet signature            |
| `InsufficientBalance` | —                              | Not enough funds to cover amount + fees      |
| `InvalidAddress`      | —                              | Address failed validation                    |
| `InvalidAmount`       | —                              | Amount 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             |
| `ResourceNotFound`    | —                              | Requested entity doesn't exist               |
| `InternalError`       | `{ message }`                  | Unclassified failure                         |

## Basic Error Handling

Prefer `instanceOf` over manual tag checks — it's safer against variant
renames:

```typescript theme={null}
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 `if`s:

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
try {
  await sdk.shield(tokenAddress, amount);
} catch (error) {
  if (SdkError.SignatureRejected.instanceOf(error)) return;
  showAlert(userMessage(error));
}
```

### 2. Log the full error for debugging

```typescript theme={null}
try {
  await sdk.shield(tokenAddress, amount);
} catch (error) {
  console.error('[PrivacyBoost] Deposit failed:', error);
  showAlert(userMessage(error));
}
```

### 3. Use categories for UI decisions

```typescript theme={null}
try {
  await someOperation();
} catch (error) {
  switch (categorize(error)) {
    case 'userAction':
      showAlert(userMessage(error));
      break;
    case 'retryable':
      showRetryButton();
      break;
    case 'fatal':
      showContactSupport();
      break;
  }
}
```

## Next Steps

* [Deposits Guide](./deposits)
* [Withdrawals Guide](./withdrawals)
* [Transfers Guide](./transfers)
* [API Reference](/sdk/react-native/api-reference)
* [Error Handling Concepts](/sdk/concepts/error-handling)
