For the complete documentation index, see llms.txt. This page is also available as Markdown.

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. Before starting, make sure your project is set up following the Configuration guide.

Example

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

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" });

Steps

1. Grant delegation

The token owner calls sdk.delegations.delegateDecryption to allow a delegate to decrypt their balance for a specific contract.

Both calls return { txHash, receipt }.

Each call grants delegation for a single (contractAddress, delegateAddress) pair and submits one on-chain transaction.

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.

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.

3. Decrypt as delegate

The delegate calls token.decryptBalanceAs to read the delegator's balance. The delegate signs with their own wallet, and the relayer verifies the on-chain delegation before decrypting.

When the balance holder differs from the delegator, pass accountAddress explicitly:

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. Batch decryption across tokens (optional)

Decrypt balances across multiple tokens in a single call:

Handle errors for individual tokens with onError:

5. Revoke delegation (optional)

6. Handle errors (optional)

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

See Handle errors for full error-handling patterns and Error types for the complete list.

Next steps

Last updated