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

> Advanced error handling — classification, telemetry, and operational patterns

# Error Handling Patterns

This page covers advanced error handling — classification, telemetry, structured logging, and circuit breakers. For the basics (try/catch, retry, re-auth, user-friendly messages), see the [Error Handling guide](../guides/error-handling).

## Error Classification

`SdkError` is a tagged union — every variant has a stable `tag` you can read at runtime. Unlike the iOS / Android SDKs (which ship a `category` extension in their `Defaults` libraries), the React Native binding leaves classification to you. This three-line helper mirrors the categories used on the other platforms:

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

export type ErrorCategory =
  | 'Auth'
  | 'Network'
  | 'Validation'
  | 'Permission'
  | 'RateLimit'
  | 'Server'
  | 'Internal';

export function categoryOf(err: unknown): ErrorCategory {
  if (!isSdkError(err)) return 'Internal';
  const tag = (err as { tag?: string }).tag;
  switch (tag) {
    case SdkError_Tags.NotAuthenticated:
    case SdkError_Tags.AuthenticationFailed:
    case SdkError_Tags.AuthServerError:
      return 'Auth';
    case SdkError_Tags.NetworkError:
      return 'Network';
    case SdkError_Tags.InvalidConfig:
    case SdkError_Tags.ConfigError:
    case SdkError_Tags.InvalidAddress:
    case SdkError_Tags.InvalidAmount:
    case SdkError_Tags.InvalidInput:
    case SdkError_Tags.InsufficientBalance:
      return 'Validation';
    case SdkError_Tags.Forbidden:
    case SdkError_Tags.SignatureRejected:
      return 'Permission';
    case SdkError_Tags.RateLimited:
      return 'RateLimit';
    case SdkError_Tags.ShieldError:
    case SdkError_Tags.TransferError:
    case SdkError_Tags.NoteError:
    case SdkError_Tags.MerkleError:
    case SdkError_Tags.ApiError:
    case SdkError_Tags.ResourceNotFound:
      return 'Server';
    default:
      return 'Internal';
  }
}

export function isRetryable(err: unknown): boolean {
  if (!isSdkError(err)) return false;
  const tag = (err as { tag?: string }).tag;
  if (tag === SdkError_Tags.NetworkError || tag === SdkError_Tags.RateLimited) {
    return true;
  }
  if (tag === SdkError_Tags.ApiError) {
    return Boolean((err as { retryable?: boolean }).retryable);
  }
  return false;
}

function isSdkError(err: unknown): boolean {
  // Every SdkError variant is constructable as a class — instanceOf checks below.
  return (
    SdkError.NotConnected.instanceOf(err) ||
    SdkError.NotAuthenticated.instanceOf(err) ||
    SdkError.NetworkError.instanceOf(err) ||
    SdkError.WalletError.instanceOf(err) ||
    SdkError.InsufficientBalance.instanceOf(err) ||
    SdkError.InvalidAddress.instanceOf(err) ||
    SdkError.InvalidAmount.instanceOf(err) ||
    SdkError.SerializationError.instanceOf(err) ||
    SdkError.AuthServerError.instanceOf(err) ||
    SdkError.ShieldError.instanceOf(err) ||
    SdkError.TransferError.instanceOf(err) ||
    SdkError.NoteError.instanceOf(err) ||
    SdkError.MerkleError.instanceOf(err) ||
    SdkError.ApiError.instanceOf(err) ||
    SdkError.RateLimited.instanceOf(err) ||
    SdkError.Forbidden.instanceOf(err) ||
    SdkError.ResourceNotFound.instanceOf(err) ||
    SdkError.InternalError.instanceOf(err) ||
    SdkError.SignatureRejected.instanceOf(err)
  );
}
```

| Category     | Meaning                          | Recommended UI                    | Retry?                          |
| ------------ | -------------------------------- | --------------------------------- | ------------------------------- |
| `Auth`       | Session/auth flow failed         | Re-prompt sign-in                 | No (but re-auth and try once)   |
| `Network`    | Connectivity / DNS / timeout     | "Check your connection"           | Yes, with backoff               |
| `Validation` | Input is malformed               | Inline form error                 | No                              |
| `Permission` | User cancelled / forbidden       | Silent dismiss or contact support | No                              |
| `RateLimit`  | Too many requests                | Wait + auto-retry                 | Yes, after `retryAfterMs`       |
| `Server`     | Backend / proof / merkle failure | Generic error toast               | Sometimes (check `isRetryable`) |
| `Internal`   | Bug or invariant violation       | "Something went wrong" + report   | No                              |

## Structured Logging

Log SDK errors with stable fields so they're queryable in your log pipeline (Logcat, Console, Datadog, etc.).

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

function variantTag(err: unknown): string {
  const tag = (err as { tag?: string }).tag;
  if (!tag) return 'Other';
  if (tag === SdkError_Tags.ApiError) return `ApiError.${(err as { code?: string }).code ?? '?'}`;
  if (tag === SdkError_Tags.ShieldError) return `ShieldError.${(err as { code?: string }).code ?? '?'}`;
  if (tag === SdkError_Tags.TransferError) return `TransferError.${(err as { code?: string }).code ?? '?'}`;
  return tag;
}

export function logError(operation: string, err: unknown) {
  const category = categoryOf(err);
  const variant  = variantTag(err);
  const retry    = isRetryable(err);

  console.error(`[PB] [${operation}] category=${category} retryable=${retry} variant=${variant}`, err);
}
```

If your message field could include PII, scrub or omit it from logs.

## Error Reporting Integration

### Sentry

```typescript theme={null}
import * as Sentry from '@sentry/react-native';

export function reportSdk(err: unknown, operation: string) {
  Sentry.withScope((scope) => {
    scope.setTag('pb.operation', operation);
    scope.setTag('pb.category', categoryOf(err));
    scope.setTag('pb.retryable', String(isRetryable(err)));
    scope.setTag('pb.variant', variantTag(err));
    Sentry.captureException(err);
  });
}
```

The `pb.category` tag lets you build dashboards that group errors by class — "auth-class errors are spiking" is more actionable than "twelve different variants are spiking."

### Filter Out Noise

User cancellations (`SignatureRejected`) and validation errors aren't bugs — don't ship them to your error tracker:

```typescript theme={null}
export function shouldReport(err: unknown): boolean {
  const cat = categoryOf(err);
  return cat !== 'Permission' && cat !== 'Validation';
}
```

## Operation-Scoped Wrapping

Wrap each meaningful SDK call so logging, reporting, and retry sit in one place.

```typescript theme={null}
export async function runSdk<T>(
  operation: string,
  block: () => Promise<T>,
): Promise<T> {
  try {
    return await block();
  } catch (err) {
    logError(operation, err);
    if (shouldReport(err)) reportSdk(err, operation);
    throw err;
  }
}

// Usage
const result = await runSdk('shield', () =>
  sdk.shield(token, amount),
);
```

## Circuit Breaker

If the backend is degraded, hammering it with retries makes things worse. Wrap operations in a circuit breaker that trips after consecutive server-class failures.

```typescript theme={null}
type BreakerState =
  | { kind: 'closed' }
  | { kind: 'open'; until: number }
  | { kind: 'halfOpen' };

class CircuitBreaker {
  private state: BreakerState = { kind: 'closed' };
  private consecutive = 0;
  constructor(
    private readonly threshold = 5,
    private readonly cooldownMs = 60_000,
  ) {}

  canPass(): boolean {
    if (this.state.kind === 'closed' || this.state.kind === 'halfOpen') return true;
    if (Date.now() >= this.state.until) {
      this.state = { kind: 'halfOpen' };
      return true;
    }
    return false;
  }

  recordSuccess() {
    this.consecutive = 0;
    this.state = { kind: 'closed' };
  }

  recordFailure(err: unknown) {
    const cat = categoryOf(err);
    if (cat !== 'Server' && cat !== 'Network') return;
    this.consecutive++;
    if (this.consecutive >= this.threshold) {
      this.state = { kind: 'open', until: Date.now() + this.cooldownMs };
    }
  }
}

const breaker = new CircuitBreaker();

export async function guarded<T>(block: () => Promise<T>): Promise<T> {
  if (!breaker.canPass()) {
    throw new Error('circuit open');
  }
  try {
    const result = await block();
    breaker.recordSuccess();
    return result;
  } catch (err) {
    breaker.recordFailure(err);
    throw err;
  }
}
```

Auth/validation/permission failures should not trip the breaker — they're user-class problems, not service degradation. The classification by `categoryOf` makes that distinction trivial.

## Telemetry: Operation Latency by Outcome

Pair errors with timings — slow successes are interesting too.

```typescript theme={null}
interface OpMetric {
  operation: string;
  durationMs: number;
  outcome: string;
}

export async function instrument<T>(
  operation: string,
  sink: (m: OpMetric) => void,
  block: () => Promise<T>,
): Promise<T> {
  const start = performance.now();
  try {
    const result = await block();
    sink({ operation, durationMs: performance.now() - start, outcome: 'success' });
    return result;
  } catch (err) {
    sink({ operation, durationMs: performance.now() - start, outcome: categoryOf(err) });
    throw err;
  }
}
```

Forward `OpMetric` to Sentry Performance, Datadog RUM, Firebase Performance, or your own analytics pipeline.

## Best Practices

1. **Branch on `categoryOf(err)`, not on individual variants** — UI logic stays small and stable across SDK upgrades.
2. **Filter user-class errors out of error reporters** — `Permission` and `Validation` are noise.
3. **Tag every report with `pb.category` and `pb.variant`** — makes dashboards actually useful.
4. **Trip circuit breakers only on `Server` and `Network`** — never on auth or validation.
5. **Pair errors with timings** — a slow success that turns into a timeout is worth catching upstream.

## Next Steps

* [Performance](./performance)
* [Error Handling guide](../guides/error-handling) — basics
* [Error Handling Concepts](/sdk/concepts/error-handling) — cross-platform error model
