Actions & Extensions
The SDK uses viem's .extend() pattern to add ZKP capabilities to standard viem clients.
What's an action extension?
A function that takes a config and returns a viem extension -- a function that adds methods to a client:
import { createPublicClient, http } from 'viem'
import { zkpPublicActions } from '@cardinal-cryptography/core'
const client = createPublicClient({ chain, transport: http() })
.extend(zkpPublicActions())
// Now client has ZKP read methods
const ct = await client.getEncryptedBalanceCiphertext({ token: 'zkUSD', zkpAddress: '...' })
The five extensions
Read actions don't need keys -- anyone can query the chain:
| Extension | What it adds |
|---|---|
zkpPublicActions({ tokens? }) | On-chain reads: encrypted balances, event logs, quorum keys, nonces |
Write actions need a wallet to sign EVM transactions:
| Extension | What it adds |
|---|---|
zkpEvmActions({ tokens?, account? }) | EVM writes: register ZK address, send to encrypted address, auto-encrypt. account is required on the bundler path (signs EIP-712 permits); the WalletClient path uses client.account implicitly. |
zkpPreparedActions() | Submit pre-signed transactions (routes to wallet or bundler automatically) |
Decrypt and prove actions need your keys to process encrypted data locally:
| Extension | What it adds |
|---|---|
zkpDecryptActions({ zkpAccount }) | Decrypt balance + history using your encryption key |
zkpWalletActions({ zkpAccount }) | Prepare/sign/send encrypted transfers (needs full account) |
You won't use all five directly -- the client factories pick the right combination for you.
Extension ordering
Extensions that depend on methods from other extensions must come later in the chain:
client
.extend(zkpPublicActions()) // 1. reads (no dependencies)
.extend(zkpEvmActions()) // 2. EVM writes (no dependencies)
.extend(zkpPreparedActions()) // 3. sendPreparedTransaction
.extend(zkpDecryptActions(...)) // 4. needs public read methods from step 1
.extend(zkpWalletActions(...)) // 5. needs reads from 1 + sendPrepared from 3
The client factories (createEvmClient, createDecryptClient, createFullClient) handle this ordering for you.
Custom token configuration
By default, token names like 'zkUSD' resolve to built-in contract addresses per chain. Override with custom addresses:
const client = createEvmClient({
chain,
transport: http(),
account,
tokens: { zkUSD: '0xMyCustomTokenAddress...' },
})
The tokens config is forwarded to zkpPublicActions and zkpEvmActions.