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";

function crossOriginIsolationMiddleware(_, res, next) {
  res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
  res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
  next();
}

const setCors = () => ({
  name: "configure-server",
  configureServer: (server) => {
    server.middlewares.use(crossOriginIsolationMiddleware);
  },
  configurePreviewServer: (server) => {
    server.middlewares.use(crossOriginIsolationMiddleware);
  },
});

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

4. Set Up WASM Cryptography Client

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

src/shielderWasm.ts
import newAccountParamsUrl from "@cardinal-cryptography/shielder-sdk-crypto-wasm/keys/new_account/params.bin?url";
import newAccountPkUrl from "@cardinal-cryptography/shielder-sdk-crypto-wasm/keys/new_account/pk.bin?url";
import depositParamsUrl from "@cardinal-cryptography/shielder-sdk-crypto-wasm/keys/deposit/params.bin?url";
import depositPkUrl from "@cardinal-cryptography/shielder-sdk-crypto-wasm/keys/deposit/pk.bin?url";
import withdrawParamsUrl from "@cardinal-cryptography/shielder-sdk-crypto-wasm/keys/withdraw/params.bin?url";
import withdrawPkUrl from "@cardinal-cryptography/shielder-sdk-crypto-wasm/keys/withdraw/pk.bin?url";
import { initWasmWorker } from "@cardinal-cryptography/shielder-sdk-crypto-wasm";

// Helper function to fetch proving/verifying keys
async function fetchArrayBuffer(url: string): Promise<Uint8Array> {
  return fetch(url)
    .then((r) => r.arrayBuffer())
    .then((b) => new Uint8Array(b));
}

// Lazy-loaded WASM crypto client
export const wasmCryptoClientRead = (async () => {
  const newAccountParams = await fetchArrayBuffer(newAccountParamsUrl);
  const newAccountPk = await fetchArrayBuffer(newAccountPkUrl);
  const depositParams = await fetchArrayBuffer(depositParamsUrl);
  const depositPk = await fetchArrayBuffer(depositPkUrl);
  const withdrawParams = await fetchArrayBuffer(withdrawParamsUrl);
  const withdrawPk = await fetchArrayBuffer(withdrawPkUrl);
  return initWasmWorker(
    navigator.hardwareConcurrency,
    {
      paramsBuf: newAccountParams,
      pkBuf: newAccountPk,
    },
    {
      paramsBuf: depositParams,
      pkBuf: depositPk,
    },
    {
      paramsBuf: withdrawParams,
      pkBuf: withdrawPk,
    }
  );
})();

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://shielder-relayer-v2.test.azero.dev/base-testnet";

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.

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

Explore the guides to implement full privacy flow.

Last updated

Was this helpful?