3.3.x
confidentialTransferAndCall, automatic pending-unshield recovery, and typed decryption error causes.
This page covers the 3.3.x line.
3.3.0
Released 2026-07-08.
This release adds a one-transaction "transfer and notify" primitive for ERC-7984 tokens, moves interrupted-unshield recovery fully inside the SDK, and gives decryption failures typed causes you can branch on. There are no breaking changes — every 3.1.x/3.2.x app upgrades cleanly.
Encrypted transfer with a receiver hook
Token.confidentialTransferAndCall() performs an encrypted transfer and invokes the recipient's ERC-7984 receiver hook (onConfidentialTransferReceived) in a single transaction. Use it when the recipient is a contract that needs to react to the transfer — for example a vault that credits a deposit the moment tokens arrive.
The amount is encrypted client-side for you. The data argument is an opaque payload the SDK forwards verbatim to the receiver hook — you encode and interpret it; the SDK never touches it.
// Transfer 1000 tokens to a vault contract and trigger its deposit hook in one tx.
const { txHash } = await token.confidentialTransferAndCall(
"0xVaultContract",
1000n,
"0xabcd", // caller-encoded payload forwarded to onConfidentialTransferReceived
);Balance validation, chain-alignment checks, and error handling match confidentialTransfer: the SDK reads your confidential balance first and throws InsufficientConfidentialBalanceError before sending anything if it is too low. Pass { skipBalanceCheck: true } to bypass the check for wallets that cannot produce EIP-712 signatures.
The recipient must implement the ERC-7984 receiver hook. Sending to a plain wallet address or a contract without the hook will revert with TransactionRevertedError. Use plain confidentialTransfer for ordinary recipients.
Automatic pending-unshield recovery
Unshielding is a two-phase flow — an unwrap transaction, then a finalize transaction after the decryption proof arrives. If the user closes the tab in between, the first transaction is on-chain but the withdrawal is unfinished.
The SDK now persists the pending unwrap automatically when phase one is submitted and clears it once finalization confirms. You no longer wire up any storage helpers yourself — you only detect and resume on the next load:
getPendingUnshield() returns the unwrap transaction hash of an interrupted unshield, or null. resumeUnshield() polls for the proof, submits the finalize transaction, and clears the persisted state on success.
In React, the same lifecycle is exposed through usePendingUnshield and useResumeUnshield.
Typed decryption error causes
Decryption can fail for reasons that call for very different responses — some are terminal, others are worth retrying. The SDK now distinguishes them with dedicated error subclasses so you can branch correctly instead of parsing messages:
NotEntitledError
NOT_ENTITLED
The account is not authorized by the on-chain ACL to decrypt this value
No — wait for a grant
RpcRateLimitError
RPC_RATE_LIMITED
Your RPC provider rate-limited an on-chain read (HTTP 429 / JSON-RPC -32005)
Yes
Relayer back-pressure surfaces the same way — as a typed, retryable cause rather than an opaque failure. Route them with matchZamaError:
See Handle errors for the full error hierarchy.
Reliability improvements
Self-healing Node worker timeouts. The Node relayer's worker timeouts are now configurable and diagnosable, and the pool recovers on its own from a stalled worker instead of wedging. See the
node()transport reference.SSR-safe worker resolution. The Node worker resolves without
import.meta.resolve, so server-side bundlers (Next.js and similar) load it correctly. See the Next.js SSR guide.Delegation propagation absorbed internally. Delegated decryption transparently retries across the short window while a new delegation propagates to the gateway, so first-attempt
delegatedUserDecryptcalls no longer fail spuriously. See Delegated decryption.
Last updated