diff --git a/client-sdk/guides/acps.mdx b/client-sdk/guides/acps.mdx
index 2ab2487..2cbdbcc 100644
--- a/client-sdk/guides/acps.mdx
+++ b/client-sdk/guides/acps.mdx
@@ -196,7 +196,7 @@ client.acp.removeACP(acpHash);
client.acp.removeActiveACP();
```
-## Validating {#validating-permits}
+## 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.
diff --git a/client-sdk/guides/client-setup.mdx b/client-sdk/guides/client-setup.mdx
index 29db76d..3e3bc14 100644
--- a/client-sdk/guides/client-setup.mdx
+++ b/client-sdk/guides/client-setup.mdx
@@ -27,7 +27,7 @@ const config = createCofheConfig({
supportedChains: [chains.sepolia],
// Optional knobs
- // defaultPermitExpiration: 60 * 60 * 24 * 30,
+ // defaultACPExpiration: 60 * 60 * 24 * 30,
// useWorkers: true,
});
```
@@ -170,7 +170,7 @@ cofheClient.connection.account; // Alice's address
To manually disconnect, call `cofheClient.disconnect()`. This clears the in-memory connection state (clients/account/chainId) and marks the client as disconnected.
-It does **not** delete persisted permits or stored FHE keys.
+It does **not** delete persisted ACPs or stored FHE keys.
```typescript
cofheClient.disconnect();
diff --git a/client-sdk/guides/decrypt-to-tx.mdx b/client-sdk/guides/decrypt-to-tx.mdx
index 87390ee..c7391e5 100644
--- a/client-sdk/guides/decrypt-to-tx.mdx
+++ b/client-sdk/guides/decrypt-to-tx.mdx
@@ -3,7 +3,7 @@ title: Decrypt to Transact
description: "Decrypt with a verifiable Threshold Network signature for onchain use"
---
-Use `decryptForTx` to reveal a confidential (encrypted) value onchain: it returns the plaintext together with a Threshold Network signature, so a contract can verify the reveal when you publish it in a transaction.
+Use `decryptForTx` to reveal a confidential (encrypted) value onchain. It returns the plaintext together with a Threshold Network signature, so a contract can verify the reveal when you publish it in a transaction.
Common use cases:
@@ -18,21 +18,21 @@ If you only need to show plaintext in your UI (and you do **not** need an onchai
1. [Create and connect a client](/client-sdk/guides/client-setup).
2. Know the onchain encrypted handle (`ctHash`) you want to decrypt.
-3. Determine whether the contract's ACL policy for this `ctHash` requires a [permit](/client-sdk/guides/permits).
+3. Determine whether the contract's ACL policy for this `ctHash` requires a [ACP](/client-sdk/guides/acps).
`decryptForTx` does not take a `utype`. It always returns the plaintext as a `bigint` because the result is intended to be passed into a transaction. If you need UI-friendly decoding, use [`decryptForView`](/client-sdk/guides/decrypt-to-view).
-## Permit: when is it needed?
+## ACP: when is it needed?
-Often, `decryptForTx` is used to reveal a value that the protocol already considers OK to make public. In those cases, the contract's ACL policy can allow anyone to decrypt, and you can use `.withoutPermit()`.
+Often, `decryptForTx` is used to reveal a value that the protocol already considers OK to make public. In those cases, the contract's ACL policy can allow anyone to decrypt, and you can use `.withoutACP()`.
-Examples where a permit is **not** needed:
+Examples where an ACP is **not** needed:
- **Unshielding**: the amount being unshielded is no longer meant to stay secret.
- **Auction/game reveal**: it doesn't matter who submits the reveal, only that the result is verified.
-If the ACL policy restricts decryption, you must use `.withPermit(...)`.
+If the ACL policy restricts decryption, you must use `.withACP(...)`.
## What `decryptForTx` returns
@@ -42,33 +42,33 @@ If the ACL policy restricts decryption, you must use `.withPermit(...)`.
- `decryptedValue: bigint`: the plaintext value (always a `bigint`)
- `signature: 0x${string}`: the Threshold Network signature as a hex string
-## Decrypt (choose permit mode)
+## Decrypt (choose ACP mode)
-```typescript No permit
+```typescript No ACP
const decryptResult = await client
.decryptForTx(ctHash)
- .withoutPermit()
+ .withoutACP()
.execute();
decryptResult.decryptedValue;
decryptResult.signature;
```
-```typescript Active permit
+```typescript Active ACP
const decryptResult = await client
.decryptForTx(ctHash)
- .withPermit()
+ .withACP()
.execute();
```
-```typescript Explicit permit
-const permit = await client.permits.getOrCreateSelfPermit();
+```typescript Explicit ACP
+const acp = await client.acp.getOrCreateSelfACP();
const decryptResult = await client
.decryptForTx(ctHash)
- .withPermit(permit)
+ .withACP(acp)
.execute();
```
@@ -82,23 +82,23 @@ After decrypting, see [Writing Decrypt Result to Contract](/client-sdk/guides/wr
Runs the decryption and returns `{ ctHash, decryptedValue, signature }`.
-### `.withPermit(...)` (required unless using `.withoutPermit()`)
+### `.withACP(...)` (required unless using `.withoutACP()`)
-- `.withPermit()`: uses the active permit
-- `.withPermit(permitHash)`: fetches a stored permit by hash
-- `.withPermit(permit)`: uses the provided permit object
+- `.withACP()`: uses the active ACP
+- `.withACP(acpHash)`: fetches a stored ACP by hash
+- `.withACP(acp)`: uses the provided ACP object
-### `.withoutPermit()` (required unless using `.withPermit(...)`)
+### `.withoutACP()` (required unless using `.withACP(...)`)
-Decrypt via global allowance (no permit). Only works if the contract's ACL policy allows anyone to decrypt that `ctHash`.
+Decrypt via global allowance (no ACP). Only works if the contract's ACL policy allows anyone to decrypt that `ctHash`.
### `.setAccount(address)` (optional)
-Overrides the account used to resolve the active/stored permit.
+Overrides the account used to resolve the active/stored ACP.
### `.setChainId(chainId)` (optional)
-Overrides the chain used to resolve the Threshold Network URL and permits.
+Overrides the chain used to resolve the Threshold Network URL and ACPs.
### `.onPoll(callback)` (optional)
@@ -107,7 +107,7 @@ Register a callback that fires once per poll attempt while `decryptForTx` waits
```typescript
const decryptResult = await client
.decryptForTx(ctHash)
- .withoutPermit()
+ .withoutACP()
.onPoll(({ operation, requestId, attemptIndex, elapsedMs, intervalMs, timeoutMs }) => {
console.log(`[${operation}] attempt ${attemptIndex} after ${elapsedMs}ms (next in ${intervalMs}ms, budget ${timeoutMs}ms)`);
})
@@ -132,7 +132,7 @@ Configures how long `decryptForTx` keeps retrying when the Threshold Network's s
```typescript
const decryptResult = await client
.decryptForTx(ctHash)
- .withoutPermit()
+ .withoutACP()
.set404RetryTimeout(20_000) // give a slower backend more time
.execute();
```
@@ -141,5 +141,5 @@ Pass `0` to disable submit-time retries (`404` becomes a hard failure). Submit r
## Common pitfalls
-- **Permit mode must be selected**: you must call exactly one of `.withPermit(...)` or `.withoutPermit()` before `.execute()`.
-- **Wrong chain/account**: permits are scoped to `chainId + account`. If you get an ACL/permit error, double-check the connected chain and account.
+- **ACP mode must be selected**: you must call exactly one of `.withACP(...)` or `.withoutACP()` before `.execute()`.
+- **Wrong chain/account**: ACPs are scoped to `chainId + account`. If you get an ACL/ACP error, double-check the connected chain and account.
diff --git a/client-sdk/guides/decrypt-to-view.mdx b/client-sdk/guides/decrypt-to-view.mdx
index 97097cf..dc109f0 100644
--- a/client-sdk/guides/decrypt-to-view.mdx
+++ b/client-sdk/guides/decrypt-to-view.mdx
@@ -1,6 +1,6 @@
---
title: Decrypt to View
-description: "Reveal encrypted values locally for UI display using permits"
+description: "Reveal encrypted values locally for UI display using ACPs"
---
Use `decryptForView` to reveal a confidential (encrypted) value locally in your app so you can display it in the UI.
@@ -10,18 +10,18 @@ Unlike [`decryptForTx`](/client-sdk/guides/decrypt-to-tx), this flow does **not*
## Flow
1. Read the encrypted handle (`ctHash`) from your contract.
-2. Ensure you have a permit that authorizes decryption of that value.
+2. Ensure you have an ACP that authorizes decryption of that value.
3. Call `decryptForView(ctHash, utype).execute()` to get the plaintext.
-`decryptForView` always decrypts using a permit (there is no `.withoutPermit()` mode). If your protocol intends for the plaintext to become publicly visible onchain, use [`decryptForTx`](/client-sdk/guides/decrypt-to-tx) instead.
+`decryptForView` always decrypts using an ACP (there is no `.withoutACP()` mode). If your protocol intends for the plaintext to become publicly visible onchain, use [`decryptForTx`](/client-sdk/guides/decrypt-to-tx) instead.
## 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`.
+3. Have a [ACP](/client-sdk/guides/acps) available for the connected `chainId + account`.
**Getting `ctHash`**: In most apps, `ctHash` comes from reading a stored encrypted value, an event arg, or a return value from a `view` call.
@@ -36,45 +36,45 @@ Supported `utype`s:
- `FheTypes.Uint8 | Uint16 | Uint32 | Uint64 | Uint128` to returns a `bigint`
-## Permit setup
+## ACP setup
-If you don't have a permit yet, create one once after connecting:
+If you don't have an ACP yet, create one once after connecting:
```typescript
await client.connect(publicClient, walletClient);
-// Creates a permit if needed, stores it, and selects it as the active permit.
-await client.permits.getOrCreateSelfPermit();
+// Creates an ACP if needed, stores it, and selects it as the active ACP.
+await client.acp.getOrCreateSelfACP();
```
## Decrypt for UI
-Choose the pattern that matches how your app manages permits:
+Choose the pattern that matches how your app manages ACPs:
-```typescript Active permit
+```typescript Active ACP
await client.connect(publicClient, walletClient);
-await client.permits.getOrCreateSelfPermit();
+await client.acp.getOrCreateSelfACP();
const plaintext = await client
.decryptForView(ctHash, FheTypes.Uint32)
.execute();
```
-```typescript Permit object
-const permit = await client.permits.getOrCreateSelfPermit();
+```typescript ACP object
+const acp = await client.acp.getOrCreateSelfACP();
const plaintext = await client
.decryptForView(ctHash, FheTypes.Uint64)
- .withPermit(permit)
+ .withACP(acp)
.execute();
```
-```typescript Permit hash
+```typescript ACP hash
const plaintext = await client
.decryptForView(ctHash, FheTypes.Uint8)
- .withPermit(permitHash)
+ .withACP(acpHash)
.execute();
```
@@ -94,23 +94,23 @@ Running `.execute()` resolves to a scalar JS value:
Runs the decryption and returns a UI-friendly scalar value.
-### `.withPermit(...)` (optional)
+### `.withACP(...)` (optional)
-Select which permit to use:
+Select which ACP to use:
-- `.withPermit()`: uses the active permit
-- `.withPermit(permitHash)`: fetches a stored permit by hash
-- `.withPermit(permit)`: uses the provided permit object
+- `.withACP()`: uses the active ACP
+- `.withACP(acpHash)`: fetches a stored ACP by hash
+- `.withACP(acp)`: uses the provided ACP object
-If you don't call `.withPermit(...)`, the active permit is used by default.
+If you don't call `.withACP(...)`, the active ACP is used by default.
### `.setAccount(address)` (optional)
-Overrides the account used to resolve the active/stored permit.
+Overrides the account used to resolve the active/stored ACP.
### `.setChainId(chainId)` (optional)
-Overrides the chain used to resolve the Threshold Network URL and permits.
+Overrides the chain used to resolve the Threshold Network URL and ACPs.
### `.onPoll(callback)` (optional)
@@ -174,6 +174,6 @@ const shortOwner = `${decryptedOwner.slice(0, 6)}…${decryptedOwner.slice(-4)}`
## Common pitfalls
-- **Missing permit**: `decryptForView` will fail if there is no active permit for the current `chainId + account`.
+- **Missing ACP**: `decryptForView` will fail if there is no active ACP 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.
+- **Wrong chain/account**: ACPs are scoped to `chainId + account`. If the user switches wallets or networks, create/select the correct ACP.
diff --git a/client-sdk/guides/encrypting-inputs.mdx b/client-sdk/guides/encrypting-inputs.mdx
index a69271c..82529af 100644
--- a/client-sdk/guides/encrypting-inputs.mdx
+++ b/client-sdk/guides/encrypting-inputs.mdx
@@ -1,14 +1,17 @@
---
title: Encrypting Inputs
-description: "Encrypt plaintext values with ZK proofs for use in FHE-enabled smart contracts"
+description: "Encrypt plaintext values with a batch ZK proof for use in FHE-enabled smart contracts"
---
-`encryptInputs` encrypts plaintext values into FHE ciphertexts that can be passed as inputs to a confidential smart contract transaction. Values must be encrypted before being passed onchain to preserve confidentiality.
+`encryptInputs` encrypts plaintext values into FHE ciphertexts you can pass into a confidential contract call. Values must be encrypted before they go onchain, or there is nothing confidential about them.
+
+One call produces one batch: a ciphertext handle for each value, plus a single signature covering the whole batch.
## Prerequisites
1. [Create and connect a client](/client-sdk/guides/client-setup).
-2. Know which encrypted type you want to encode each value as, the type must match the Solidity parameter type your contract expects (e.g. `InEuint32` vs `InEuint64`).
+2. Know which encrypted type each value needs. It must match the Solidity parameter your contract declares, for example `externalEuint32` against `externalEuint64`.
+3. Know the address of the contract that will consume the inputs. You have to declare it before signing.
## Basic usage
@@ -17,95 +20,113 @@ import { Encryptable } from '@cofhe/sdk';
await cofheClient.connect(publicClient, walletClient);
-const encrypted = await cofheClient
+const [ageHash, flagHash, addressHash, signature] = await cofheClient
.encryptInputs([
Encryptable.uint32(42n),
Encryptable.bool(true),
Encryptable.address('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
])
+ .setConsumingContract(contractAddress)
.execute();
-
-const [eUint32, eBool, eAddress] = encrypted;
```
-The return type is a typed tuple that mirrors the array you pass in, each element is the corresponding `EncryptedItemInput` type.
+The result holds one handle per input, in the order you passed them, followed by the batch signature. It has `inputs.length + 1` elements, so code that assumes the result matches the input count is off by one.
+
+## Declaring the consuming contract
-## Using encrypted inputs in a transaction
+`setConsumingContract` is required. The verifier binds the target contract into the signed digest, so a batch signed for one contract cannot be replayed into another.
-Pass the returned `EncryptedItemInput` objects directly into your contract call. The onchain CoFHE library verifies the signature before using the ciphertext.
+Omitting it is a compile error in TypeScript: `encryptInputs()` returns a builder with no `execute()` method, and you get `Property 'execute' does not exist on type 'EncryptInputsBuilderUnset'`. JavaScript callers get no type check and hit a `ConsumingContractUninitialized` throw instead.
+
+
+The consuming contract is the contract that runs `FHE.asEuint*`, which is not always the contract you call. If your app calls `vault.deposit(...)` and the vault is what converts the value, the consuming contract is the vault. Naming the wrong one compiles, typechecks, and reverts at runtime when the digest is recomputed onchain. Trace the value to the `FHE.asEuint*` call and use that address.
+
+
+## Using the result in a transaction
+
+The handle and its proof are a pair. In Solidity the proof parameter follows the handle it authenticates:
```solidity Solidity
-function confidentialTransfer(address to, InEuint64 amount) external {
+function confidentialTransfer(
+ address to,
+ externalEuint64 amount,
+ bytes calldata inputProof
+) external {
+ euint64 value = FHE.asEuint64(amount, inputProof);
// ...
}
```
```typescript TypeScript
-const [encryptedAmount] = await cofheClient
+const [amountHash, signature] = await cofheClient
.encryptInputs([Encryptable.uint64(amount)])
+ .setConsumingContract(tokenAddress)
.execute();
-await contract.confidentialTransfer(recipient, encryptedAmount);
+await contract.confidentialTransfer(recipient, amountHash, signature);
```
-## Builder API
-
-### `.execute()` (required, call last)
-
-Runs the encryption pipeline and returns the `EncryptedItemInput[]` tuple.
+For more than one encrypted value, the handles stay adjacent and share the one signature:
```typescript
-const [encryptedAge, encryptedFlag] = await cofheClient
- .encryptInputs([Encryptable.uint8(25n), Encryptable.bool(true)])
+const [amountHash, feeHash, signature] = await cofheClient
+ .encryptInputs([Encryptable.uint32(amount), Encryptable.uint32(fee)])
+ .setConsumingContract(tokenAddress)
.execute();
+
+await contract.transfer(recipient, [amountHash, feeHash], signature);
```
+
+The signature does not have to be the last parameter, and encrypted parameters must be adjacent to each other because they share one signature. See [migrating to 0.7](/client-sdk/introduction/migrating-to-0-7) for the Solidity side.
+
+
+## Builder API
+
+### `.setConsumingContract(address)` (required)
+
+The contract that will pass these values into `FHE.asEuint*`. Returns the builder that has `execute()`.
+
+### `.execute()` (required, call last)
+
+Runs the encryption pipeline and returns the handles followed by the batch signature.
+
### `.setAccount(address)` (optional)
-Override the address that "owns" the encrypted input. Only that address will be allowed to use the encrypted inputs onchain. Defaults to the connected wallet account.
+Override the address that owns the encrypted inputs. Only that address can use them onchain. Defaults to the connected wallet account.
```typescript
-const encrypted = await cofheClient
+const result = await cofheClient
.encryptInputs([Encryptable.uint64(10n)])
.setAccount('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
+ .setConsumingContract(contractAddress)
.execute();
```
### `.setChainId(chainId)` (optional)
-Override the chain the encrypted input will be used on. Defaults to the connected chain.
+Override the chain the inputs will be used on. Defaults to the connected chain.
-```typescript
-const encrypted = await cofheClient
- .encryptInputs([Encryptable.uint64(10n)])
- .setChainId(11155111)
- .execute();
-```
+### `.setSecurityZone(zone)` (optional)
-### `.setUseWorker(boolean)` (optional)
+Override the security zone the batch is encrypted under. Defaults to zone `0`.
-Override the `useWorkers` flag for this specific call. When `true` (the default), ZK proof generation runs in a Web Worker to avoid blocking the main thread. No-op in Node.js.
+### `.setUseWorker(boolean)` (optional)
-```typescript
-const encrypted = await cofheClient
- .encryptInputs([Encryptable.uint32(7n)])
- .setUseWorker(false)
- .execute();
-```
+When `true`, the default, ZK proof generation runs in a Web Worker so it does not block the main thread. No effect in Node.js.
### `.onStep(callback)` (optional)
-Register a callback that fires at the start and end of each encryption step. Useful for building progress indicators.
+Fires at the start and end of each encryption step, which is useful for a progress indicator.
```typescript
-import { EncryptStep } from '@cofhe/sdk';
-
-const encrypted = await cofheClient
+const result = await cofheClient
.encryptInputs([Encryptable.uint64(10n)])
+ .setConsumingContract(contractAddress)
.onStep((step, ctx) => {
if (ctx?.isStart) console.log(`Starting: ${step}`);
if (ctx?.isEnd) console.log(`Done: ${step} (${ctx.duration}ms)`);
@@ -113,33 +134,35 @@ const encrypted = await cofheClient
.execute();
```
+Setters can be called in any order, as long as they come before `.execute()`.
+
#### The encryption flow
-Calling `.execute()` runs five sequential steps:
+`.execute()` runs five sequential steps:
| Step | Description |
| --- | --- |
-| `InitTfhe` | Lazy-initializes the TFHE WASM module. A no-op after the first call. |
-| `FetchKeys` | Fetches (or loads from cache) the FHE public key and CRS for the target chain. |
+| `InitTfhe` | Lazy-initializes the TFHE WASM module. Does nothing after the first call. |
+| `FetchKeys` | Fetches, or loads from cache, the FHE public key and CRS for the target chain. |
| `Pack` | Packs the plaintext values into a ZK list ready for proving. |
-| `Prove` | Generates the ZK proof of knowledge (ZKPoK). Uses a Web Worker when available. |
-| `Verify` | Sends the proof to the CoFHE verifier. Returns signed `EncryptedItemInput` objects. |
+| `Prove` | Generates the ZK proof of knowledge. Uses a Web Worker when available. |
+| `Verify` | Sends the proof to the CoFHE verifier and returns the batch. |
-## Encryptable: creating inputs
+## Creating the inputs
-Use the `Encryptable` factory to create the items you want to encrypt. Each factory function accepts the plaintext value and an optional `securityZone`.
+Use the `Encryptable` factory to build the items. Each function takes the plaintext value and an optional security zone.
-| Factory | Data type | Solidity input param |
+| Factory | Data type | Solidity parameter |
| --- | --- | --- |
-| `Encryptable.bool(value)` | `boolean` | `InEbool` |
-| `Encryptable.uint8(value)` | `bigint \| string` | `InEuint8` |
-| `Encryptable.uint16(value)` | `bigint \| string` | `InEuint16` |
-| `Encryptable.uint32(value)` | `bigint \| string` | `InEuint32` |
-| `Encryptable.uint64(value)` | `bigint \| string` | `InEuint64` |
-| `Encryptable.uint128(value)` | `bigint \| string` | `InEuint128` |
-| `Encryptable.address(value)` | `bigint \| string` | `InEaddress` |
+| `Encryptable.bool(value)` | `boolean` | `externalEbool` |
+| `Encryptable.uint8(value)` | `bigint \| string` | `externalEuint8` |
+| `Encryptable.uint16(value)` | `bigint \| string` | `externalEuint16` |
+| `Encryptable.uint32(value)` | `bigint \| string` | `externalEuint32` |
+| `Encryptable.uint64(value)` | `bigint \| string` | `externalEuint64` |
+| `Encryptable.uint128(value)` | `bigint \| string` | `externalEuint128` |
+| `Encryptable.address(value)` | `bigint \| string` | `externalEaddress` |
-You can also use the generic form:
+There is also a generic form:
```typescript
Encryptable.create('uint32', 42n);
@@ -148,26 +171,24 @@ Encryptable.create('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045');
```
-**Bit limit:** A single `encryptInputs` call may encrypt at most **2048 bits** of plaintext in total. Exceeding this limit throws a `ZkPackFailed` error.
+A single `encryptInputs` call may encrypt at most **2048 bits** of plaintext in total. Going over throws `ZkPackFailed`.
-## EncryptedItemInput: the result type
+## What replaced the per-item types
-Each element of the returned array is an `EncryptedItemInput`:
+`0.7` removed the per-item input structs. `EncryptedItemInput`, `EncryptedUint64Input`, and the rest of that family no longer exist, and neither does `asHashPlusProof()`, because its output is what `execute()` always returns now.
-```typescript
-type EncryptedItemInput = {
- ctHash: bigint; // The ciphertext hash registered with CoFHE
- securityZone: number; // The security zone the input was encrypted under
- utype: FheTypes; // The FHE type (Bool, Uint8, …, Uint160)
- signature: string; // CoFHE verifier signature authorizing this input
-};
-```
+A value that used to be one of those types is now a plain hash, typed `` `0x${string}` ``. The whole result is ``readonly `0x${string}`[]``.
-Pass these directly into a contract function that accepts `InEuint*` structs. The contract's CoFHE library validates the signature onchain before operating on the ciphertext.
+
+These also break on your own helpers. A fixture typed `(encAmount: EncryptedUint64Input)` fails at its own definition, not at the call site. Spread the pair instead, or infer it with `Awaited>`.
+
## Common pitfalls
-- **Wrong `Encryptable` type**: `Encryptable.uint32(...)` must match what your Solidity function expects (e.g. `InEuint32`).
-- **Wrong account / chain**: encrypted inputs are authorized for a specific `account + chainId`. If you override these, your inputs may not be usable for the intended transaction.
-- **Bit limit exceeded**: a single call can encrypt at most **2048 bits** of plaintext. Exceeding this throws `ZkPackFailed`.
+- **Forgetting the consuming contract**, or naming the contract you call rather than the one that converts the value. The second one fails at runtime, not at compile time.
+- **Destructuring the wrong length.** The result carries a trailing signature, so a wrong-length destructure typechecks and then fails at runtime.
+- **Wrong `Encryptable` type.** `Encryptable.uint32(...)` has to match the `externalEuint32` your function declares.
+- **Wrong account or chain.** Inputs are authorized for one account and chain. Overriding either can make them unusable for the transaction you intended.
+- **Reusing one encryption against two contracts.** Not possible with a single batch, because the signature binds to one consuming contract. Encrypt once per target.
+- **Bit limit exceeded.** At most 2048 bits per call, otherwise `ZkPackFailed`.
diff --git a/client-sdk/guides/error-handling.mdx b/client-sdk/guides/error-handling.mdx
index ec20fe1..7f99625 100644
--- a/client-sdk/guides/error-handling.mdx
+++ b/client-sdk/guides/error-handling.mdx
@@ -22,12 +22,12 @@ try {
}
```
-## CofheError structure
+## What a CofheError carries
Every `CofheError` has:
-- `code` — a `CofheErrorCode` enum value identifying the error type
-- `message` — a human-readable description of what went wrong
+- `code`: a `CofheErrorCode` enum value identifying the error type
+- `message`: a human-readable description of what went wrong
Use `isCofheError(err)` to check if a caught error is a `CofheError`.
@@ -36,11 +36,20 @@ Use `isCofheError(err)` to check if a caught error is a `CofheError`.
| Error code | When it occurs |
| --- | --- |
| `ZkPackFailed` | `encryptInputs` exceeded the 2048-bit plaintext limit |
-| `PermitNotFound` | No permit found for the given `chainId + account` |
-| `PermitInvalid` | The permit signature is invalid or expired |
+| `ConsumingContractUninitialized` | `execute()` was called without `setConsumingContract(...)` |
+| `ACPNotFound` | No ACP found for the given `chainId + account` |
+| `ACPInvalid` | The ACP signature is invalid |
+| `ACPExpired` | The ACP is past its expiration |
+| `ACPRevoked` | The issuer revoked this ACP |
+| `ACPDenied` | The ACP does not cover the requested handle |
+| `ACPRequired` | The flow needs an ACP and none was supplied |
| `DecryptFailed` | Decryption request was rejected by the Threshold Network |
| `NotConnected` | Attempted an operation before calling `client.connect(...)` |
+
+The `Permit*` codes are gone. `PermitNotFound` and `PermitInvalid` are now `ACPNotFound` and `ACPInvalid`. The set also expanded: expiry, revocation, and scope denial each have their own code, so you no longer have to infer which one applied from the message.
+
+
## Error handling patterns
### Encryption errors
@@ -51,10 +60,11 @@ import { isCofheError, CofheErrorCode, Encryptable } from '@cofhe/sdk';
try {
const encrypted = await client
.encryptInputs([Encryptable.uint128(veryLargeValue)])
+ .setConsumingContract(contractAddress)
.execute();
} catch (err) {
if (isCofheError(err) && err.code === CofheErrorCode.ZkPackFailed) {
- console.error('Input too large — split into multiple calls');
+ console.error('Input too large, split into multiple calls');
}
}
```
@@ -69,9 +79,9 @@ try {
.decryptForView(ctHash, FheTypes.Uint32)
.execute();
} catch (err) {
- if (isCofheError(err) && err.code === CofheErrorCode.PermitNotFound) {
- // Create a permit and retry
- await client.permits.getOrCreateSelfPermit();
+ if (isCofheError(err) && err.code === CofheErrorCode.ACPNotFound) {
+ // Create an ACP and retry
+ await client.acp.getOrCreateSelfACP();
const plaintext = await client
.decryptForView(ctHash, FheTypes.Uint32)
.execute();
@@ -79,17 +89,17 @@ try {
}
```
-### Distinguishing why a permit is invalid
+### Distinguishing why an ACP is invalid
-Since `@cofhe/sdk@0.5.0`, the decrypt flows call `PermitUtils.validate(permit)` internally before talking to the Threshold Network. That helper enforces **schema + signed + not-expired** all at once, so when it fails the recovery path depends on **which** check tripped.
+The decrypt flows call `ACPUtils.validate(acp)` internally before talking to the Threshold Network. That helper enforces **schema + signed + not-expired** all at once, so when it fails the recovery path depends on **which** check tripped.
-Use the non-throwing `ValidationUtils.isValid` helper from `@cofhe/sdk/permits` to pre-flight the active permit and route based on the typed reason — this avoids the thrown error path entirely:
+Use the non-throwing `ValidationUtils.isValid` helper from `@cofhe/sdk/acps` to pre-flight the active ACP and route based on the typed reason, which avoids the thrown error path entirely:
```typescript
import { FheTypes } from '@cofhe/sdk';
-import { ValidationUtils } from '@cofhe/sdk/permits';
+import { ValidationUtils } from '@cofhe/sdk/acps';
-const active = client.permits.getActivePermit();
+const active = client.acp.getActiveACP();
const result = active
? ValidationUtils.isValid(active)
: { valid: false, error: 'not-signed' as const };
@@ -97,13 +107,13 @@ const result = active
if (!result.valid) {
switch (result.error) {
case 'expired':
- await client.permits.getOrCreateSelfPermit(); // create a fresh one
+ await client.acp.getOrCreateSelfACP(); // create a fresh one
break;
case 'not-signed':
- await client.permits.getOrCreateSelfPermit(); // prompt the wallet to sign
+ await client.acp.getOrCreateSelfACP(); // prompt the wallet to sign
break;
case 'invalid-schema':
- client.permits.removeActivePermit(); // stored payload is malformed
+ client.acp.removeActiveACP(); // stored payload is malformed
break;
}
}
@@ -113,8 +123,8 @@ const plaintext = await client
.execute();
```
-`ValidationResult.error` is the typed union `'invalid-schema' | 'expired' | 'not-signed' | null` — see [Permits → Validating permits](/client-sdk/guides/permits#validating-permits) for the full helper surface.
+`ValidationResult.error` is the typed union `'invalid-schema' | 'expired' | 'not-signed' | null`. See [validating ACPs](/client-sdk/guides/acps#validating) for the full helper surface.
-If you prefer the throwing path: `PermitUtils.validate(permit)` raises plain `Error`s with messages `Permit is expired` / `Permit is not signed` (or a Zod schema error). These are not wrapped in `CofheError`, so use `err.message` rather than an error code to branch.
+If you prefer the throwing path: `ACPUtils.validate(acp)` raises plain `Error`s with messages `ACP is expired` / `ACP is not signed` (or a Zod schema error). These are not wrapped in `CofheError`, so use `err.message` rather than an error code to branch.
diff --git a/client-sdk/guides/writing-encrypted-data.mdx b/client-sdk/guides/writing-encrypted-data.mdx
index 2c496f1..15a0d0b 100644
--- a/client-sdk/guides/writing-encrypted-data.mdx
+++ b/client-sdk/guides/writing-encrypted-data.mdx
@@ -3,26 +3,26 @@ title: Writing Encrypted Data to Contract
description: "Encrypt plaintext values and pass them directly into a contract call"
---
-This page covers the "encrypt to write tx" flow: encrypt plaintext values into `InE*` structs and pass them directly into a contract call.
+This page covers the "encrypt to write tx" flow: encrypt plaintext values and pass them straight into a contract call.
-`encryptInputs` returns `EncryptedItemInput` objects that match the Solidity `InE*` input structs. The onchain CoFHE library validates the verifier signature before the contract can use the ciphertext.
+`encryptInputs` returns one ciphertext handle per value, followed by a single signature covering the batch. Each handle is an `externalEuintXX`, and the signature is the `bytes` proof that follows it in the function signature. The onchain CoFHE library verifies the proof before the contract can use the ciphertext.
## Flow
-1. Ensure your contract function accepts encrypted `InE*` parameters.
-2. Encrypt the plaintext values with [`encryptInputs`](/client-sdk/guides/encrypting-inputs).
-3. Send a transaction and pass the encrypted structs as the `InE*` arguments.
+1. Ensure your contract function accepts an `externalEuintXX` handle plus a `bytes` proof.
+2. Encrypt the plaintext values with [`encryptInputs`](/client-sdk/guides/encrypting-inputs), naming the contract that will consume them.
+3. Send a transaction, passing the handle and the proof.
## Prerequisites
1. [Create and connect a client](/client-sdk/guides/client-setup).
-2. Your contract function must accept encrypted `InE*` structs.
+2. Your contract function must accept `externalEuintXX` parameters with a `bytes` proof.
The encrypted type you choose in TypeScript must match the Solidity parameter type:
-- `Encryptable.uint32(...)` to `InEuint32`
-- `Encryptable.bool(...)` to `InEbool`
-- `Encryptable.address(...)` to `InEaddress`
+- `Encryptable.uint32(...)` to `externalEuint32`
+- `Encryptable.bool(...)` to `externalEbool`
+- `Encryptable.address(...)` to `externalEaddress`
## Example: encrypt and call a contract
@@ -37,8 +37,8 @@ import '@fhenixprotocol/cofhe-contracts/FHE.sol';
contract EncryptedCounter {
euint32 public count;
- function setCount(InEuint32 memory _inCount) external {
- count = FHE.asEuint32(_inCount);
+ function setCount(externalEuint32 inCount, bytes calldata inputProof) external {
+ count = FHE.asEuint32(inCount, inputProof);
FHE.allowThis(count);
FHE.allowSender(count);
}
@@ -46,28 +46,29 @@ contract EncryptedCounter {
```
```typescript TypeScript (viem)
-import { Encryptable, assertCorrectEncryptedItemInput } from '@cofhe/sdk';
+import { Encryptable } from '@cofhe/sdk';
import { parseAbi } from 'viem';
import { sepolia } from 'viem/chains';
+// externalEuint32 is a bytes32 value type on the wire
const encryptedCounterAbi = parseAbi([
- 'function setCount((uint256 ctHash, uint8 securityZone, uint8 utype, bytes signature) _inCount)',
+ 'function setCount(bytes32 inCount, bytes inputProof)',
]);
// 1) Encrypt right before sending the transaction
-const [inCount] = await cofheClient
+const [countHash, signature] = await cofheClient
.encryptInputs([Encryptable.uint32(42n)])
+ .setConsumingContract(encryptedCounterAddress)
.execute();
-assertCorrectEncryptedItemInput(inCount);
-// 2) Pass the encrypted struct as the InE* argument
+// 2) Pass the handle and its proof
const hash = await walletClient.writeContract({
chain: sepolia,
account,
address: encryptedCounterAddress,
abi: encryptedCounterAbi,
functionName: 'setCount',
- args: [inCount],
+ args: [countHash, signature],
});
await publicClient.waitForTransactionReceipt({ hash });
@@ -77,12 +78,13 @@ await publicClient.waitForTransactionReceipt({ hash });
import { Encryptable } from '@cofhe/sdk';
// 1) Encrypt
-const [inCount] = await cofheClient
+const [countHash, signature] = await cofheClient
.encryptInputs([Encryptable.uint32(42n)])
+ .setConsumingContract(encryptedCounterAddress)
.execute();
// 2) Send the transaction
-const tx = await contract.setCount(inCount);
+const tx = await contract.setCount(countHash, signature);
await tx.wait();
```
@@ -90,6 +92,7 @@ await tx.wait();
## Common pitfalls
-- **Wrong `Encryptable` type**: the `Encryptable.*` factory must match the Solidity parameter type (`InEuint32` vs `InEuint64`, etc).
+- **Wrong `Encryptable` type**: the factory must match the Solidity parameter, `externalEuint32` against `externalEuint64`.
- **Wrong account / chain**: encrypted inputs are authorized for a specific `account + chainId`. If you encrypt under the wrong wallet/network, the contract call may revert.
-- **ABI struct shape mismatch**: if you hand-write an ABI, ensure the tuple fields are `(ctHash, securityZone, utype, signature)` in the same order your client library expects.
+- **Stale ABI shape**: the old `(ctHash, securityZone, utype, signature)` tuple is gone. A hand-written ABI takes `bytes32` for the handle and `bytes` for the proof.
+- **Missing or wrong consuming contract**: name the contract that runs `FHE.asEuint*`. Getting it wrong compiles and reverts at runtime.