> 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/guides/delegated-decryption.md).

# Delegated decryption

Grant another address the right to decrypt confidential balances, then read those balances as a delegate.

Delegation lets one address grant another address the right to decrypt its confidential balances. The delegate never receives the delegator's private keys — they use their own transport key pair and a delegated EIP-712 flow to prove they have permission.

Common use cases:

* **Portfolio dashboards** — a read-only service decrypts balances across wallets without holding keys.
* **Auditors** — a third party verifies holdings without the token owner being online.

This guide uses `sdk.delegations` and `token.decryptBalanceAs` in the core SDK, or the `useDelegateDecryption` and `useDecryptBalanceAs` hooks in React. Before starting, make sure your project is set up following the [Configuration](/protocol/sdk/guides/configuration.md) guide.

## Example

A complete delegation flow — grant, then decrypt as delegate (the SDK rides out ACL propagation for you):

{% tabs %}
{% tab title="Core SDK" %}

```ts
import { createConfig, ZamaSDK } from "@zama-fhe/sdk";
import { sepolia } from "@zama-fhe/sdk/chains";

const sdk = new ZamaSDK(config); // config from createConfig()
const token = sdk.createToken("0xConfidentialToken");

// 1. Delegator grants decryption rights
const { txHash } = await sdk.delegations.delegateDecryption({
  contractAddress: token.address,
  delegateAddress: "0xDelegate",
});

// 2. Delegate reads the delegator's balance — no wait needed. Propagation
//    usually completes within ~10 blocks (a few seconds), and the SDK retries
//    across that window internally.
const balance = await token.decryptBalanceAs({ delegatorAddress: "0xDelegator" });
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { useDelegateDecryption, useDecryptBalanceAs } from "@zama-fhe/react-sdk";

const TOKEN = "0xConfidentialToken";

// 1. Delegator grants decryption rights
const { mutateAsync: delegate } = useDelegateDecryption(TOKEN);
await delegate({ delegateAddress: "0xDelegate" });

// 2. Delegate reads the delegator's balance — the SDK rides out ACL propagation
const { mutateAsync: decryptAs } = useDecryptBalanceAs(TOKEN);
const balance = await decryptAs({ delegatorAddress: "0xDelegator" });
```

{% endtab %}
{% endtabs %}

## Steps

### 1. Grant delegation

The token owner grants a delegate the right to decrypt their balance for a specific contract. Each call grants delegation for a single `(contractAddress, delegateAddress)` pair and submits one on-chain transaction, returning `{ txHash, receipt }`.

{% tabs %}
{% tab title="Core SDK" %}

```ts
// Permanent delegation (no expiration)
await sdk.delegations.delegateDecryption({
  contractAddress: token.address,
  delegateAddress: "0xDelegate",
});

// Delegation with an expiration date
await sdk.delegations.delegateDecryption({
  contractAddress: token.address,
  delegateAddress: "0xDelegate",
  expirationDate: new Date("2027-12-31T00:00:00Z"),
});
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { useDelegateDecryption } from "@zama-fhe/react-sdk";

const { mutateAsync: delegate } = useDelegateDecryption("0xConfidentialToken");

// Permanent delegation (no expiration)
await delegate({ delegateAddress: "0xDelegate" });

// Delegation with an expiration date
await delegate({ delegateAddress: "0xDelegate", expirationDate: new Date("2027-12-31T00:00:00Z") });
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
The expiration date must be **at least 1 hour in the future**. Passing a closer date throws `DelegationExpirationTooSoonError` before the transaction is sent.
{% endhint %}

### 2. ACL propagation (handled for you)

After the delegation transaction is mined, the Zama Gateway (on Arbitrum) syncs the ACL state via cross-chain event propagation — usually within \~10 blocks (a few seconds). You don't need to wait or poll: the delegated-decrypt path rides out that window with a bounded internal retry (\~30s), so a decrypt issued right after granting simply waits for sync.

{% hint style="info" %}
`DelegationNotPropagatedError` only surfaces if propagation outlasts the retry budget (rare) — or if you opt out of the wait with `waitForPropagation: false` on `sdk.decryption.delegatedDecryptValues` to fail fast instead.
{% endhint %}

### 3. Decrypt as delegate

The delegate reads the delegator's balance. The delegate signs with their own wallet, and the relayer verifies the on-chain delegation before decrypting.

{% tabs %}
{% tab title="Core SDK" %}

```ts
const balance = await token.decryptBalanceAs({ delegatorAddress: "0xDelegator" });

// When the balance holder differs from the delegator, pass accountAddress explicitly:
const other = await token.decryptBalanceAs({
  delegatorAddress: "0xDelegator",
  accountAddress: "0xBalanceHolder",
});
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { useDecryptBalanceAs } from "@zama-fhe/react-sdk";

const { mutateAsync: decryptAs, data: balance } = useDecryptBalanceAs("0xConfidentialToken");

await decryptAs({ delegatorAddress: "0xDelegator" });

// When the balance holder differs from the delegator, pass accountAddress explicitly:
await decryptAs({ delegatorAddress: "0xDelegator", accountAddress: "0xBalanceHolder" });
```

{% endtab %}
{% endtabs %}

Clear values are cached in storage, keyed by `(accountAddress, token, encryptedValue)`. Every on-chain balance change produces a new encrypted value, so stale cache entries are never served.

### 4. Check delegation status (optional)

Query whether a delegation is currently active between a delegator and a delegate, along with its expiry:

{% tabs %}
{% tab title="Core SDK" %}

```ts
const { isActive, expiryTimestamp } = await sdk.delegations.getStatus({
  contractAddress: token.address,
  delegatorAddress: "0xDelegator",
  delegateAddress: "0xDelegate",
});
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { useDelegationStatus } from "@zama-fhe/react-sdk";

const { data } = useDelegationStatus({
  contractAddress: "0xConfidentialToken",
  delegatorAddress: "0xDelegator",
  delegateAddress: "0xDelegate",
});

// data?.isActive, data?.expiryTimestamp
```

{% endtab %}
{% endtabs %}

### 5. Batch decryption across tokens (optional)

Decrypt balances across multiple tokens in a single call. The result is a `Map<Address, bigint>`.

{% tabs %}
{% tab title="Core SDK" %}

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

const tokens = addresses.map((a) => sdk.createToken(a));

// Without `onError`, a single failing token rejects the whole call and discards the
// map. Pass `onError` for a partial result: it's called once per failed token and its
// return value becomes that token's entry. `maxConcurrency` caps parallel decryptions.
const balances = await Token.batchDecryptBalancesAs(tokens, {
  delegatorAddress: "0xDelegator",
  maxConcurrency: 3,
  onError: (err, addr) => {
    console.error(addr, err);
    return 0n;
  },
});

for (const [address, balance] of balances) {
  console.log(`${address}: ${balance}`);
}
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { useMemo } from "react";
import { useBatchDecryptBalancesAs, useZamaSDK } from "@zama-fhe/react-sdk";

// Build Token instances with the SDK factory, not `useToken` — hooks can't be called in a loop.
const sdk = useZamaSDK();
const tokens = useMemo(() => addresses.map((a) => sdk.createToken(a)), [sdk, addresses]);

const { mutateAsync: batchDecryptAs } = useBatchDecryptBalancesAs(tokens);

try {
  const balances = await batchDecryptAs({ delegatorAddress: "0xDelegator" });
  // balances is a Map<Address, bigint>. Any single token failing rejects the whole call.
} catch (err) {
  console.error(err);
}
```

{% endtab %}
{% endtabs %}

### 6. Revoke delegation (optional)

{% tabs %}
{% tab title="Core SDK" %}

```ts
await sdk.delegations.revokeDelegation({
  contractAddress: token.address,
  delegateAddress: "0xDelegate",
});
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { useRevokeDelegation } from "@zama-fhe/react-sdk";

const { mutateAsync: revoke } = useRevokeDelegation("0xConfidentialToken");

await revoke({ delegateAddress: "0xDelegate" });
```

{% endtab %}
{% endtabs %}

### 7. Handle errors (optional)

Delegation operations can throw several error types. The most common:

{% tabs %}
{% tab title="Core SDK" %}

```ts
import {
  DelegationNotPropagatedError,
  DelegationExpirationTooSoonError,
  SigningRejectedError,
  DecryptionFailedError,
  TransactionRevertedError,
} from "@zama-fhe/sdk";

try {
  await sdk.delegations.delegateDecryption({
    contractAddress: token.address,
    delegateAddress: "0xDelegate",
  });
} catch (error) {
  if (error instanceof DelegationExpirationTooSoonError) {
    // expiration date is less than 1 hour in the future
  } else if (error instanceof TransactionRevertedError) {
    // on-chain transaction failed
  }
}

try {
  const balance = await token.decryptBalanceAs({ delegatorAddress: "0xDelegator" });
} catch (error) {
  if (error instanceof SigningRejectedError) {
    // user cancelled the wallet prompt — do not retry automatically
  } else if (error instanceof DelegationNotPropagatedError) {
    // delegation still hadn't synced after the SDK's internal retry — rare; retry shortly
  } else if (error instanceof DecryptionFailedError) {
    // delegated decryption failed
  }
}
```

{% endtab %}

{% tab title="React SDK" %}

```tsx
import { DelegationNotPropagatedError, SigningRejectedError } from "@zama-fhe/sdk";
import { useDecryptBalanceAs } from "@zama-fhe/react-sdk";

const { mutateAsync: decryptAs, error } = useDecryptBalanceAs("0xConfidentialToken");

// The mutation's `error` is a ZamaError subclass — narrow it with `instanceof`:
if (error instanceof SigningRejectedError) {
  // user cancelled the wallet prompt — do not retry automatically
} else if (error instanceof DelegationNotPropagatedError) {
  // delegation still hadn't synced after the SDK's internal retry — rare; retry shortly
}
```

{% endtab %}
{% endtabs %}

See [Handle errors](/protocol/sdk/guides/handle-errors.md) for full error-handling patterns and [Error types](/protocol/sdk/api-references/sdk/errors.md) for the complete list.

## Next steps

* [Delegations reference](/protocol/sdk/api-references/sdk/delegation.md) — full `Delegations` namespace API
* [useDelegateDecryption](/protocol/sdk/api-references/react/usedelegatedecryption.md) — React hook to grant delegation
* [useDecryptBalanceAs](/protocol/sdk/api-references/react/usedecryptbalanceas.md) — React hook to decrypt as a delegate
* [useDelegationStatus](/protocol/sdk/api-references/react/usedelegationstatus.md) — React hook to query delegation status


---

# 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/guides/delegated-decryption.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.
