> For the complete documentation index, see [llms.txt](https://docs.zama.org/protocol/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zama.org/protocol/sdk/changelog/v3/v3-3.md).

# 3.3.x

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.

```ts
// 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`](/protocol/sdk/guides/transfer-privately.md): 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.

{% hint style="warning" %}
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`](/protocol/sdk/guides/transfer-privately.md) for ordinary recipients.
{% endhint %}

### 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:

```ts
// On next page load, check for an interrupted unshield and offer to resume it.
const pending = await wrappedToken.getPendingUnshield();
if (pending) {
  await wrappedToken.resumeUnshield(pending);
}
```

`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`](/protocol/sdk/api-references/react/usependingunshield.md) and [`useResumeUnshield`](/protocol/sdk/api-references/react/useresumeunshield.md).

{% hint style="info" %}
Resume is intentionally caller-driven: surface a "resume withdrawal" prompt rather than finalizing automatically on load, so you never trigger a wallet transaction the user did not initiate. See [Unshield tokens](/protocol/sdk/guides/unshield-tokens.md#4-handle-interrupted-unshields) for the full flow.
{% endhint %}

### 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:

| Error               | Code               | Meaning                                                                        | Retry?                |
| ------------------- | ------------------ | ------------------------------------------------------------------------------ | --------------------- |
| `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`:

```ts
import { matchZamaError } from "@zama-fhe/sdk";

try {
  const balance = await wrappedToken.balanceOf(owner);
} catch (error) {
  matchZamaError(error, {
    NOT_ENTITLED: () => toast("You don't have access to decrypt this value yet"),
    RPC_RATE_LIMITED: () => retryWithBackoff(),
    _: () => toast("Decryption failed — please retry"),
  });
}
```

See [Handle errors](/protocol/sdk/guides/handle-errors.md) 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](/protocol/sdk/api-references/sdk/relayernode.md).
* **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](/protocol/sdk/guides/nextjs-ssr.md).
* **Delegation propagation absorbed internally.** Delegated decryption transparently retries across the short window while a new delegation propagates to the gateway, so first-attempt `delegatedUserDecrypt` calls no longer fail spuriously. See [Delegated decryption](/protocol/sdk/guides/delegated-decryption.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zama.org/protocol/sdk/changelog/v3/v3-3.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
