Client SDK for Zul — a Solana-RPC-compatible privacy Layer 2. One isomorphic (browser + Node) package over three surfaces:
- chain —
connect(), thefield/poseidon/merkleprimitives, program ids - bridge — SOL (Solana devnet L1) ↔ ZUL (L2):
depositIx,withdrawIx,claimIxs,getWithdrawalProof,findBatchForRoot - shielded — the privacy pool:
buildShield/buildTransfer/buildUnshield(Groth16), the scanning walletsyncWallet,deriveSpendingKey, and note selection
npm i @zuldotso/zul-sdkimport { Connection, Transaction } from "@solana/web3.js";
import { depositIx, DEVNET_RPC } from "@zuldotso/zul-sdk";
const conn = new Connection(DEVNET_RPC, "confirmed");
const tx = new Transaction().add(depositIx(wallet.publicKey, 20_000_000n, wallet.publicKey));
// sign with the wallet + send to devnet; the bridge watcher credits the L2.import { Connection } from "@solana/web3.js";
import {
ZUL_RPC, NATIVE_ASSET_ID, deriveSpendingKey, ownerPk, encryptionKeypair,
buildShield, buildUnshield, syncWallet, selectForUnshield, poolIx,
unshieldArtifacts,
} from "@zuldotso/zul-sdk";
const conn = new Connection(ZUL_RPC, "confirmed");
const s = deriveSpendingKey(await wallet.signMessage(MSG)); // recoverable identity
const enc = encryptionKeypair(s);
// shield 5 ZUL
const sh = await buildShield({
asset: { kind: "native" }, assetId: NATIVE_ASSET_ID, amount: 5_000_000_000n,
recipientPk: await ownerPk(s), recipientEncPk: enc.publicKey,
});
// submit poolIx(wallet.publicKey, sh.data) on the L2…
// later: scan + unshield 3 ZUL to a public address
const w = await syncWallet(conn, s);
const note = selectForUnshield(w.store.unspent(NATIVE_ASSET_ID), 3_000_000_000n);
const un = await buildUnshield({
spendingKey: s, asset: { kind: "native" }, assetId: NATIVE_ASSET_ID,
input: note!, amount: 3_000_000_000n, recipient: recipientPubkey.toBytes(),
changePk: await ownerPk(s), changeEncPk: enc.publicKey,
tree: w.tree, artifacts: unshieldArtifacts,
});- Proving fetches the hosted artifacts (
transferArtifacts/unshieldArtifactsURLs) in the browser, or pass{ wasm, zkey }asUint8Arrayin Node. - Browser bundlers must stub Node built-ins for snarkjs (
fs/os/crypto→false) and, if importing TS sources directly, map.js→.ts. Seezulweb/next.config.tsfor a Next.js example. - The shielded wallet keeps no state —
syncWalletre-derives every owned note from chain history each call (the pool account stores only the tree frontier). - Defaults target the live devnet deployment (
rpc.zul.so, settlement2hwZWD…); pass your ownConnectionto point elsewhere.