Skip to main content

Advanced

This guide covers patterns for developers who’ve built the basics and need custom hooks, server-side rendering support, or testing utilities.

Custom Hooks

Build reusable hooks that combine SDK functionality with application logic.

useDepositFlow

Encapsulates shield state management:
import { useState, useCallback } from 'react';
import { useVault } from '@testinprod-io/privacy-boost-react';

function useDepositFlow(tokenAddress: string) {
  const { shield } = useVault();
  const [loading, setLoading] = useState(false);
  const [step, setStep] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  const execute = useCallback(async (amount: string) => {
    setLoading(true);
    setError(null);
    try {
      const result = await shield({
        tokenAddress,
        amount,
        onProgress: ({ step }) => setStep(step),
      });
      return result;
    } catch (err: any) {
      if (err.code !== 'TRANSACTION_REJECTED') {
        setError(err.message);
      }
      throw err;
    } finally {
      setLoading(false);
      setStep(null);
    }
  }, [deposit, tokenAddress]);

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

usePollingBalance

Auto-refresh balances on an interval:
import { useEffect, useCallback, useRef } from 'react';
import { useBalances } from '@testinprod-io/privacy-boost-react';

function usePollingBalance(intervalMs = 30000) {
  const { balances, refresh } = useBalances();
  const intervalRef = useRef<ReturnType<typeof setInterval>>();

  useEffect(() => {
    intervalRef.current = setInterval(refresh, intervalMs);
    return () => clearInterval(intervalRef.current);
  }, [refresh, intervalMs]);

  return balances;
}

SSR Support

The SDK uses WebAssembly, which requires special handling in server-side rendering environments.

Next.js App Router

'use client'; // Required — SDK can only run in the browser

import { PrivacyBoostProvider } from '@testinprod-io/privacy-boost-react';

export default function PrivacyLayout({ children }: { children: React.ReactNode }) {
  return (
    <PrivacyBoostProvider config={config}>
      {children}
    </PrivacyBoostProvider>
  );
}
Add WASM support to next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
  webpack: (config) => {
    config.experiments = {
      ...config.experiments,
      asyncWebAssembly: true,
    };
    return config;
  },
};

module.exports = nextConfig;

Next.js Pages Router

Use dynamic imports to prevent server-side loading:
import dynamic from 'next/dynamic';

const PrivacyWallet = dynamic(() => import('../components/PrivacyWallet'), {
  ssr: false,
  loading: () => <div>Loading wallet...</div>,
});

export default function WalletPage() {
  return <PrivacyWallet />;
}

Hydration Mismatch Prevention

If you conditionally render based on auth state, use a client-only check:
import { useState, useEffect } from 'react';

function useIsClient() {
  const [isClient, setIsClient] = useState(false);
  useEffect(() => setIsClient(true), []);
  return isClient;
}

function WalletUI() {
  const isClient = useIsClient();
  const { isAuthenticated } = useAuth();

  if (!isClient) return <div>Loading...</div>;
  // Now safe to render based on auth state
}

Testing

Mock SDK

Create a mock SDK for unit tests:
import type { PrivacyBoost } from '@testinprod-io/privacy-boost';

function createMockSDK(): Partial<PrivacyBoost> {
  return {
    auth: {
      authenticate: jest.fn().mockResolvedValue({ status: 'authenticated', privacyAddress: '0x04...' }),
      isAuthenticated: jest.fn().mockReturnValue(true),
      getPrivacyAddress: jest.fn().mockReturnValue('0x04...'),
      logout: jest.fn(),
      clearSession: jest.fn(),
    } as any,
    vault: {
      shield: jest.fn().mockResolvedValue({ txHash: '0x...' }),
      unshield: jest.fn().mockResolvedValue({ txHash: '0x...' }),
      send: jest.fn().mockResolvedValue({ txHash: '0x...' }),
      getBalance: jest.fn().mockResolvedValue(1000000000000000000n),
      getAllBalances: jest.fn().mockResolvedValue([]),
    } as any,
  };
}

Mock Provider

Wrap components in a test provider:
import { PrivacyBoostProvider } from '@testinprod-io/privacy-boost-react';

function TestProvider({ children }: { children: React.ReactNode }) {
  const mockSDK = createMockSDK();
  return (
    <PrivacyBoostProvider sdk={mockSDK as any}>
      {children}
    </PrivacyBoostProvider>
  );
}

// Usage in tests
render(
  <TestProvider>
    <YourComponent />
  </TestProvider>
);

Testing useAuth

import { renderHook, act } from '@testing-library/react-hooks';
import { useAuth } from '@testinprod-io/privacy-boost-react';

test('authenticateWithWalletAdapter sets isAuthenticated', async () => {
  const { result } = renderHook(() => useAuth(), { wrapper: TestProvider });

  await act(async () => {
    await result.current.authenticateWithWalletAdapter();
  });

  expect(result.current.isAuthenticated).toBe(true);
  expect(result.current.privacyAddress).toBeDefined();
});

Testing useVault

test('shield calls vault.shield', async () => {
  const { result } = renderHook(() => useVault(), { wrapper: TestProvider });

  await act(async () => {
    await result.current.shield({
      tokenAddress: '0x...',
      amount: '1.0',
    });
  });

  expect(mockSDK.vault.shield).toHaveBeenCalledWith(
    expect.objectContaining({ tokenAddress: '0x...' })
  );
});

Next Steps