Quickstart

1. Create a React + Typescript Project

Let's create a simple vite project with React and Typescript:

npm create vite@latest sdk-test -- --template react-ts

2. Install Dependencies

Install shielder-sdk and additional dependencies:

npm install @cardinal-cryptography/[email protected]
npm install @cardinal-cryptography/[email protected]
npm install @cardinal-cryptography/[email protected]
npm install viem @types/node @vitejs/plugin-react

3. Configure Vite

Add following code to vite.config.ts :

vite.config.ts
import * as path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
  optimizeDeps: {
    exclude: ["@cardinal-cryptography/shielder-sdk-crypto-wasm-light"],
  },
});

4. Set Up WASM Cryptography Client

Create a file src/shielderWasm.ts , where we'll set up the wasm engine loading.

Note, that in quickstart guide we use a light wasm client, which is simple to set up, but currently is in alpha stage. Refer to Cryptography Client for production-ready setup details.

PCR values can be found at Github Releases, for example pcr-92cd84c.json

src/shielderWasm.ts
import { initWasmWorker } from "@cardinal-cryptography/shielder-sdk-crypto-wasm-light";

// values from releases page
const pcrs = new Map<string, string>(
  Object.entries({
    "0": "94b74422daddb8f503fcd69df064c7cde5d053001b9cd153c75ec34484283496a37e89c7287a00f467759a6863362b60",
    "1": "927e084e583f5c2d60a39e2b9cd9728bfb390aa9f83dee4b6ac768509850ba273ea8b019ccfbf3180eb18a2dd0c4a678",
    "2": "07c20c057d5c10cb732b273b7fa26a2b67e333344ccda49be939a6b7c5bed5e46f0b0703386dd2d0f6104a13a4894cb2",
  })
);

// WASM crypto client
export const wasmCryptoClientRead = (async () => {
  return initWasmWorker("https://prover-server.test.blanksquare.dev", pcrs);
})();

5. Initialize the Shielder SDK Client

Create src/shielder.ts:

src/shielder.ts:
import {
  createShielderClient,
  type ShielderOperation,
} from "@cardinal-cryptography/shielder-sdk";
import { createPublicClient, http } from "viem";
import { baseSepolia } from "viem/chains";
import { wasmCryptoClientRead } from "./shielderWasm";

const chain = baseSepolia;

const shielderContractAddress =
  "0x2098a5f59DAB63F1a2aB7C0715DA437D1efB012B" as `0x${string}`;

const relayerUrl = "https://base-testnet-shielder-relayer-v3.test.blanksquare.dev";

export const publicClient = createPublicClient({
  chain,
  transport: http(),
});

// Simple in-memory key-value storage
const shielderStorage: Map<string, string> = new Map();

// 66-character (0x prefix + 64 hex symbols) of account private key
const shieldedAccountPrivateKey = "0x..." as `0x${string}`;

export async function initializeShielderClient() {
  return createShielderClient({
    shielderSeedPrivateKey,
    chainId: BigInt(chain.id),
    // Note: cast publicClient to 'any' for compatibility
    publicClient: publicClient as any,
    contractAddress: shielderContractAddress,
    relayerUrl,
    storage: {
      getItem: async (key: string) => {
        return shielderStorage.get(key) || null;
      },
      setItem: async (key: string, value: string) => {
        shielderStorage.set(key, value);
      },
    },
    cryptoClient: await wasmCryptoClientRead,
    callbacks: {
      onAccountNotOnChain: async (
        error: unknown,
        stage: string,
        operation: ShielderOperation
      ) => {
        console.error("Account not on chain:", error, stage, operation);
      },
      onSdkOutdated: async (
        error: unknown,
        stage: string,
        operation: ShielderOperation
      ) => {
        console.error("SDK outdated:", error, stage, operation);
      },
    },
  });
}

6. Verify the Setup

In your main file (e.g. src/main.tsx or src/App.tsx):

import { initializeShielderClient } from "./shielder";
import { nativeToken } from "@cardinal-cryptography/shielder-sdk";

(async () => {
  // Initialize the SDK client
  const shielder = await initializeShielderClient();
  // Sync the account state from chain
  await shielder.syncShielder();
  
  // Get the native token representation
  const token = nativeToken();
  
  // Query your current account state (balance, nonce.)
  const accountState = await shielder.accountState(token);

  // Log the result to console (should be null at this point)
  console.log("Account:", accountState);
})();

At this point, your app is connected to the Shielder network, has synced the private account, and can query its state.\

Next steps:

Both operations require working with token approvals, gas fees, and relayer coordination. For a comprehensive understanding of all fee structures and costs involved, see Understanding Fees.

We recommend you handle those through user-facing components in your app UI.

Explore the guides to implement full privacy flow.

Last updated