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
:
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
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:
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:
To shield tokens (i.e. deposit), see: Shielding Tokens
To withdraw tokens back to a public address, see: Withdrawing Tokens
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?