> ## Documentation Index
> Fetch the complete documentation index at: https://cofhe-docs.fhenix.zone/llms.txt
> Use this file to discover all available pages before exploring further.

# Decrypt to View

> Reveal encrypted values locally for UI display using permits

Use `decryptForView` to reveal a confidential (encrypted) value locally in your app so you can display it in the UI.

Unlike [`decryptForTx`](/client-sdk/guides/decrypt-to-tx), this flow does **not** return an on-chain-verifiable signature, and it is **not** meant to be published on-chain.

## Flow

1. Read the encrypted handle (`ctHash`) from your contract.
2. Ensure you have a permit that authorizes decryption of that value.
3. Call `decryptForView(ctHash, utype).execute()` to get the plaintext.

<Note>
  `decryptForView` always decrypts using a permit (there is no `.withoutPermit()` mode). If your protocol intends for the plaintext to become publicly visible on-chain, use [`decryptForTx`](/client-sdk/guides/decrypt-to-tx) instead.
</Note>

## Prerequisites

1. [Create and connect a client](/client-sdk/guides/client-setup).
2. Know the encrypted handle (`ctHash`) and the encrypted type (`utype`).
3. Have a [permit](/client-sdk/guides/permits) available for the connected `chainId + account`.

<Tip>
  **Getting `ctHash`**: In most apps, `ctHash` comes from reading a stored encrypted value, an event arg, or a return value from a `view` call.
</Tip>

<Tip>
  **Providing `utype`**: `utype` must match the ciphertext's underlying FHE type. The SDK uses it to convert the decrypted `bigint` into a convenient JS type.

  Supported `utype`s:

  * `FheTypes.Bool` → returns a `boolean`
  * `FheTypes.Uint160` (address) → returns a checksummed `0x...` string
  * `FheTypes.Uint8 | Uint16 | Uint32 | Uint64 | Uint128` → returns a `bigint`
</Tip>

## Permit setup

If you don't have a permit yet, create one once after connecting:

```typescript theme={null}
await client.connect(publicClient, walletClient);

// Creates a permit if needed, stores it, and selects it as the active permit.
await client.permits.getOrCreateSelfPermit();
```

## Decrypt for UI

Choose the pattern that matches how your app manages permits:

<CodeGroup>
  ```typescript Active permit theme={null}
  await client.connect(publicClient, walletClient);
  await client.permits.getOrCreateSelfPermit();

  const plaintext = await client
    .decryptForView(ctHash, FheTypes.Uint32)
    .execute();
  ```

  ```typescript Permit object theme={null}
  const permit = await client.permits.getOrCreateSelfPermit();

  const plaintext = await client
    .decryptForView(ctHash, FheTypes.Uint64)
    .withPermit(permit)
    .execute();
  ```

  ```typescript Permit hash theme={null}
  const plaintext = await client
    .decryptForView(ctHash, FheTypes.Uint8)
    .withPermit(permitHash)
    .execute();
  ```
</CodeGroup>

## What `decryptForView` returns

Running `.execute()` resolves to a scalar JS value:

* Integer utypes (`Uint8`, `Uint16`, `Uint32`, `Uint64`, `Uint128`): a `bigint`
* `FheTypes.Bool`: a `boolean`
* `FheTypes.Uint160` (address): a checksummed `0x...` address string

## Builder API

### `.execute()` — required, call last

Runs the decryption and returns a UI-friendly scalar value.

### `.withPermit(...)` — optional

Select which permit to use:

* `.withPermit()` — uses the active permit
* `.withPermit(permitHash)` — fetches a stored permit by hash
* `.withPermit(permit)` — uses the provided permit object

If you don't call `.withPermit(...)`, the active permit is used by default.

### `.setAccount(address)` — optional

Overrides the account used to resolve the active/stored permit.

### `.setChainId(chainId)` — optional

Overrides the chain used to resolve the Threshold Network URL and permits.

### `.onPoll(callback)` — optional

Register a callback that fires once per poll attempt while `decryptForView` waits for the Threshold Network to return the sealed plaintext. Useful for surfacing decrypt progress in a UI.

```typescript theme={null}
const plaintext = await client
  .decryptForView(ctHash, FheTypes.Uint64)
  .onPoll(({ operation, requestId, attemptIndex, elapsedMs, intervalMs, timeoutMs }) => {
    console.log(`[${operation}] attempt ${attemptIndex} after ${elapsedMs}ms (next in ${intervalMs}ms, budget ${timeoutMs}ms)`);
  })
  .execute();
```

The callback receives:

| Field          | Type                        | Description                                                                                                                      |
| -------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `operation`    | `'decrypt' \| 'sealoutput'` | Which Threshold Network flow is polling. For `decryptForView` this is `'sealoutput'`.                                            |
| `requestId`    | `string`                    | The Threshold Network request id. **May be the empty string** during submit-time retries — see `.set404RetryTimeout(...)` below. |
| `attemptIndex` | `number`                    | Zero-based poll attempt counter.                                                                                                 |
| `elapsedMs`    | `number`                    | Time since the first submit attempt.                                                                                             |
| `intervalMs`   | `number`                    | Delay until the next poll.                                                                                                       |
| `timeoutMs`    | `number`                    | Overall budget shared by submit-retries and status-polling.                                                                      |

### `.set404RetryTimeout(timeoutMs)` — optional

Configures how long `decryptForView` keeps retrying when the Threshold Network's submit endpoint responds with `404 Not Found` before a `requestId` is available. This typically happens on slower backends where the ciphertext isn't visible yet at submit time. Defaults to `10_000` ms.

```typescript theme={null}
const plaintext = await client
  .decryptForView(ctHash, FheTypes.Uint32)
  .set404RetryTimeout(20_000) // give a slower backend more time
  .execute();
```

Pass `0` to disable submit-time retries (`404` becomes a hard failure). Submit retries share the same overall timeout budget as status polling.

## Common UI patterns

<CodeGroup>
  ```typescript Format bigint for display theme={null}
  import { formatUnits } from 'viem';

  const decimals = 6;
  const display = formatUnits(amount, decimals);
  ```

  ```typescript Bigint → number (range-check) theme={null}
  const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER);
  const asNumber = amount <= MAX_SAFE ? Number(amount) : undefined;
  ```

  ```typescript Booleans & addresses theme={null}
  const statusLabel = decryptedIsAllowed ? 'Allowed' : 'Not allowed';
  const shortOwner = `${decryptedOwner.slice(0, 6)}…${decryptedOwner.slice(-4)}`;
  ```
</CodeGroup>

## Common pitfalls

* **Missing permit**: `decryptForView` will fail if there is no active permit for the current `chainId + account`.
* **Wrong `utype`**: you must pass the correct FHE type for the ciphertext.
* **Wrong chain/account**: permits are scoped to `chainId + account`. If the user switches wallets or networks, create/select the correct permit.
