> 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/confidential-vault/guides/read-balances.md).

# Read Confidential Balances

Read encrypted balance handles and decrypt them client-side with a permit the SDK manages for you.

Every confidential value — a token balance, a pending batch position, a claimed amount — lives on-chain as an opaque 32-byte **handle**. Displaying one takes two steps: read the handle with an ordinary `eth_call`, then decrypt it through the Zama relayer with the user's authorization. The SDK manages the authorization; your job is to scope it correctly and to keep wallet prompts rare.

## Read the handle

`confidentialBalanceOf` returns `bytes32`:

```ts
import { parseAbi } from "viem";

const BALANCE_ABI = parseAbi([
  "function confidentialBalanceOf(address account) view returns (bytes32)",
]);

const handle = await publicClient.readContract({
  address: CSHARE,
  abi: BALANCE_ABI,
  functionName: "confidentialBalanceOf",
  args: [user],
});
```

{% hint style="warning" %}
**Filter the zero handle** An account that never received the token returns `0x00…00`. The relayer errors on it. Treat the zero handle as "balance 0" and never submit it for decryption.

```ts
import { zeroHash } from "viem";

if (handle === zeroHash) return 0n;
```

{% endhint %}

The same pattern reads a pending batch position: `deposits(batchId, account)` on a batcher returns the user's encrypted position handle, and the batcher grants the user decryption permission on it automatically at join time.

## Decrypt with the SDK-managed permit

`sdk.decryption.decryptValues` turns handles into bigints. On first use it prompts the user for one EIP-712 signature — a **permit** authorizing a transport keypair to decrypt handles on the listed contracts — then caches the permit and keypair in the configured storage:

```ts
const clear = await sdk.decryption.decryptValues([
  { encryptedValue: shareBalanceHandle, contractAddress: CSHARE },
  { encryptedValue: pendingDepositHandle, contractAddress: DEPOSIT_BATCHER },
]);

const shareBalance = clear[shareBalanceHandle];
```

Behind the scenes, the relayer re-encrypts each ciphertext to the user's transport key and the SDK decrypts it locally. There is no separate "re-encryption" step for you to implement, and no gas — the permit is an off-chain signature.

Two rules govern the request:

* **`contractAddress` is the contract that owns the handle** — the token for a balance, the **batcher** for a batch position or a `Joined` event handle. Decryption permission is granted per contract: a permit covering your token catalog will not open handles the batcher granted. Scope the permit to every contract whose handles you display.
* **One request, many handles.** A single permit covers any number of handles across the listed contracts. Collect everything your UI needs and decrypt in one call.

## Manage the permit session

The permit and the transport keypair live for 30 days, persisted in the storage you configured. What to build around that:

* **Use `indexedDBStorage` in browsers.** With `memoryStorage` the user re-signs on every page load.
* **Ask before prompting.** With `@zama-fhe/react-sdk`, check `useHasPermit({ contractAddresses })` and offer an explicit "reveal balances" action that runs `useGrantPermit`; hold decryption queries until the permit exists.
* **Keep the permit's contract set stable.** Checksum-normalize and deduplicate addresses so the same set never re-prompts.
* **Handle expiry as an error state.** A permit or keypair past its TTL surfaces as `TransportKeyPairExpired` — map it to "authorization expired, sign again".

## Who can decrypt what

FHE access control is explicit and narrow:

* A user is automatically permitted to decrypt their own balance handles, batch positions, and claim amounts — the contracts grant this as part of each operation.
* A contract allowed on a handle can **use** it in FHE computations, never decrypt it. No integration step you perform can leak a user's plaintext to a third party.
* Permissions are permanent once granted.

A batch's decrypted **aggregate**, and the burnt amount of an unwrap, are *publicly* decryptable by design — `sdk.decryption.decryptPublicValues` reads them with no signature at all.

## Next steps

* [Deposit into a Vault](/protocol/confidential-vault/guides/deposit.md) — where the position handles come from.
* [Track Batch State](/protocol/confidential-vault/guides/track-batches.md) — pair decrypted positions with batch 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/confidential-vault/guides/read-balances.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.
