> ## Documentation Index
> Fetch the complete documentation index at: https://cofhe-docs.fhenix.zone/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating to 0.7

> Upgrade from 0.6 to 0.7.1: batch inputs, ACPs, and the new encrypted types

The `0.7` release renames permits to ACPs, replaces per-ciphertext input signatures with one signature per batch, and binds every encrypted input to the contract that will consume it. It also moves you to `@fhenixprotocol/cofhe-contracts` `0.2.0`, which deletes the `InEuintXX` structs. Most projects need Solidity changes, not only TypeScript changes.

There are no deprecation shims. Old names are removed, so the compiler finds most of the work for you.

<Warning>
  Some of this migration produces no compile error at all. The [silent changes](#silent-changes) section lists every case where the old code still builds and then misbehaves at runtime. Read it before you declare the upgrade finished.
</Warning>

## Three packages move together

This is one migration across three independently versioned packages. Bumping a subset is the most common way to end up with errors that look like SDK bugs.

| Package                           | Target  | Notes                                          |
| --------------------------------- | ------- | ---------------------------------------------- |
| `@cofhe/*` (every package)        | `0.7.1` | Keep them all on one version                   |
| `@fhenixprotocol/cofhe-contracts` | `0.2.0` | Use the stable release, not a `0.2.0-beta.*`   |
| `fhenix-confidential-contracts`   | `0.4.0` | Only if you use FHERC20 or confidential tokens |

`fhenix-confidential-contracts` `0.4.0` depends on an exact `@fhenixprotocol/cofhe-contracts@0.2.0`. Leaving a beta pin in place resolves two copies of `FHE.sol`, and `sharedEuint64` from one is not the same type as `sharedEuint64` from the other. The resulting errors read like nonsense, so check the pin first.

If you are coming from `cofhejs` rather than `@cofhe/sdk`, follow [migrating from cofhejs](/client-sdk/introduction/migrating-from-cofhejs) first. If you are on `0.5.x`, the Solidity work below is identical, because the `InEuintXX` structs did not change between `0.5` and `0.6`.

## Let the skill do the mechanical work

Most of this migration is mechanical, and the parts that are not deserve a conversation rather than a find and replace. Fhenix publishes an agent skill that drives the whole thing. It detects what your project uses, works in dependency order, shows you a diff before touching anything, and reports what it could not decide for you.

The skill uses the open [Agent Skill](https://agentskills.io/) format, which Claude Code, Cursor, GitHub Copilot, VS Code, Codex, Gemini CLI, OpenCode, Roo, Kiro, and Goose all read. Drop the folder wherever your agent looks for skills, commonly `.claude/skills/` or `.cursor/skills/`:

```bash theme={null}
mkdir -p .claude/skills
curl -L https://github.com/FhenixProtocol/cofhesdk/archive/refs/heads/master.tar.gz \
  | tar -xz --strip-components=2 -C .claude/skills \
    '*/skills/cofhe-migrate-0-6-to-0-7'
```

You can also [browse the skill on GitHub](https://github.com/FhenixProtocol/cofhesdk/tree/master/skills/cofhe-migrate-0-6-to-0-7) and copy the directory by hand.

Then ask your agent to run it:

```text theme={null}
Migrate this project to @cofhe/sdk 0.7.1
```

It proposes each change as a diff and waits for your approval. Tell it to apply everything if you would rather review at the end.

It stops and asks on the decisions that are genuinely yours:

* A function taking two or more encrypted parameters.
* Encrypted inputs produced in one place and consumed in another.
* A contract you do not control on the other side of a handoff.
* Persisted `EncryptedItemInput` records. They carry per-item signatures, which cannot be rebuilt from a batch signature.

## Work contract-first

If you are doing this by hand, the order matters. The ABI you land on decides what every call site has to look like, so going backwards means rewriting the same call sites twice.

<Steps>
  <Step title="Bump the three packages">
    Resolve versions from the registry rather than copying them from a guide. Update the contract dependencies at the same time as the JavaScript ones.
  </Step>

  <Step title="Change your contracts">
    Move off the deleted `InEuintXX` structs, then decide which values cross a contract boundary.
  </Step>

  <Step title="Update config keys">
    These throw at client construction, so they surface the moment you boot.
  </Step>

  <Step title="Rename permits to ACPs">
    Sweeping the renames first makes every remaining TypeScript error a genuine shape problem rather than a missing identifier.
  </Step>

  <Step title="Update encrypt call sites">
    The batch result and the consuming contract land on the same lines, so do them together.
  </Step>
</Steps>

## Contracts: inputs from a user

`0.2.0` deletes the `InEuintXX` structs, the `FHE.asEuint32(InEuint32)` overloads, the `Utils.inputFromEuint32` helpers, and `ITaskManager.verifyInput`. An encrypted value arriving from a user is now an `externalEuintXX` handle plus a `bytes` proof.

```solidity theme={null}
// Before
function setValue(InEuint32 memory inValue) public {
  _setStoredValue(FHE.asEuint32(inValue));   // the struct carried its own signature
}
```

```solidity theme={null}
// After
function setValue(externalEuint32 inValue, bytes memory proof) public {
  _setStoredValue(FHE.asEuint32(inValue, proof));
}
```

The signature moves out of the struct into a `bytes` parameter that immediately follows the handle it authenticates. This changes the ABI, so a contract coming from `InEuintXX` needs a redeploy.

A contract that already takes `(externalEuint32, bytes)` for a single encrypted value needs no change and no redeploy. `FHE.asEuint32(handle, proof)` verifies that input as a batch of one, so a `0.7` signature works against the ABI you already deployed.

<Note>
  The proof does not have to be the last parameter. It has to follow the `external*` handle as a pair. Extra plain arguments can come after it, which is how ERC-7984's `confidentialTransferAndCall` is shaped.
</Note>

### More than one encrypted value

One signature now covers all the handles in the batch, computed over them together. You cannot keep two separate `(handle, proof)` pairs, because verifying one handle against a signature covering two reverts.

You can still keep the parameter names, which most projects prefer to an array:

```solidity theme={null}
function transfer(
  address to,
  externalEuint32 amount,
  externalEuint32 fee,
  bytes calldata signature
) public {
  externalEuint32[] memory packed = new externalEuint32[](2);
  packed[0] = amount;
  packed[1] = fee;
  euint32[] memory values = FHE.asEuint32s(packed, signature);
  // values[0] is amount, values[1] is fee
}
```

The array form, `function transfer(address to, externalEuint32[] calldata values, bytes calldata signature)`, is shorter but collapses named parameters into indices. Either way the encrypted parameters must be adjacent, because they share one signature and there is no other way to tell which `bytes` belongs to them.

For a batch mixing types, such as a `euint32` with an `ebool`, call `ITaskManager.batchVerifyInputs` directly.

### Values that cross a contract boundary

`0.2.0` adds a `sharedEuintXX` type for encrypted values passed between contracts. Both directions count: a value handed over as an argument, and a value returned by a function that is not `view`. A `view` function returning an encrypted value is unaffected, because it never granted anything.

This is the part of the migration the compiler cannot help with. The `0.6` spelling, an `FHE.allowTransient` grant plus a bare `euintXX` parameter, still compiles and still runs while both sides stay on it.

It is also the part with a security consequence. A function taking a bare `euintXX` from outside can be turned into an oracle over every ciphertext the contract holds. FHE operations check the permission of the contract performing them, not of whoever called it. An attacker passes a handle the contract is allowed on, such as one read from its own storage, and gets back a value derived from it.

```solidity theme={null}
// Before: permission granted out of band, and the sharer cannot be verified
FHE.allowTransient(amount, address(token));
token.pull(amount);
```

```solidity theme={null}
// After: the type carries the permission, and the sharer is checked
token.pull(FHE.shareEuint64(amount, address(token)));
```

```solidity theme={null}
// On the receiving side
function pull(sharedEuint64 shared) external {
  euint64 amount = FHE.receiveEuint64Param(shared);
  ...
}
```

Both sides have to move in the same change. `pull(euint64)` and `pull(sharedEuint64)` are both `bytes32` on the wire, so an unmigrated caller compiles against a migrated callee and then reverts at runtime with `NotShared`.

Pick the receive form by how the value reached you. `receiveEuint64Param` checks the sharer against `msg.sender` and suits a value that arrived as an argument. `receiveEuint64FromCall(shared, callee)` checks it against the contract you called, and `callee` must be the address called in that same expression.

Returning an encrypted value works the same way in reverse. Share the result with `msg.sender`, and unwrap it with the `FromCall` form:

```solidity theme={null}
// In Token
function swap(sharedEuint64 shared) external returns (sharedEuint64) {
  euint64 amountIn = FHE.receiveEuint64Param(shared);
  return FHE.shareEuint64(FHE.div(amountIn, FHE.asEuint64(2)), msg.sender);
}
```

```solidity theme={null}
// In Vault
euint64 out = FHE.receiveEuint64FromCall(token.swap(shared), address(token));
FHE.allowThis(out);
```

`FHE.shareEuint64` reverts with `SenderNotAllowed` unless your contract is itself allowed on the handle. You cannot share what you cannot use.

Sharing is single-use and transaction-scoped, so a share cannot be stored, replayed, or reconstructed from an event. To keep a received value past the transaction, call `FHE.allowThis` on the unwrapped `euintXX`. Anything you derive from it produces a new handle that needs its own `FHE.allowThis` before you store it.

## Config keys

Five keys were renamed. In `0.6` an unknown key was silently dropped and the setting fell back to its default. Both schemas now reject unknown keys and name the replacement, so a stale key throws when you construct the client.

| Before                                 | After                               |
| -------------------------------------- | ----------------------------------- |
| `defaultPermitExpiration`              | `defaultACPExpiration`              |
| `react.shareablePermits`               | `react.shareableACPs`               |
| `react.autogeneratePermits`            | `react.autogenerateACPs`            |
| `react.permitExpirationOptions`        | `react.acpExpirationOptions`        |
| `react.defaultPermitExpirationSeconds` | `react.defaultACPExpirationSeconds` |

<Note>
  If your `0.6` app set any of these, the setting was already being ignored and the default was in force. Behavior can change once it starts applying again. An expiration you believed was one day may have been running at the 30 day default.
</Note>

## Permits are now ACPs

"Permit" is now "ACP", short for Access Control Permission, so that it is not confused with an ERC-2612 permit. The entry point moves and the client namespace is singular:

```ts theme={null}
// Before
import { PermitUtils, type Permit, type Permission } from '@cofhe/sdk/permits';
const permit = await client.permits.createSelf();

// After
import { ACPUtils, type ACP, type ACPPublic } from '@cofhe/sdk/acps';
const acp = await client.acp.createSelf();
```

Decrypt builders follow the same rename: `.withPermit()` becomes `.withACP()` and `.withoutPermit()` becomes `.withoutACP()`.

React hooks rename the same way. `useCofhePermits` becomes `useCofheACPs`, `useCofheActivePermit` becomes `useCofheActiveACP`, and the rest of the family follows the pattern.

Rename only identifiers that resolve to a `@cofhe/*` import. A blind replace of `Permit` corrupts unrelated code, and the English words `permitted` and `permitting` are not renames. `isPermittedCofheEnvironment` and `isAllowedWithPermission` keep their names.

For the full type table, the new scope model, and what the client gained, see [Access Control Permissions](/client-sdk/guides/acps).

<Warning>
  Stored permits are dropped. They were signed with retired EIP-712 types and cannot verify against the upgraded ACL, so your users are prompted to sign again on first use. Nothing needs migrating, but it looks like data loss if you are not expecting it.
</Warning>

## Encrypt call sites

Two changes land on the same lines. `execute()` returns a different shape, and you must now declare the consuming contract.

```ts theme={null}
// Before
const [encA, encB] = await client.encryptInputs([a, b]).execute();
await contract.f(encA, encB);

// After
const [handleA, handleB, signature] = await client
  .encryptInputs([a, b])
  .setConsumingContract(contractAddress)
  .execute();
await contract.f([handleA, handleB], signature);
```

The result now holds one handle per input followed by a single signature, so it has `inputs.length + 1` elements. Code that assumed the result matched the input count is off by one.

`setConsumingContract` is required because the verifier binds the target contract into the signed digest, which stops a batch signed for one contract being replayed into another. Omitting it is a compile error in TypeScript, since `encryptInputs()` returns a builder without an `execute()` method.

<Warning>
  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. Trace the value to the `FHE.asEuint*` call.
</Warning>

The per-item input types are gone: `EncryptedItemInput`, `EncryptedUint64Input`, and the rest of that family. A value that used to be one of them is now a handle. These also break on your own helpers, where a fixture typed `(encAmount: EncryptedUint64Input)` fails at its definition rather than at the call site. `asHashPlusProof()` is removed, because its output is what `execute()` always returns now.

<h2 id="silent-changes">
  Silent changes
</h2>

A clean build proves very little in this migration. Each of these compiles and then behaves differently:

| Change                                                                  | What happens                                                                                               |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| A bare `euintXX` parameter left unmigrated                              | Compiles, and keeps running while both sides stay on it. Can be a live disclosure path, not a style issue. |
| Only one side moved to `sharedEuintXX`                                  | Both spellings are `bytes32` on the wire, so the call compiles and reverts with `NotShared`.               |
| `receiveEuintXXFromCall` naming a trusted address instead of the callee | Checks who created the share rather than who handed it over. Exploitable.                                  |
| A wrong-length destructure of the `execute()` result                    | Typechecks, then fails at runtime on the trailing signature.                                               |
| A reordered or partial batch                                            | The signature covers the exact ordered set that `execute()` produced. Compiles, then fails verification.   |
| `ACPUtils.export()` on a self ACP                                       | `0.6` serialized anything; `0.7` throws unless the ACP is a signed sharing ACP.                            |
| A transient allowance relied on across transactions                     | Mock transient storage is now real EIP-1153, so it expires with its own transaction rather than the block. |
| Overload selector strings and ERC-165 interface ids                     | `InEuint64` was a tuple; `externalEuint64` is a `bytes32`, so ids change. Recompute them.                  |

Verify by exercising a round trip, not by compiling. For every bare-handle function you kept, ask whether an arbitrary caller can reach it with a handle the contract is allowed on. If they can, guard it with `FHE.isAllowed(value, msg.sender)`.

## Next steps

* Check every version against the [compatibility page](/get-started/introduction/compatibility).
* Read the [Access Control Permissions guide](/client-sdk/guides/acps) for the permission model in full.
* Review [encrypting inputs](/client-sdk/guides/encrypting-inputs) for the current builder API.
