Skip to main content

Demo Project

A complete example project demonstrating how to use the Privacy Cash EVM SDK in a Next.js frontend: https://github.com/Privacy-Cash/base-sdk-demo-interface

Installation

npm install privacycash-evm wagmi viem @rainbow-me/rainbowkit @tanstack/react-query --save
Requires Node.js 20+. The SDK is written in TypeScript and includes type definitions.

Wallet Provider Setup

Wrap your app with Wagmi and RainbowKit providers configured for the supported EVM mainnets:
'use client'
import { RainbowKitProvider } from '@rainbow-me/rainbowkit'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { WagmiProvider, createConfig, http } from 'wagmi'
import { base, mainnet } from 'wagmi/chains'
import { walletConnect, coinbaseWallet } from 'wagmi/connectors'

const config = createConfig({
  ssr: true,
  chains: [base, mainnet],
  connectors: [
    coinbaseWallet({ appName: 'Your App' }),
    walletConnect({ projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID! }),
  ],
  transports: {
    [base.id]: http(process.env.NEXT_PUBLIC_BASE_RPC_URL),
    [mainnet.id]: http(process.env.NEXT_PUBLIC_ETH_RPC_URL),
  },
})

const queryClient = new QueryClient()

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <WagmiProvider config={config}>
        <RainbowKitProvider initialChain={base}>
          {children}
        </RainbowKitProvider>
      </WagmiProvider>
    </QueryClientProvider>
  )
}

Deriving Encryption Key

Before a user can interact with Privacy Cash, they must sign an off-chain message. This signature is used to derive their private encryption key and UTXO keypair — neither key ever leaves the client.
import { useSignMessage } from 'wagmi'

const SIGN_MESSAGE = 'Privacy Money account sign in'
const LS_KEY_PREFIX = 'evm_sign_'

function saveSignature(address: string, signature: string) {
  localStorage.setItem(`${LS_KEY_PREFIX}${address}`, signature)
}

function getStoredSignature(address: string): string | null {
  return localStorage.getItem(`${LS_KEY_PREFIX}${address}`)
}

// In your component:
const { signMessage } = useSignMessage()
const { address } = useAccount()

function handleSignIn() {
  signMessage(
    { message: SIGN_MESSAGE },
    {
      onSuccess(signature) {
        saveSignature(address!, signature)
      },
    }
  )
}
Auto-prompt the user to sign when they connect their wallet:
useEffect(() => {
  if (!isConnected || !address) return
  const stored = getStoredSignature(address)
  if (!stored) handleSignIn()
}, [isConnected, address])

Circuit Key File

Place the circuit2.zkey file in your Next.js public/ folder. Pass the base path (without extension) to each SDK function:
public/
  circuit2.zkey    ← download from the SDK repo
keyBasePath: '/circuit'  // resolves to /circuit2.zkey at runtime

Selecting a Network

Pass the active EVM network to SDK calls. Base is the default when network is omitted, but explicit selection keeps multi-chain apps predictable.
import { BASE_NETWORK, ETH_NETWORK, getBalance } from 'privacycash-evm'
import { useChainId } from 'wagmi'

function BalanceLoader({ signature, address }: { signature: string, address: string }) {
  const chainId = useChainId()
  const network = chainId === 1 ? ETH_NETWORK : chainId === 8453 ? BASE_NETWORK : undefined

  async function refreshBalance() {
    if (!network) throw new Error('Switch to Base or Ethereum mainnet.')

    return getBalance({
      signature,
      address,
      network,
    })
  }

  return null // Render your balance UI here.
}

Common Issues

  1. Signature re-prompt on every page refresh — Store the signature in localStorage keyed by address as shown above.
  2. walletClient temporarily undefined — Use connector.getProvider() as a fallback when useWalletClient() is unavailable during initial mount.
  3. Wrong chain data — Pass network: BASE_NETWORK or network: ETH_NETWORK to each SDK call after checking the connected wallet chain.