Skip to main content

SDK Structure

The SDK is a TypeScript library that exposes the encrypted-balance protocol as viem action extensions. It owns key-derived account types, ZK proof generation, ElGamal decryption, and ERC-4337 submission. Persistent storage, key management UX, and account discovery are the application's responsibility.

Architecture decisions: ADR-016, ADR-017.

Layers

Runtime (decryption + proving services; WASM-backed; long-lived)

Account (keys in closures: decrypt, prove, sign)

Client (viem PublicClient or BundlerClient + ZKP action extensions)

Chain (EVM JSON-RPC + ERC-4337 bundler)

One runtime per process; accounts derive from it; clients compose via factories or manual .extend() chains.

Action extensions

The public surface — each one a plain viem extension:

ExtensionRequired clientHoistsPurpose
zkpPublicActionsPublicClientChain reads — ciphertexts, registration state, balance logs
zkpEvmActionsWalletClient or BundlerLikeEVM-side writes by EOA — sendPublicToEncryptedTransfer, sendSetAutoEncrypt, signPermit
zkpPreparedActionsWalletClient or BundlerLikeSubmit pre-built PreparedTransactions
zkpDecryptActionszkpPublicActionsZkpReadAccountDecrypt balances and history
zkpWalletActionszkpPublicActions + zkpPreparedActionsZkpAccountOrchestrated sendEncryptedTransfer, sendEncryptedToPublicTransfer, sendRegisterEpk
erc3009Actionsbundleroptional accountEIP-3009 transferWithAuthorization for plain ERC-20

TypeScript enforces composition order at compile time. ERC-20 reads and publicTransfer are not wrapped — viem handles them natively.

Account

Three capability interfaces split so that decrypt / prove / sign can be granted independently — e.g. a proving service holds the ESK while custody (Fireblocks, Safe, etc.) holds the CSK and signs (ADR-014).

interface ZkpReadAccount { zkpAddress; decrypt(ct) }
interface ZkpProvingAccount extends ZkpReadAccount { prove(req) }
interface ZkpSigningAccount { controllerAddress; signControllerAuth(typed) }
interface ZkpAccount extends ZkpProvingAccount, ZkpSigningAccount {}

Runtime

A runtime composes a solver (and optionally a prover) with account factories. Two variants:

  • ReadRuntime holds a Solver and exposes createReadAccount, createReadAccountFromMnemonic, createReadAccountFromSeed.
  • ZkpRuntime extends ReadRuntime additionally holds a Prover and exposes createAccount, createAccountFromMnemonic, createAccountFromSeed, prewarmProver.

One per process. Pick ReadRuntime or ZkpRuntime at startup; you don't upgrade between them. Both expose destroy() to release WASM threads at shutdown.

@cardinal-cryptography/sdk exposes createReadRuntime and createRuntime. @cardinal-cryptography/core ships assembleRuntime(solver, prover) and assembleReadRuntime(solver) for tests and custom backends.

Convenience factories

FactoryBacked byAccount bindingUse
createDecryptClientPublicClientZkpReadAccountRead + decrypt; no submission
createEvmClientBundlerClientLocalAccount (EOA permit signer)Public-to-encrypted transfer, auto-encrypt toggle
createFullClientBundlerClientZkpAccountFull encrypted-balance flows

Custom token addresses and bespoke compositions drop down to manual .extend() chains.

Transaction submission

Default routing is paymaster → ERC-4337 bundler → classic eth_sendTransaction, picked by capability detection on the client. Through the bundler, msg.sender is the SharedAccount contract; security comes from the ZK proof and EIP-712 controller signature in calldata, not from tx.origin. The user holds no ETH, and the controller key never appears on-chain as the gas-payer.

The classic WalletClient path bypasses the bundler — msg.sender is the user's EOA and the unlinkability above does not hold. No convenience factory; assemble manually.

See Relayer for the wire protocol with the paymaster backend.

Packages and platform targets

@cardinal-cryptography/sdk ships a single npm package with conditional exports resolving per platform — Node and browser today, React Native when added.

ConditionEntrySolverProver
nodeindex.node.jsDlogSolver (Node WASM build)NoirProver (NoirJS + bb.js, worker pool)
browser / defaultindex.browser.jsDlogSolver (browser WASM, multi-threaded if crossOriginIsolated)NoirProver (NoirJS + bb.js)

@cardinal-cryptography/core holds the platform-agnostic surface (extensions, account types, runtime interfaces, client factories, EIP-712 builders, sponsor middleware) and depends only on viem; @cardinal-cryptography/sdk re-exports it alongside the WASM-backed backends.