> ## 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.

# CofheClient

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

`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 / signed permits **as if it were that user's frontend SDK** — 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 `createInEuintN` and `permit_*` calls use that account automatically — there's no `account` argument to pass.

To act on-chain as that user, prank with `client.account()`:

```solidity theme={null}
vm.prank(bob.account());
counter.reset(bob.createInEuint32(2000));
```

<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:

| Method                      | Returns      |
| --------------------------- | ------------ |
| `createInEbool(bool)`       | `InEbool`    |
| `createInEuint8(uint8)`     | `InEuint8`   |
| `createInEuint16(uint16)`   | `InEuint16`  |
| `createInEuint32(uint32)`   | `InEuint32`  |
| `createInEuint64(uint64)`   | `InEuint64`  |
| `createInEuint128(uint128)` | `InEuint128` |
| `createInEaddress(address)` | `InEaddress` |

All produce signed `EncryptedInput` shapes — drop straight into the `InEuintN` parameter on the contract under test.

```solidity theme={null}
InEuint32 memory encrypted = bob.createInEuint32(42);

vm.prank(bob.account());
counter.reset(encrypted);
```

## Decrypting

The plugin exposes both decryption flows the SDK supports:

| Method                                    | Returns                                                | Use for                                                                                           |
| ----------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `decryptForTx_withoutPermit(ctHash)`      | `(bytes32 ctHash, uint256 plaintext, bytes signature)` | Globally-allowed (`FHE.allowPublic`) ciphertexts. Pass `signature` to `FHE.publishDecryptResult`. |
| `decryptForTx_withPermit(ctHash, permit)` | `(bytes32, uint256, bytes)`                            | ACL-gated `decryptForTx` flow.                                                                    |
| `decryptForView(ctHash, permit)`          | `uint256 plaintext`                                    | Off-chain seal/unseal flow. **Reverts on deny** — use the mock directly to assert deny.           |

### `decryptForTx_withoutPermit` — public-decrypt 3-step flow

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_withoutPermit(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.

### `decryptForView` — permit-based unseal

```solidity theme={null}
Permission memory bobPermit = bob.permit_createSelf();
uint256 value = bob.decryptForView(ctHash, bobPermit);
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-asserting-alice-cannot-decrypt-bob-s-value).

## Permits

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

| Method                           | Purpose                                                                                                            |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `permit_createSelf()`            | Self-permit for the connected account; sealing key is auto-derived (`keccak(address)`).                            |
| `permit_createShared(recipient)` | Issuer half of a shared permit (no sealing key — the recipient adds it on import).                                 |
| `permit_exportShared(perm)`      | Strip sensitive fields → `SharedPermitExport` (safe to transmit out-of-band).                                      |
| `permit_importShared(export)`    | Recipient-side completion: adds sealing key + recipient signature. Reverts unless `export.recipient == account()`. |
| `createSealingKey(seed)`         | Custom sealing key. Rarely needed — `permit_createSelf` derives one for you.                                       |

### Self-permit (most common)

```solidity theme={null}
Permission memory bobPermit = bob.permit_createSelf();
uint256 plaintext = bob.decryptForView(ctHash, bobPermit);
```

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

### Shared permits (issuer → recipient)

```solidity theme={null}
// Bob (issuer) creates a permit shared to Alice (recipient)
Permission memory shared = bob.permit_createShared(alice.account());

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

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

`permit_importShared` reverts unless the calling client's `account()` matches `export.recipient` — preventing Alice from importing a permit 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.createInEuintN(...)` 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="Permit issuer must derive from the connected key" icon="key">
    The `pkey` passed to `connect` must derive the address used as `permit.issuer`. If you call `bob.permit_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, alicePermit
    );
    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.
