Recipe · Operations

Reading encrypted state end-to-end

Two hooks, two phases. The on-chain mutation grants ACL; the reactive decrypt pulls plaintext. Treat them as one flow, the second only fires when the first settles.

Why two hooks#

On-chain reads of encrypted values aren't free queries. FHEVM requires the contract to call FHE.allow(handle, msg.sender)to grant the caller ACL on the result, that's a tx. So the SDK exposes encrypted-view reads as useMutationhooks. Once the handle is in your hand, decrypting is a relayer round trip, a separate concern.

1. Grant yourself ACL#

components/Vested.tsx
tsx
// 1. Submit the encrypted-view tx. Contract grants ACL to msg.sender.
const getVested = useGetVestedAmount({ address: managerAddress });

getVested.mutate({
  vestingId,
  timestamp: Math.floor(Date.now() / 1000),
});

// getVested.data → { handle: "0x...", hash: "0x..." } when settled.

2. Decrypt the handle#

components/Vested.tsx
tsx
// 2. Reactively decrypt the handle via the relayer.
const decrypted = useDecryptedHandle({
  handle: getVested.data?.handle,
  enabled: !!getVested.data?.handle,
});

// decrypted.data is the plaintext bigint once the relayer returns.
if (decrypted.isPending) return <span>Decrypting…</span>;
return <span>{decrypted.data?.toString()} ARI vested</span>;

What goes wrong#

Decrypt fails with "Not authorized."The wallet signing the relayer request isn't the one that received the ACL grant in step 1. Verify you're using the same wallet across both phases.

Decrypt returns last tick's value. useGetVestedAmount takes a timestamp arg, pass the current second to force a fresh on-chain compute.

See also