issuer field identifies who is reading the data, and that address must already have been granted access onchain with FHE.allow(handle, address). When you use an ACP, CoFHE validates it against the ACL contract to confirm the issuer really holds that access.
Every ACP carries a sealing keypair. The public key goes to CoFHE so it can re-encrypt the result for the holder. The private key stays on the client and unseals the value when it comes back.
ACPs were called permits before
0.7. The name changed so they are not confused with an ERC-2612 permit, which is a different thing entirely. If you are upgrading, see migrating to 0.7. This page uses “ACP” throughout.When you need one
decryptForViewalways requires an ACP.decryptForTxdepends on the contract’s ACL policy for that handle. If the policy lets anyone decrypt, use.withoutACP(). If it restricts decryption, use.withACP(...).
Prerequisites
Create and connect a client. ACPs are scoped to a chainId and account pair.Quick start
client.acp is the recommended API. It signs with the connected wallet and manages the store for you. ACPUtils is the lower-level alternative when you want direct control over signing and storage. Note the namespace is singular: client.acp, not client.acps.decryptForView(...).execute() and by decryptForTx(...).withACP().execute().
The three types
expiration is a unix timestamp in seconds and defaults to 7 days from creation. The client-wide defaultACPExpiration config key is a separate setting that defaults to 30 days. Creating an ACP through client.acp.* stores it and makes it active.
Creating a self ACP
A self ACP lets you decrypt data that was allowed to your own address.createSelf always creates a new one. getOrCreateSelfACP() reuses the active ACP when there is one, which is what most applications want.
Narrowing what an ACP can read
0.7 adds a scope to every ACP. An unscoped ACP covers everything the issuer can read, which is rarely what you want to hand to someone else.
Sharing with another account
An issuer can delegate their ACL access to a recipient, who can then decrypt the issuer’s data without holding their ownFHE.allow grant. There are two routes: pass the offer yourself, or post it onchain.
Passing the offer yourself
1
Issuer creates a sharing ACP
2
Issuer exports it
3
Recipient imports and signs
Sharing onchain
The issuer can instead post the signed offer to a registry, so the recipient discovers it without a side channel.dismissShare(shareId) clears an entry the recipient does not want, and cancelShare withdraws one the issuer posted.
Revoking access
An issuer can revoke an ACP they created, which matters when a sharing ACP has left their control.Managing stored ACPs
The SDK keeps every stored ACP and one active ACP hash per chainId and account.Validating
ACPUtils.validate enforces the full check: schema, signed, and not expired. The decrypt flows call it for you and surface failures as typed errors, so validate manually only when you want to inspect or filter ACPs first.
Use
validateSchema on an ACP that arrived over the wire, when you want to reject a malformed payload before caring about expiry.
For inspection without exception handling, ValidationUtils returns a typed result:
Persistence and security
- ACPs are stored per chainId and account. On the web the store is
localStorageunder the keycofhesdk-acps. - A stored ACP contains the sealing private key. Treat it as a secret, and never hand a serialized ACP to another user.
- To share access, use
ACPUtils.export, which strips the sensitive fields.