Skip to main content

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.

Multi-Chain

This guide covers using ChainContextHandle to operate on multiple blockchains from a single React Native SDK instance.
For an overview of multi-chain concepts, see Multi-Chain Concepts.

Setup

Initialize the parent SDK against your primary chain, then open a chain context for each additional chain:
import {
  PrivacyBoost,
  PrivacyBoostConfig,
  ChainContextConfig,
} from '@sunnyside-io/privacy-boost-react-native';

const sdk = new PrivacyBoost(
  PrivacyBoostConfig.create({
    serverUrl: 'https://op.example.com',
    appId: 'my-app',
    // chainId / shieldContractAddress / wethContractAddress
    // are discovered from the server when omitted.
    chainId: undefined,
    shieldContractAddress: undefined,
    wethContractAddress: undefined,
    teePublicKey: undefined,
    persistenceStorage: undefined,
    persistenceUnlock: undefined,
  }),
);

// After authenticating the parent SDK once, open per-chain contexts.
const ethereum = sdk.createChainContext({
  serverUrl: 'https://eth.example.com',
  chainId: 1n,
  shieldContractAddress: undefined,
  wethContractAddress: undefined,
  rpcUrl: undefined,
  teePublicKey: undefined,
  timeoutMs: 30_000n,
});

const base = sdk.createChainContext({
  serverUrl: 'https://base.example.com',
  chainId: 8453n,
  shieldContractAddress: undefined,
  wethContractAddress: undefined,
  rpcUrl: undefined,
  teePublicKey: undefined,
  timeoutMs: 30_000n,
});
ChainContextConfig.chainId is a bigint and is required. Pass undefined for the optional fields to let the SDK discover them from the server.

Authentication

Each chain context needs to authenticate before it can operate. Authentication shares your privacy keys from the parent SDK but obtains a chain-specific JWT and registration:
// Authenticate the parent SDK once — establishes identity keys.
await sdk.authenticate(walletDelegate, undefined, undefined);

// Authenticate each chain context with the same wallet.
const ethLogin = await ethereum.authenticate(walletDelegate);
const baseLogin = await base.authenticate(walletDelegate);

console.log('Ethereum chain id:', ethereum.chainId());
console.log('Ethereum authenticated:', ethereum.isAuthenticated());
console.log('Ethereum registered:', ethereum.isRegistered());
ChainContextHandle.authenticate() only takes a WalletDelegatekeySource and tokenProvider come from the parent SDK.

Operations

Once authenticated, a ChainContextHandle exposes the same per-chain operations as the parent SDK:

Deposits

const amount = sdk.parseAmount('1.0', 18);
const result = await ethereum.shield(
  '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH on Ethereum
  amount,
);
console.log('Ethereum shield tx:', result.txHash);

Private Transfers

const identity = await ethereum.resolveIdentity?.('0x1234...abcd');
// resolveIdentity is on the parent SDK; ChainContextHandle inherits identity.
const recipient = sdk.resolveIdentity('0x1234...abcd');

const transfer = await base.send(
  '0x...token-on-base',
  sdk.parseAmount('0.5', 18),
  recipient.privacyAddress,
);
console.log('Base transfer tx:', transfer.txHash);

Withdrawals

await ethereum.unshield(
  '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
  sdk.parseAmount('0.25', 18),
  '0x...destination',
);

Balances

const ethBalance = await ethereum.getBalance(
  '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
);
const baseBalance = await base.getBalance('0x...token-on-base');

console.log('Ethereum shielded:', ethBalance.shieldedBalance);
console.log('Base shielded:', baseBalance.shieldedBalance);

Status polling and history

const status = await ethereum.getShieldStatus(result.requestId);
const history = await base.getTransactionHistory(undefined, 20, undefined);
const notes = await ethereum.getUnspentNotes(undefined);

Cross-Chain Patterns

Parallel Operations

Chain contexts are independent — run operations concurrently:
const [ethResult, baseResult] = await Promise.all([
  ethereum.shield('0x...', sdk.parseAmount('1.0', 18)),
  base.shield('0x...', sdk.parseAmount('2.0', 18)),
]);

Multi-Chain Balance Aggregation

import type { ChainContextHandle } from '@sunnyside-io/privacy-boost-react-native';

async function totalShielded(
  chains: { ctx: ChainContextHandle; token: string }[],
): Promise<bigint> {
  const balances = await Promise.all(
    chains.map(({ ctx, token }) => ctx.getBalance(token)),
  );
  return balances.reduce(
    (sum, b) => sum + BigInt(b.shieldedBalance),
    0n,
  );
}

const total = await totalShielded([
  { ctx: ethereum, token: '0x...' },
  { ctx: base, token: '0x...' },
]);

Chain Selection

const chains = new Map<string, ChainContextHandle>([
  ['ethereum', ethereum],
  ['base', base],
]);

function getChain(name: string): ChainContextHandle {
  const ctx = chains.get(name);
  if (!ctx) throw new Error(`Unknown chain: ${name}`);
  return ctx;
}

await getChain('ethereum').shield('0x...', sdk.parseAmount('1.0', 18));

React Hook Pattern

import { useEffect, useRef } from 'react';
import {
  PrivacyBoost,
  ChainContextHandle,
  ChainContextConfig,
} from '@sunnyside-io/privacy-boost-react-native';

function useChainContext(
  sdk: PrivacyBoost,
  config: ChainContextConfig,
): ChainContextHandle {
  const ref = useRef<ChainContextHandle | null>(null);
  if (ref.current === null) {
    ref.current = sdk.createChainContext(config);
  }
  useEffect(() => {
    return () => {
      ref.current?.clearSession();
    };
  }, []);
  return ref.current;
}

Cleanup

Each chain context can drop its JWT independently while keeping the parent SDK identity intact:
ethereum.clearSession();   // drop just Ethereum's JWT
sdk.clearSession();        // drop the parent JWT (does not clear contexts)
sdk.logout();              // end the whole session and clear identity

Next Steps