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

# Performance

> Performance optimization strategies for the React Native SDK

# Performance

This guide covers performance optimization strategies for the Privacy Boost React Native SDK. Unlike the web SDK there's no WASM to lazy-load — the heavy lifting runs in native iOS/Android binaries linked into the app. The costs to manage are SDK init, JS↔native bridge traffic, balance refresh frequency, and re-renders.

## SDK Lifecycle

### One Instance Per JS Runtime

`PrivacyBoost` holds a JWT, identity keys, and an internal note cache. Construct it once in a module-level ref or a context provider — not inside a component that may unmount and remount.

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

let cached: PrivacyBoost | null = null;

export function getPrivacyBoost(): PrivacyBoost {
  if (cached) return cached;
  cached = new PrivacyBoost(PrivacyBoostConfig.create({
    serverUrl: 'https://optimism.sepolia.privacyboost.io',
    chainId: undefined,
    shieldContractAddress: undefined,
    wethContractAddress: '0x4200000000000000000000000000000000000006',
    teePublicKey: undefined,
    appId: 'app_abc123xyz',
    persistenceStorage: undefined,
    persistenceUnlock: undefined,
  }));
  return cached;
}
```

Or via context, lifted above any screen that needs it:

```tsx theme={null}
const PrivacyBoostContext = React.createContext<PrivacyBoost | null>(null);

export function PrivacyBoostProvider({ children }: { children: React.ReactNode }) {
  const ref = React.useRef<PrivacyBoost | null>(null);
  if (ref.current === null) {
    ref.current = getPrivacyBoost();
  }
  return (
    <PrivacyBoostContext.Provider value={ref.current}>
      {children}
    </PrivacyBoostContext.Provider>
  );
}

export const usePrivacyBoost = () => {
  const sdk = React.useContext(PrivacyBoostContext);
  if (!sdk) throw new Error('PrivacyBoostProvider missing');
  return sdk;
};
```

### Defer Init Until First Use

The first call into the native SDK pays one-time setup cost. If your app has flows that don't touch the privacy pool (onboarding, splash, marketing), defer construction until the first gated screen — keep it off cold-start.

## Bridge Traffic

Every method on the SDK is a bridge call. The cost is small but not free — for tight loops or per-render fetches it adds up.

### Avoid Per-Render Fetches

```tsx theme={null}
// BAD — fetches on every render
function BalanceLabel({ token }: { token: string }) {
  const sdk = usePrivacyBoost();
  const [balance, setBalance] = React.useState<string>('0');
  sdk.getBalance(token).then(b => setBalance(b.shieldedBalance));  // wrong
  return <Text>{balance}</Text>;
}

// GOOD — fetched once, refresh on demand
function BalanceLabel({ token }: { token: string }) {
  const sdk = usePrivacyBoost();
  const [balance, setBalance] = React.useState<string>('0');
  React.useEffect(() => {
    let active = true;
    sdk.getBalance(token).then(b => {
      if (active) setBalance(b.shieldedBalance);
    });
    return () => { active = false; };
  }, [sdk, token]);
  return <Text>{balance}</Text>;
}
```

### Batch Where Possible

```typescript theme={null}
// SLOW — N bridge calls
const balances = [];
for (const token of tokens) {
  balances.push(await sdk.getBalance(token));
}

// FAST — one call
const balances = await sdk.getAllBalances();
```

### Don't `await` Inside `forEach`

```typescript theme={null}
// BAD — `forEach` doesn't await; promises run unsupervised
tokens.forEach(async (t) => {
  await sdk.getBalance(t);
});

// GOOD — Promise.all if you really need per-token, otherwise getAllBalances
const balances = await Promise.all(tokens.map(t => sdk.getBalance(t)));
```

## Balance Refresh

### Centralize Through One Hook

Put refresh logic in a single hook so screens don't each schedule their own polling:

```typescript theme={null}
import { create } from 'zustand';

interface BalancesState {
  byToken: Record<string, TokenBalance>;
  lastFetch: number;
  refresh: (sdk: PrivacyBoost, force?: boolean) => Promise<void>;
}

const STALENESS_MS = 30_000;
let inflight: Promise<void> | null = null;

export const useBalances = create<BalancesState>((set, get) => ({
  byToken: {},
  lastFetch: 0,
  refresh: async (sdk, force = false) => {
    if (inflight) return inflight;
    if (!force && Date.now() - get().lastFetch < STALENESS_MS) return;
    inflight = (async () => {
      try {
        const fresh = await sdk.getAllBalances();
        const byToken: Record<string, TokenBalance> = {};
        for (const b of fresh) byToken[b.tokenAddress] = b;
        set({ byToken, lastFetch: Date.now() });
      } finally {
        inflight = null;
      }
    })();
    return inflight;
  },
}));
```

The module-scoped `inflight` deduplicates concurrent refreshes.

### Refresh on Foreground, Not on a Timer

Tying refresh to `setInterval` drains battery. Use `AppState`:

```typescript theme={null}
import { AppState } from 'react-native';

React.useEffect(() => {
  const sub = AppState.addEventListener('change', (state) => {
    if (state === 'active') {
      useBalances.getState().refresh(sdk);
    }
  });
  return () => sub.remove();
}, [sdk]);
```

## Re-Renders

### Selector Subscriptions

If you're using a global store (Zustand, Redux, Jotai), subscribe to the smallest slice:

```typescript theme={null}
// BAD — re-renders on every balance change anywhere
const balances = useBalances(s => s.byToken);

// GOOD — re-renders only when this token changes
const usdcBalance = useBalances(s => s.byToken[USDC]?.shieldedBalance);
```

### Stable Keys in `FlatList`

For transaction history, key by `txHash` so the list reuses rows:

```tsx theme={null}
<FlatList
  data={transactions}
  keyExtractor={(tx) => tx.txHash}
  renderItem={({ item }) => <TransactionRow tx={item} />}
/>
```

### Memoize Heavy Rows

If a row component does meaningful work (formatting amounts, deriving labels), wrap it in `React.memo`:

```tsx theme={null}
const TransactionRow = React.memo(function TransactionRow({ tx }: Props) {
  // ...
}, (a, b) => a.tx.txHash === b.tx.txHash && a.tx.status === b.tx.status);
```

## Network

### Parallelize Independent Reads

```typescript theme={null}
const [balances, history, fees] = await Promise.all([
  sdk.getAllBalances(),
  sdk.getTransactionHistory(undefined, 20, undefined),
  sdk.getFees(tokenId),
]);
```

### Page History — Don't Fetch It All

```typescript theme={null}
const firstPage = await sdk.getTransactionHistory(undefined, 20, undefined);
// later
if (firstPage.next) {
  const nextPage = await sdk.getTransactionHistory(undefined, 20, firstPage.next);
}
```

## Memory

### Drop Off-Screen Pages

For long histories, store the visible window only and refetch on scroll-back instead of accumulating every page in state. `FlatList`'s `removeClippedSubviews` and `windowSize` reduce native view memory but won't help your JS state.

### Don't Wrap Big Arrays in `useState`

Arrays of `Transaction` objects can be heavy. Prefer a normalized store keyed by `txHash` so component state holds only ids:

```typescript theme={null}
const ids = useTransactions(s => s.visibleIds);    // string[]
const tx  = useTransactions(s => s.byHash[id]);    // single row
```

## Operation Timing

Wrap SDK calls to observe timing during development:

```typescript theme={null}
async function timed<T>(label: string, op: () => Promise<T>): Promise<T> {
  const start = performance.now();
  try {
    return await op();
  } finally {
    const ms = performance.now() - start;
    if (__DEV__) console.log(`[PB] ${label}: ${ms.toFixed(1)}ms`);
    if (ms > 5_000) reportSlow(label, ms);
  }
}

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

In production, send timings to your APM (Sentry Performance, Datadog RUM, Firebase Performance) so slow operations surface as percentile histograms.

## What the SDK Already Optimizes

Things you do **not** need to layer on top:

* **Note cache** — the SDK caches unspent notes natively and only refetches new merkle tree leaves.
* **Merkle tree pruning** — proven nodes are kept; the rest are pruned.
* **JWT reuse** — `clearSession()` keeps identity keys so the next `authenticate()` is fast.
* **Native execution** — proofs run on the native side; the JS thread is not blocked while a shield is being generated.

## Best Practices Summary

1. One SDK instance per JS runtime — hold it in a module-level ref or a context provider.
2. Defer SDK init until the first privacy-pool screen.
3. Don't fetch in render bodies — use `useEffect` with cleanup.
4. Refresh balances on `AppState === 'active'` and after writes, not on a timer.
5. Deduplicate concurrent refreshes with an `inflight` ref.
6. Subscribe to the smallest slice of state your screen needs.
7. Page transaction history; don't fetch it all.
8. Use `clearSession()` over `logout()` when re-auth is expected soon.

## Next Steps

* [Error Handling Patterns](./error-handling)
* [Wallet Integration](../guides/wallet-integration)
