React Integration
Complete React example with wallet connection and privacy operations.
Setup
bash
npm create vite@latest my-privacy-app -- --template react-ts
cd my-privacy-app
npm install @zkprivacy/sdk viem wagmi @rainbow-me/rainbowkitProvider Setup
tsx
// src/providers.tsx
import { WagmiProvider, createConfig, http } from 'wagmi';
import { mainnet } from 'wagmi/chains';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { RainbowKitProvider, getDefaultConfig } from '@rainbow-me/rainbowkit';
import '@rainbow-me/rainbowkit/styles.css';
const config = getDefaultConfig({
appName: 'ZK Privacy App',
projectId: 'YOUR_PROJECT_ID', // Get from WalletConnect
chains: [mainnet],
});
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
{children}
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
);
}Privacy Context
tsx
// src/context/privacy.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import {
PrivacyClient,
createProver,
setGlobalProver,
DEPLOYMENTS,
} from '@zkprivacy/sdk';
import { useWalletClient } from 'wagmi';
interface PrivacyContextType {
client: PrivacyClient | null;
isConnected: boolean;
isLoading: boolean;
balance: bigint;
address: string | null;
connect: () => Promise<void>;
disconnect: () => void;
sync: () => Promise<void>;
}
const PrivacyContext = createContext<PrivacyContextType | null>(null);
export function PrivacyProvider({ children }: { children: ReactNode }) {
const { data: walletClient } = useWalletClient();
const [client, setClient] = useState<PrivacyClient | null>(null);
const [isConnected, setIsConnected] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [balance, setBalance] = useState(0n);
const [address, setAddress] = useState<string | null>(null);
// Initialize prover on mount
useEffect(() => {
async function initProver() {
const prover = await createProver({
transferCircuit: '/circuits/transfer.json',
unshieldCircuit: '/circuits/unshield.json',
});
setGlobalProver(prover);
const newClient = new PrivacyClient({
rpcUrl: DEPLOYMENTS.remote.rpcUrl,
poolAddress: DEPLOYMENTS.remote.pool,
relayerUrl: DEPLOYMENTS.remote.relayerUrl,
});
setClient(newClient);
setIsLoading(false);
}
initProver();
}, []);
const connect = async () => {
if (!client || !walletClient) return;
setIsLoading(true);
try {
await client.connect({ mode: 'linked', walletClient });
await client.sync();
setIsConnected(true);
setAddress(client.getAddress());
setBalance(client.getBalance());
} finally {
setIsLoading(false);
}
};
const disconnect = () => {
client?.disconnect();
setIsConnected(false);
setAddress(null);
setBalance(0n);
};
const sync = async () => {
if (!client || !isConnected) return;
await client.sync();
setBalance(client.getBalance());
};
return (
<PrivacyContext.Provider value={{
client,
isConnected,
isLoading,
balance,
address,
connect,
disconnect,
sync,
}}>
{children}
</PrivacyContext.Provider>
);
}
export function usePrivacy() {
const context = useContext(PrivacyContext);
if (!context) throw new Error('usePrivacy must be used within PrivacyProvider');
return context;
}Transfer Component
tsx
// src/components/Transfer.tsx
import { useState } from 'react';
import { parseEther, formatEther } from 'viem';
import { usePrivacy } from '../context/privacy';
export function Transfer() {
const { client, isConnected, balance, sync } = usePrivacy();
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const [isPending, setIsPending] = useState(false);
const [txHash, setTxHash] = useState<string | null>(null);
const handleTransfer = async () => {
if (!client || !recipient || !amount) return;
setIsPending(true);
setTxHash(null);
try {
const hash = await client.transfer({
recipient,
amount: parseEther(amount),
});
setTxHash(hash);
await sync();
setRecipient('');
setAmount('');
} catch (error) {
console.error('Transfer failed:', error);
alert('Transfer failed: ' + (error as Error).message);
} finally {
setIsPending(false);
}
};
if (!isConnected) {
return <p>Connect wallet to transfer</p>;
}
return (
<div>
<h2>Transfer</h2>
<p>Balance: {formatEther(balance)} ETH</p>
<input
type="text"
placeholder="Recipient (zks1...)"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
disabled={isPending}
/>
<input
type="text"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
disabled={isPending}
/>
<button onClick={handleTransfer} disabled={isPending || !recipient || !amount}>
{isPending ? 'Generating Proof...' : 'Transfer'}
</button>
{txHash && (
<p>Success! TX: {txHash}</p>
)}
</div>
);
}App Component
tsx
// src/App.tsx
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { usePrivacy } from './context/privacy';
import { Transfer } from './components/Transfer';
function App() {
const { isLoading, isConnected, address, connect, disconnect } = usePrivacy();
return (
<div>
<h1>ZK Privacy App</h1>
<ConnectButton />
{isLoading ? (
<p>Loading prover...</p>
) : isConnected ? (
<>
<p>ZK Address: {address}</p>
<button onClick={disconnect}>Disconnect Privacy</button>
<Transfer />
</>
) : (
<button onClick={connect}>Connect Privacy</button>
)}
</div>
);
}
export default App;Vite Config
typescript
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
optimizeDeps: {
exclude: ['@aztec/bb.js'],
},
assetsInclude: ['**/*.wasm'],
});