Action Extensions
Viem action extensions that add ZKP capabilities to clients.
zkpPublicActions
On-chain read operations. Works with any PublicClient.
import { zkpPublicActions } from '@cardinal-cryptography/core'
client.extend(zkpPublicActions({ tokens?: TokenMap }))
| Method | Parameters | Returns | Description |
|---|---|---|---|
getEncryptedBalanceCiphertext | { token, zkpAddress } | ElGamalCiphertext | Read encrypted balance |
getAutoEncryptZkpAddress | { token, address } | ZkpAddress | null | Reverse lookup: EVM address to ZK address |
getQuorumPublicKey | { token } | GrumpkinPublicKey | Quorum encryption public key |
getPermitNonce | { token, owner } | bigint | Next permit nonce for controller auth |
getAutoEncrypt | { token, address } | boolean | Is auto-encrypt enabled? |
getEncryptedBalanceLogs | { token, zkpAddress, fromBlock, toBlock?, events? } | EncryptedBalanceLog[] | Raw event history. fromBlock is required; caller paginates with successive [fromBlock, toBlock] windows to stay under hosted-RPC block-range caps. |
getTokenName | { token } | string | Token contract name (for EIP-712 domain) |
zkpEvmActions
EVM write transactions. Requires WalletClient or BundlerLike.
import { zkpEvmActions } from '@cardinal-cryptography/core'
client.extend(zkpEvmActions({ tokens?: TokenMap, account?: LocalAccount }))
account is required on the bundler path (and ignored on the WalletClient
path, where viem's client.account plays the same role). It's used to sign
EIP-712 permits for publicToEncryptedTransferWithAuth.
| Method | Parameters | Returns | Description |
|---|---|---|---|
preparePublicToEncryptedTransfer | { token, amount, zkpAddress } | PreparedTransaction | Prepare public-to-encrypted transfer (plain variant, WalletClient path) |
preparePublicToEncryptedTransferWithAuth | { token, amount, zkpAddress, owner, deadline, nonce? } | UnsignedTransaction | Prepare permit variant — returned shape needs signing before submission |
prepareSetAutoEncrypt | { token, enabled, zkpAddress } | PreparedTransaction | Prepare auto-encrypt initial bind (sets the EPK and the enabled flag). Reverts on-chain if the address has already bound an EPK. |
prepareToggleAutoEncrypt | { token, enabled } | PreparedTransaction | Prepare auto-encrypt on/off flip for an already-bound address. Does not change the bound EPK. |
sendPublicToEncryptedTransfer | { token, amount, zkpAddress, account?, nonce?, deadline? } | 0x${string} | Auto-routes: plain variant on WalletClient, WithAuth on bundler (prepare → sign → send under the hood) |
sendSetAutoEncrypt | { token, enabled, zkpAddress, account?, nonce?, deadline? } | 0x${string} | Auto-routes: plain variant on WalletClient, WithAuth on bundler. Initial bind only — reverts on-chain if the address has already bound an EPK. account?, nonce?, deadline? apply to the bundler path only. |
sendToggleAutoEncrypt | { token, enabled, account?, nonce?, deadline? } | 0x${string} | Auto-routes: plain variant on WalletClient, WithAuth on bundler. Flips the enabled flag for an already-bound address; on the bundler path the SDK reads the stored EPK from chain to construct the digest. |
signAuthorization | UnsignedTransaction | PreparedTransaction | Sign an EIP-712 permit with the bound account. Named distinctly from zkpWalletActions.signTransaction so both can coexist on a full client. |
toSignedTransaction | (UnsignedTransaction, HexSignature) | PreparedTransaction | Assemble a PreparedTransaction from an externally-produced signature (custody, HSM, etc.) |
Permit prepare/sign/submit flow
publicToEncryptedTransferWithAuth takes an EIP-712 permit as its last argument. Use preparePublicToEncryptedTransferWithAuth (returns UnsignedTransaction) + signAuthorization + sendPreparedTransaction. For external signers (custody, Ledger, Fireblocks), use toSignedTransaction to wrap an externally-produced signature.
See Transaction Lifecycle — EVM transactions with an EIP-712 permit for the three-step code example.
erc3009Actions
EIP-3009 transferWithAuthorization — plain ERC-20 transfers via a recipient-bound off-chain authorization. The signed digest commits to to, so anyone (the SharedAccount bundler in practice) may forward the call without being able to redirect funds. Bundler-only — on a WalletClient call transfer directly.
import { erc3009Actions } from '@cardinal-cryptography/core'
client.extend(erc3009Actions({ tokens?: TokenMap, account?: LocalAccount }))
account is the EOA whose tokens are transferred — also the EIP-712 signer (from = account.address). Required for sendTransferWithAuthorization; prepare* and reads don't need it.
| Method | Parameters | Returns | Description |
|---|---|---|---|
prepareTransferWithAuthorization | { token, from, to, value, validAfter?, validBefore?, nonce? } | UnsignedTransaction | Build EIP-712 typed data + calldata. Caller signs typedData and assembles a PreparedTransaction via signTransaction / toSignedTransaction. |
sendTransferWithAuthorization | { token, to, value, validAfter?, validBefore?, nonce? } | 0x${string} | prepare → sign with the bound account → submit via the bundler. |
getAuthorizationState | { token, authorizer, nonce } | boolean | Has (authorizer, nonce) been consumed (used or canceled)? Mirrors EIP-3009's authorizationState view. |
Defaults: validAfter = 0n, validBefore = 20 min ahead, nonce is a fresh random bytes32.
zkpDecryptActions
Decrypt balances and history. Requires a client with zkpPublicActions methods.
import { zkpDecryptActions } from '@cardinal-cryptography/core'
client.extend(zkpDecryptActions({ zkpAccount: ZkpReadAccount }))
| Method | Parameters | Returns | Description |
|---|---|---|---|
getDecryptedBalance | { token, zkpAccount? } | bigint | Read + decrypt balance |
getDecryptedBalanceLogs | { token, fromBlock, toBlock?, events?, zkpAccount? } | DecryptedBalanceLog[] | Decrypt history. fromBlock is required (forwarded to getEncryptedBalanceLogs). |
The optional zkpAccount parameter overrides the account from config (useful for multi-account views).
zkpWalletActions
Prepare, sign, and send encrypted transfers. Requires a client with zkpPublicActions + zkpPreparedActions methods.
import { zkpWalletActions } from '@cardinal-cryptography/core'
client.extend(zkpWalletActions({ zkpAccount: ZkpAccount }))
| Method | Parameters | Returns | Description |
|---|---|---|---|
prepareEncryptedTransfer | { token, amount, to, deadline?, nonce?, onProgress?, zkpAccount? } | UnsignedTransaction | Prepare private transfer |
prepareEncryptedToPublicTransfer | { token, amount, recipient, deadline?, nonce?, onProgress?, zkpAccount? } | UnsignedTransaction | Prepare encrypted-to-public transfer |
sendEncryptedTransfer | Same as prepare | 0x${string} | Full flow: prepare + sign + send |
sendEncryptedToPublicTransfer | Same as prepare | 0x${string} | Full flow: prepare + sign + send |
prepareRegisterEpk | { token, controller, onProgress?, zkpAccount? } | PreparedTransaction | Bind a controller EVM address to this account's EPK on the token contract. Required first transaction for every new ZKP account. |
sendRegisterEpk | Same as prepare | 0x${string} | Full flow: prove + submit |
signTransaction | UnsignedTransaction | PreparedTransaction | Sign with the controller key (ZKP actions). Permit signing lives under zkpEvmActions.signAuthorization. |
getDecryptedBalance | { token, zkpAccount? } | bigint | Inherited from decrypt actions |
getDecryptedBalanceLogs | { token, ... } | DecryptedBalanceLog[] | Inherited from decrypt actions |
zkpPreparedActions
Submit pre-signed transactions. Handles routing between WalletClient and BundlerClient.
import { zkpPreparedActions } from '@cardinal-cryptography/core'
client.extend(zkpPreparedActions())
| Method | Parameters | Returns | Description |
|---|---|---|---|
sendPreparedTransaction | PreparedTransaction | 0x${string} | Submit a signed transaction |