The primitive#
The FHEVM coprocessor maintains a list of (handle, address) tuples. When the SDK asks the relayer to decrypt a handle, the relayer checks: does this caller's signature recover an address that's in the handle's ACL list? If yes, return plaintext. If no, refuse.
The contract grants ACL with FHE.allow(handle, addr) — a single tx call that the SDK wraps inside hooks like useDiscloseToParty, useDiscloseHandleToParty, and (for batches) useBatchDiscloseHandlesToParty.
The append-only rule#
Once granted, an ACL entry can't be revoked. There is no "undo" primitive in FHEVM. If you grant Mira access to her vested amount, Mira can decrypt that handle forever — even if she leaves the company. Vesting positions get split or transferred to rotate the handle in practice, but the original handle remains decryptable by the original recipient.
Granting from your code#
import { useDiscloseToParty } from "@tokenops/sdk/fhe-vesting/react";
function DiscloseToAuditor({ vestingId }) {
const disclose = useDiscloseToParty({ address: managerAddress });
return (
<button
onClick={() =>
disclose.mutate({
vestingId,
party: "0xAuditor...",
encryptor: () => zamaSDK.relayer,
})
}
>
Grant auditor read access
</button>
);
}useDiscloseToParty grants ACL on the full bundle (vested + claimable + settled + total allocation) for a single vesting. For one-off granular disclosure, use useDiscloseHandleToParty with a specific handle.
Operators#
ERC-7984 has a sibling primitive called operators — addresses authorised to call transferFromConfidential on an account's behalf. Operators are separate from ACL: they're an ERC-7984 token primitive, not an FHEVM one. The SDK wraps both — check setOperator, useApproveTokenOnWallets (disperse), and the disperse story's chapter 1 walkthrough.