Skip to main content

Multi-Chain

This guide covers using ChainClient to perform privacy operations across multiple blockchains from a single SDK instance.
For an overview of multi-chain concepts, see Multi-Chain Concepts.

Setup

Initialize the parent SDK, then create chain clients for each blockchain you want to interact with:
import { PrivacyBoost } from '@testinprod-io/privacy-boost';

// Initialize with your primary chain
const sdk = await PrivacyBoost.create({
  indexerUrl: 'https://op.example.com',
  appId: 'my-app',
});

// Create chain clients — only indexerUrl is required
const arbitrum = sdk.chain({ indexerUrl: 'https://arb.example.com' });
const base = sdk.chain({ indexerUrl: 'https://base.example.com' });
You can pass additional configuration to override auto-discovered values:
const arbitrum = sdk.chain({
  indexerUrl: 'https://arb.example.com',
  chainId: 42161,
  wethContract: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
  rpcUrl: 'https://arb1.arbitrum.io/rpc',
});

Authentication

Each chain client must be authenticated before use. Authentication shares your identity keys from the parent SDK but obtains a chain-specific JWT:
// Create a wallet adapter (same as parent SDK auth)
const adapter = {
  async connect() { /* ... */ },
  async signMessage(msg: string) { /* ... */ },
  async signTypedData(data: string) { /* ... */ },
  async sendTransaction(tx: unknown) { /* ... */ },
  async getAddress() { /* ... */ },
  async getChainId() { /* ... */ },
  async disconnect() {},
};

// Authenticate on each chain
await arbitrum.authenticate(adapter);
await base.authenticate(adapter);

// Check auth status
console.log('Arbitrum authenticated:', arbitrum.isAuthenticated);
console.log('Arbitrum chain ID:', arbitrum.chainId);

Operations

Once authenticated, a ChainClient provides the same vault, transactions, and audit resources as the parent SDK:

Deposits

const result = await arbitrum.vault.shield({
  tokenAddress: '0x...token-on-arbitrum',
  amount: 1000000000000000000n, // 1 token
  onProgress: ({ step, message }) => {
    console.log(`[Arbitrum] ${step}: ${message}`);
  },
});
console.log('Arbitrum deposit tx:', result.txHash);

Private Transfers

// Look up recipient on a specific chain
const identity = await base.searchAddress('0x1234...abcd');

await base.vault.send({
  to: identity.privacyAddress,
  tokenAddress: '0x...token-on-base',
  amount: 500000000000000000n,
});

Withdrawals

await arbitrum.vault.unshield({
  tokenAddress: '0x...',
  amount: 250000000000000000n,
  recipientAddress: '0x...destination',
});

Balances

// Per-chain balances
const arbBalance = await arbitrum.vault.getBalance('0x...');
const baseBalance = await base.vault.getBalance('0x...');

console.log('Arbitrum shielded:', arbBalance);
console.log('Base shielded:', baseBalance);

Cross-Chain Patterns

Parallel Operations

Since chain clients are independent, you can run operations in parallel:
const [arbResult, baseResult] = await Promise.all([
  arbitrum.vault.shield({ tokenAddress: '0x...', amount: 1000n }),
  base.vault.shield({ tokenAddress: '0x...', amount: 2000n }),
]);

Multi-Chain Balance Aggregation

async function getTotalBalance(
  chains: { client: ChainClient; token: string }[]
): Promise<bigint> {
  const balances = await Promise.all(
    chains.map(({ client, token }) => client.vault.getBalance(token))
  );
  return balances.reduce((sum, b) => sum + b, 0n);
}

Chain Selection

// Store chain clients in a map for easy selection
const chains = new Map([
  ['arbitrum', sdk.chain({ indexerUrl: 'https://arb.example.com' })],
  ['base', sdk.chain({ indexerUrl: 'https://base.example.com' })],
  ['optimism', sdk.chain({ indexerUrl: 'https://op.example.com' })],
]);

// Select chain by name
function getChain(name: string): ChainClient {
  const client = chains.get(name);
  if (!client) throw new Error(`Unknown chain: ${name}`);
  return client;
}

await getChain('arbitrum').vault.shield({ tokenAddress: '0x...', amount: 1000n });

Cleanup

// Clear a single chain's session (JWT + pending state)
arbitrum.clearSession();

// Dispose a single chain client
arbitrum.dispose();

// Dispose everything (parent SDK + all chain clients)
sdk.dispose();

Next Steps