Permits
Permits are EIP-712 signatures that authorize decryption of confidential data. The issuer field identifies who is accessing the data - the issuer must have been granted access on-chain via FHE.allow(handle, address). When a permit is used, CoFHE validates it against the ACL contract to confirm that the issuer has access to the requested encrypted handle.
Each permit includes a sealing keypair. The public key is sent to CoFHE so it can re-encrypt the data for the permit holder. The private key stays client-side and is used to unseal the returned data. Permits are stored locally and identified by a deterministic hash of their fields.
Permits can be used to decrypt your own data (self permits), or to delegate your access to another party (sharing permits).
When do you need a permit?
decryptForView: always requires a permit.decryptForTx: depends on the contract's ACL policy for that handle (ctHashin code).- If the policy allows anyone to decrypt, you can use
.withoutPermit(). - If the policy restricts decryption, you must use
.withPermit(...).
- If the policy allows anyone to decrypt, you can use
Prerequisites
- Create and connect a client (see the client page).
Permits are scoped to a chainId + account. The SDK uses the connected chain and account by default.
Quickstart
The recommended approach is to create (or reuse) a self permit. Once created, it is stored and set as the active permit for the connected chain and account.
await client.connect(publicClient, walletClient);
// Returns the active self permit if one exists, otherwise creates and signs a new one.
const permit = await client.permits.getOrCreateSelfPermit();After this, the active permit is picked up automatically:
decryptForView(...).execute()uses the active permit.decryptForTx(...).withPermit().execute()uses the active permit.
Permit types
The SDK supports three permit types:
| Type | Who signs | Use case |
|---|---|---|
self | issuer only | Decrypt your own data (most common) |
sharing | issuer only | A shareable "offer" created by the issuer for a recipient |
recipient | recipient (and includes issuer signature) | The imported permit after the recipient signs it |
Notes:
- A permit includes a sealing keypair. The public key is sent to CoFHE for re-encryption. The private key stays client-side for unsealing.
- Permit
expirationis a unix timestamp in seconds. The default is 7 days from creation. - When a permit is created via
client.permits.*, it is automatically stored and set as the active permit for the current chain and account.
Creating a self permit
A self permit lets you decrypt data that was allowed to your address. Use createSelf to always create a new permit, or getOrCreateSelfPermit to reuse an existing active permit.
createSelf
await client.connect(publicClient, walletClient);
const permit = await client.permits.createSelf({
issuer: walletClient.account!.address,
name: 'My self permit',
});
permit.type;
permit.hash;getOrCreateSelfPermit
Only available via the client.permits API. Returns the active self permit if one exists. Otherwise creates and signs a new one. This is the recommended approach for most applications.
await client.connect(publicClient, walletClient);
const permit = await client.permits.getOrCreateSelfPermit();
permit.type;Sharing permits
Sharing permits let an issuer delegate their ACL access to a recipient. The recipient can then decrypt the issuer's data without needing their own FHE.allow. This flow may be useful for auditors or other parties that need to access data but do not have the ability to grant themselves access.
Issuer creates a sharing permit
The issuer creates a sharing permit specifying the recipient's address. The issuer's signature does not include a sealing key.
await client.connect(publicClient, walletClient);
const sharingPermit = await client.permits.createSharing({
issuer: walletClient.account!.address,
recipient,
name: 'Share with recipient',
});Issuer exports the permit
Export the permit as a JSON blob and share it with the recipient.
const exported = PermitUtils.export(sharingPermit);Recipient imports and signs
The recipient imports the exported JSON and signs it with their wallet. On import, a new sealing key is generated for the recipient. The recipient's sealing key is the one CoFHE uses for re-encryption. The resulting permit is stored and set as active.
await client.connect(publicClient, walletClient);
const recipientPermit = await client.permits.importShared(exported);
recipientPermit.type;
recipientPermit.hash;Active permit management
The SDK tracks all stored permits and an active permit hash per chainId + account. Creating or importing a permit via client.permits.* automatically stores it and selects it as active.
List stored permits
await client.connect(publicClient, walletClient);
const permits = client.permits.getPermits();
Object.keys(permits);Read / select the active permit
await client.connect(publicClient, walletClient);
const active = client.permits.getActivePermit();
active?.hash;
client.permits.selectActivePermit(somePermitHash);Removing permits
await client.connect(publicClient, walletClient);
client.permits.removePermit(permitHash);
client.permits.removeActivePermit();Persistence and security
- The SDK persists permits in a store keyed by
chainId + account. - In web and React environments, this store uses
localStorageunder the keycofhesdk-permits. - A stored permit includes the sealing private key. Treat it like a secret.
- Never share serialized permits with other users.
- To share access, use
PermitUtils.export(...)which strips sensitive fields.