Multi-Chain
This guide covers using the useChain hook and ChainClient to build React applications that operate across multiple blockchains.
The useChain Hook
useChain returns a ChainClient for a specific chain, memoized to prevent unnecessary re-renders:
import { useChain } from '@testinprod-io/privacy-boost-react';
function MyComponent() {
const arbitrum = useChain({ indexerUrl: 'https://arb.example.com' });
if (!arbitrum) return <div>Loading SDK...</div>;
return <div>Chain ID: {arbitrum.chainId}</div>;
}
useChain returns null if the parent SDK is not yet initialized. Chain clients are cached by indexerUrl, so calling useChain with the same URL in multiple components returns the same instance.
Setup
Wrap your app with PrivacyBoostProvider as usual. The provider initializes the parent SDK, and useChain creates chain clients from it:
import { PrivacyBoostProvider } from '@testinprod-io/privacy-boost-react';
function App() {
return (
<PrivacyBoostProvider config={{
indexerUrl: 'https://op.example.com',
appId: 'my-app',
}}>
<MultiChainApp />
</PrivacyBoostProvider>
);
}
Authentication
Each chain client must be authenticated independently. Use the wallet adapter from useAuth or create one manually:
import { useChain, useAuth } from '@testinprod-io/privacy-boost-react';
import { useState } from 'react';
function ChainConnect({ indexerUrl, name }: { indexerUrl: string; name: string }) {
const chain = useChain({ indexerUrl });
const [authenticated, setAuthenticated] = useState(false);
const connect = async () => {
if (!chain) return;
await chain.authenticate(walletAdapter);
setAuthenticated(true);
};
if (!chain) return <div>Loading...</div>;
if (!authenticated) {
return <button onClick={connect}>Connect to {name}</button>;
}
return (
<div>
<p>Connected to {name} (chain {chain.chainId})</p>
<ChainOperations chain={chain} />
</div>
);
}
Multi-Chain Dashboard
Here’s a pattern for building a multi-chain dashboard:
import { useChain } from '@testinprod-io/privacy-boost-react';
const CHAINS = [
{ name: 'Arbitrum', indexerUrl: 'https://arb.example.com' },
{ name: 'Base', indexerUrl: 'https://base.example.com' },
{ name: 'Optimism', indexerUrl: 'https://op.example.com' },
];
function MultiChainDashboard() {
return (
<div>
{CHAINS.map((chain) => (
<ChainPanel key={chain.indexerUrl} {...chain} />
))}
</div>
);
}
function ChainPanel({ name, indexerUrl }: { name: string; indexerUrl: string }) {
const chain = useChain({ indexerUrl });
if (!chain) return <div>{name}: Loading...</div>;
if (!chain.isAuthenticated) return <div>{name}: Not connected</div>;
return (
<div>
<h3>{name}</h3>
<ChainBalances chain={chain} />
<ChainDeposit chain={chain} />
</div>
);
}
Chain-Scoped Components
Build reusable components that accept a ChainClient:
Balance Display
import type { ChainClient } from '@testinprod-io/privacy-boost-react';
import { useState, useEffect } from 'react';
function ChainBalances({ chain }: { chain: ChainClient }) {
const [balance, setBalance] = useState<bigint>(0n);
useEffect(() => {
chain.vault.getBalance('0x...token').then(setBalance);
}, [chain]);
return <p>Shielded: {balance.toString()}</p>;
}
import type { ChainClient } from '@testinprod-io/privacy-boost-react';
import { useState } from 'react';
function ChainDeposit({ chain }: { chain: ChainClient }) {
const [amount, setAmount] = useState('');
const [loading, setLoading] = useState(false);
const handleShield = async () => {
setLoading(true);
try {
await chain.vault.shield({
tokenAddress: '0x...',
amount: BigInt(amount),
});
} finally {
setLoading(false);
}
};
return (
<div>
<input value={amount} onChange={(e) => setAmount(e.target.value)} />
<button onClick={handleShield} disabled={loading}>
{loading ? 'Depositing...' : 'Deposit'}
</button>
</div>
);
}
function ChainTransfer({ chain }: { chain: ChainClient }) {
const [to, setTo] = useState('');
const [amount, setAmount] = useState('');
const handleSend = async () => {
await chain.vault.send({
to,
tokenAddress: '0x...',
amount: BigInt(amount),
});
};
return (
<div>
<input value={to} onChange={(e) => setTo(e.target.value)} placeholder="Privacy address" />
<input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="Amount" />
<button onClick={handleSend}>Send</button>
</div>
);
}
Chain Switching
Build a chain selector that lets users switch between chains:
import { useChain } from '@testinprod-io/privacy-boost-react';
import { useState } from 'react';
const CHAINS = {
arbitrum: { name: 'Arbitrum', indexerUrl: 'https://arb.example.com' },
base: { name: 'Base', indexerUrl: 'https://base.example.com' },
};
function ChainSwitcher() {
const [selected, setSelected] = useState<keyof typeof CHAINS>('arbitrum');
const chain = useChain(CHAINS[selected]);
return (
<div>
<select value={selected} onChange={(e) => setSelected(e.target.value as keyof typeof CHAINS)}>
{Object.entries(CHAINS).map(([key, { name }]) => (
<option key={key} value={key}>{name}</option>
))}
</select>
{chain && <ChainOperations chain={chain} />}
</div>
);
}
Available Hooks
| Hook | Purpose |
|---|
useChain(config) | Get a ChainClient for a specific chain |
useAuth() | Wallet connection and authentication (primary chain) |
useVault() | Vault operations (primary chain) |
useBalances() | Reactive balances (primary chain) |
useTransactions() | Transaction history (primary chain) |
usePrivacyBoost() | Direct SDK access |
useAuth, useVault, useBalances, and useTransactions operate on the primary chain configured in the provider. For other chains, use useChain and access resources directly via the ChainClient (e.g., chain.vault, chain.transactions).
Next Steps