> ## Documentation Index
> Fetch the complete documentation index at: https://fhenix-ci-version-drift-check.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# CofheClient

> The in-Solidity SDK shim: one client per user, produces encrypted inputs and signed ACPs

`CofheClient` is the Foundry plugin's in-Solidity SDK shim. One client per "user" in your scenario. The client carries a private key and produces encrypted inputs and signed ACPs **as if it were that user's frontend SDK**, with no JS bridge required.

## Creating and connecting

Spin up a client from inside [`CofheTest`](/client-sdk/foundry-plugin/cofhe-test) with `createCofheClient()`, then bind it to an address with `connect(pkey)`:

```solidity theme={null}
CofheClient bob = createCofheClient();
bob.connect(0xB0B);            // bob.account() == vm.addr(0xB0B)
```

After `connect`, the client knows which address to sign as. All `createExternalEuintN` and `ACP_*` calls use that account automatically; there's no `account` argument to pass.

To act onchain as that user, prank with `client.account()`:

```solidity theme={null}
(externalEuint32 hash, bytes memory proof) = bob.createExternalEuint32(2000, address(counter));

vm.prank(bob.account());
counter.reset(hash, proof);
```

<Warning>
  A mismatch between the prank address and the client that produced the input will fail the ZK-verifier signature check. The input was signed for `bob.account()`, not whoever you pranked. Always match the client to the prank.
</Warning>

## Encrypting inputs

The client mirrors the JS SDK's `encryptInputs` API, one method per encrypted Solidity type. Each takes the plaintext plus the contract that will consume it, and returns the handle and its proof as a pair:

| Method                                     | Returns                     |
| ------------------------------------------ | --------------------------- |
| `createExternalEbool(bool, address)`       | `(externalEbool, bytes)`    |
| `createExternalEuint8(uint8, address)`     | `(externalEuint8, bytes)`   |
| `createExternalEuint16(uint16, address)`   | `(externalEuint16, bytes)`  |
| `createExternalEuint32(uint32, address)`   | `(externalEuint32, bytes)`  |
| `createExternalEuint64(uint64, address)`   | `(externalEuint64, bytes)`  |
| `createExternalEuint128(uint128, address)` | `(externalEuint128, bytes)` |
| `createExternalEaddress(address, address)` | `(externalEaddress, bytes)` |

The second argument is the consuming contract. The verifier binds it into the signature, so a proof made for one contract will not verify in another.

```solidity theme={null}
(externalEuint32 hash, bytes memory proof) = bob.createExternalEuint32(42, address(counter));

vm.prank(bob.account());
counter.reset(hash, proof);
```

For several values under one signature, use `createEncryptedInputsBatch`.

## Decrypting

The plugin exposes both decryption flows the SDK supports:

| Method                              | Returns                                                | Use for                                                                                           |
| ----------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `decryptForTx_withoutACP(ctHash)`   | `(bytes32 ctHash, uint256 plaintext, bytes signature)` | Globally-allowed (`FHE.allowPublic`) ciphertexts. Pass `signature` to `FHE.publishDecryptResult`. |
| `decryptForTx_withACP(ctHash, acp)` | `(bytes32, uint256, bytes)`                            | ACL-gated `decryptForTx` flow.                                                                    |
| `decryptForView(ctHash, acp)`       | `uint256 plaintext`                                    | Offchain seal/unseal flow. **Reverts on deny**, so use the mock directly to assert deny.          |

### Public-decrypt 3-step flow with `decryptForTx_withoutACP`

Mirrors the production flow when a contract calls `FHE.publishDecryptResult`:

```solidity theme={null}
// Step 1: contract grants public decrypt permission
vm.prank(bob.account());
counter.allowCounterPublicly();   // calls FHE.allowPublic(handle)

// Step 2: SDK fetches plaintext + threshold-network signature
bytes32 ctHash = euint32.unwrap(counter.count());
(, uint256 plaintext, bytes memory sig) = bob.decryptForTx_withoutACP(ctHash);

// Step 3: contract verifies signature and stores plaintext
counter.revealCounter(uint32(plaintext), sig);
```

The same shape runs unmodified against real CoFHE on testnet. The mock signature is produced by the same `MockThresholdNetworkSigner` that `FHE.verifyDecryptResult` accepts.

### ACP-based unseal with `decryptForView`

```solidity theme={null}
ACP memory bobAcp = bob.ACP_createSelf();
uint256 value = bob.decryptForView(ctHash, bobAcp);
assertEq(value, 42);
```

`decryptForView` reverts when the caller isn't on the ACL. To **assert** the deny path (e.g. "Alice should NOT be able to decrypt Bob's value"), drop down to the mock directly. See [Testing: Deny path](/client-sdk/foundry-plugin/testing#deny-path).

## Access Control Permissions

The client signs EIP-712 ACPs against the ACL's domain. Two flavors:

| Method                        | Purpose                                                                                                              |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `ACP_createSelf()`            | Self-ACP for the connected account; sealing key is auto-derived (`keccak(address)`).                                 |
| `ACP_createShared(recipient)` | Issuer half of a shared ACP (no sealing key; the recipient adds it on import).                                       |
| `ACP_exportShared(acp)`       | Strip sensitive fields to produce `SharedACPExport` (safe to transmit out-of-band).                                  |
| `ACP_importShared(export)`    | Recipient-side completion: adds sealing key and recipient signature. Reverts unless `export.recipient == account()`. |
| `createSealingKey(seed)`      | Custom sealing key. Rarely needed: `ACP_createSelf` derives one for you.                                             |

### Self-ACP (most common)

```solidity theme={null}
ACP memory bobAcp = bob.ACP_createSelf();
uint256 plaintext = bob.decryptForView(ctHash, bobAcp);
```

`ACP_createSelf` builds the EIP-712 typed-data, derives a sealing key from the connected account, and signs, all in one call.

### Shared ACPs (issuer to recipient)

```solidity theme={null}
// Bob (issuer) creates an ACP shared to Alice (recipient)
ACP memory shared = bob.ACP_createShared(alice.account());

// Bob exports it (strips bob's sealing key) for transmission
SharedACPExport memory exported = bob.ACP_exportShared(shared);

// Alice imports it — adds her sealing key and recipient signature
ACP memory aliceImported = alice.ACP_importShared(exported);
```

`ACP_importShared` reverts unless the calling client's `account()` matches `export.recipient`, preventing Alice from importing an ACP shared to someone else.

## Common pitfalls

<AccordionGroup>
  <Accordion title="Wrong client for the prank" icon="triangle-exclamation">
    `vm.prank(bob.account())` while the input came from `alice.createExternalEuintN(...)` fails ZK verification. The input was signed for Alice's address, not Bob's. Match the client to the prank.
  </Accordion>

  <Accordion title="Stale handle reads" icon="rotate">
    `euint32.unwrap(counter.count())` returns the *current* handle. Storing it in a local then asserting after a write reads the old handle.

    ```solidity theme={null}
    bytes32 oldHandle = euint32.unwrap(counter.count());
    counter.increment();
    // ❌ oldHandle still references the pre-increment handle
    expectPlaintext(oldHandle, uint32(0));   // passes by accident
    expectPlaintext(counter.count(), uint32(1));   // ✅ fetch the new handle
    ```

    Re-fetch after each state change.
  </Accordion>

  <Accordion title="ACP issuer must derive from the connected key" icon="key">
    The `pkey` passed to `connect` must derive the address used as `acp.issuer`. If you call `bob.ACP_createSelf()` after `bob.connect(0xB0B)`, the issuer is `vm.addr(0xB0B)`. Trying to forge an issuer mismatch will fail signature verification.
  </Accordion>

  <Accordion title="`decryptForView` reverts on deny" icon="ban">
    Useful default: most tests want a hard failure when the caller isn't permitted. To assert "Alice cannot decrypt", call the mock's `querySealOutput` directly:

    ```solidity theme={null}
    (bool allowed, string memory err, ) = mockThresholdNetwork.querySealOutput(
        uint256(ctHash), block.chainid, aliceAcp
    );
    assertFalse(allowed);
    assertEq(err, "NotAllowed");
    ```
  </Accordion>
</AccordionGroup>

## Next steps

* [Testing](/client-sdk/foundry-plugin/testing): full test-writing patterns.
* [CofheTest](/client-sdk/foundry-plugin/cofhe-test): the test base contract that creates clients.
