Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions client-sdk/hardhat-3-plugin/client.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Client
description: "Create and connect a CofheClient inside a Hardhat 3 test"
---

The plugin extends every Hardhat 3 connection with a `cofhe` namespace. There are three ways to create and connect a `CofheClient` from a test, in increasing order of control.

## Batteries included (recommended)

`cofhe.createClientWithBatteries(walletClient?)` is the one-call setup. It:

1. Creates a CoFHE config with `environment: 'hardhat'`.
2. Creates a `CofheClient`.
3. Connects it using the wallet client you pass (or the first wallet client on the connection if you don't).
4. Generates and signs a self permit so the client can immediately decrypt encrypted values.

```typescript
import { describe, it } from 'node:test';
import { network } from 'hardhat';

describe('My FHE contract', async () => {
const { viem, cofhe } = await network.connect();
const [walletClient] = await viem.getWalletClients();

it('decrypts a value', async () => {
const client = await cofhe.createClientWithBatteries(walletClient);
client.connected; // true
});
});
```

Or call it with no arguments to use the first wallet client on the connection:

```typescript
const client = await cofhe.createClientWithBatteries();
```

<Info>
Because the self permit is created up-front, encrypt / decrypt / decryptForView / decryptForTx all work immediately without any additional setup.
</Info>

## Manual setup

For more control — custom config options, multiple signers, adjusting `encryptDelay` — set up the client step by step.

<Steps>

<Step title="Create config">

```typescript
const config = await cofhe.createConfig();
// With overrides:
const config = await cofhe.createConfig({ mocks: { encryptDelay: 0 } });
```

`cofhe.createConfig` wraps `createCofheConfig` from `@cofhe/sdk/node` with two Hardhat-3-specific additions:

- Sets `environment: 'hardhat'` automatically.
- Defaults `mocks.encryptDelay` to `0` so tests run without artificial wait times.

</Step>

<Step title="Create the client">

```typescript
const client = cofhe.createClient(config);
```

The client is **not connected yet** — call `client.connect(...)` before using it.

</Step>

<Step title="Connect with the connection's wallet client">

```typescript
const publicClient = await viem.getPublicClient();
const [walletClient] = await viem.getWalletClients();

await client.connect(publicClient, walletClient);
```

</Step>

</Steps>

## API summary

| Method | Returns | Description |
| --- | --- | --- |
| `cofhe.createConfig(overrides?)` | `Promise<CofheConfig>` | CoFHE config pre-wired for the Hardhat mock environment (`environment: 'hardhat'`, `mocks.encryptDelay: 0`). |
| `cofhe.createClient(config)` | `CofheClient` | Unconnected client. Call `client.connect(publicClient, walletClient)` before use. |
| `cofhe.createClientWithBatteries(walletClient?)` | `Promise<CofheClient>` | Fully configured + connected + self-permit signed. Defaults to the first wallet client on the connection. |

## Using the client

Once connected, the client works identically to the standard SDK client. See:

- [Encrypting Inputs](/client-sdk/guides/encrypting-inputs)
- [Decrypt to View](/client-sdk/guides/decrypt-to-view)
- [Decrypt to Transact](/client-sdk/guides/decrypt-to-tx)
- [Permits](/client-sdk/guides/permits)
140 changes: 140 additions & 0 deletions client-sdk/hardhat-3-plugin/getting-started.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
title: Getting Started
description: "Set up @cofhe/hardhat-3-plugin for local FHE contract development under Hardhat 3"
---

`@cofhe/hardhat-3-plugin` is the Hardhat 3 counterpart to [`@cofhe/hardhat-plugin`](/client-sdk/hardhat-plugin/getting-started). It deploys the CoFHE mock contracts to an in-process Hardhat network on every `network.connect()` call and exposes a `cofhe` namespace on the connection object — ready to use immediately, no boilerplate required.

<Note>
Use this plugin if you've adopted the [Hardhat 3 plugin/hook model](https://hardhat.org). For Hardhat v2 projects, use [`@cofhe/hardhat-plugin`](/client-sdk/hardhat-plugin/getting-started) instead.
</Note>

## What the plugin provides

- **Auto-deploys mocks** on every `network.connect()` — `MockTaskManager`, `MockACL`, `MockZkVerifier`, `MockThresholdNetwork`, and `TestBed`.
- **`conn.cofhe` namespace** on the Hardhat 3 connection object (sits alongside `conn.viem` from `@nomicfoundation/hardhat-viem`).
- **`cofhe.createClientWithBatteries()`** — one-call SDK client setup with a pre-signed self permit.
- **Mock helpers** — Viem contract descriptors for every mock, plaintext inspection (`getPlaintext` / `expectPlaintext`), and logging control.

## Installation

<Steps>

<Step title="Install the package">

<CodeGroup>

```bash npm
npm install @cofhe/hardhat-3-plugin @cofhe/sdk @fhenixprotocol/cofhe-contracts
```

```bash pnpm
pnpm add @cofhe/hardhat-3-plugin @cofhe/sdk @fhenixprotocol/cofhe-contracts
```

```bash yarn
yarn add @cofhe/hardhat-3-plugin @cofhe/sdk @fhenixprotocol/cofhe-contracts
```

</CodeGroup>

</Step>

<Step title="Register the plugin">

```typescript hardhat.config.ts
import { defineConfig } from 'hardhat/config';
import cofhePlugin from '@cofhe/hardhat-3-plugin';
import hardhatViem from '@nomicfoundation/hardhat-viem';
import hardhatNodeTestRunner from '@nomicfoundation/hardhat-node-test-runner';

export default defineConfig({
plugins: [cofhePlugin, hardhatViem, hardhatNodeTestRunner],
});
```

</Step>

</Steps>

## Configuration

The plugin adds an optional `cofhe` key to your Hardhat 3 config. All values shown below are their defaults:

```typescript hardhat.config.ts
export default defineConfig({
plugins: [cofhePlugin, hardhatViem, hardhatNodeTestRunner],

cofhe: {
gasWarning: true, // warn that mock gas costs differ from live FHE
logMocks: true, // enable event-based logging in mock contracts
mocksDeployVerbosity: 'v', // '' silent | 'v' summary | 'vv' full per-contract
},
});
```

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `gasWarning` | `boolean` | `true` | Print a one-line warning after mock deployment reminding that mock gas costs differ from live FHE. |
| `logMocks` | `boolean` | `true` | Enable event-based logging inside mock contracts. |
| `mocksDeployVerbosity` | `'' \| 'v' \| 'vv'` | `'v'` | How much output to print while deploying mocks. `''` silent, `'v'` one summary line per mock, `'vv'` full per-contract deployment log. |

## How auto-deployment works

Every call to `network.connect()` automatically:

1. Deploys all CoFHE mock contracts to the fresh in-process EVM.
2. Attaches a `cofhe` namespace to the returned connection object.

Because `network.connect()` is awaitable at the top level of an `async describe`, you can set everything up without lifecycle hooks:

```typescript
import { describe, it } from 'node:test';
import { network } from 'hardhat';

describe('My FHE contract', async () => {
const { viem, cofhe } = await network.connect();
const publicClient = await viem.getPublicClient();
const [walletClient] = await viem.getWalletClients();

it('encrypts and decrypts a value', async () => {
const client = await cofhe.createClientWithBatteries(walletClient);
// ... test logic
});
});
```

<Tip>
**Why `async describe`?** Hardhat 3's `node:test` runner supports top-level `await` inside the describe callback. This lets you resolve the connection (and deploy mocks) exactly once per test file without needing a `before()` hook.
</Tip>

## Mock contracts deployed

| Contract | Address | Description |
| --- | --- | --- |
| `MockTaskManager` | `0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9` | Coordinates FHE operations and ACL |
| `MockACL` | dynamic | Access Control List — address resolved from TaskManager |
| `MockZkVerifier` | `0x0000000000000000000000000000000000005001` | Verifies ZK proofs for encrypted inputs |
| `MockThresholdNetwork` | `0x0000000000000000000000000000000000005002` | Simulates the threshold decryption network |
| `TestBed` | `0x0000000000000000000000000000000000005003` | Utility contract for storing and retrieving encrypted values in tests |

Fixed-address contracts are deployed via `hardhat_setCode`, so they are always at the same address regardless of deployment order. `MockACL` is deployed as a normal contract (so its EIP-712 domain constructor runs correctly) and its address is registered in `MockTaskManager`.

## Differences from `@cofhe/hardhat-plugin`

| Concern | `@cofhe/hardhat-plugin` (v2) | `@cofhe/hardhat-3-plugin` |
| --- | --- | --- |
| Entry point | `hre.cofhe` (global) | `conn.cofhe` (per `network.connect()` call) |
| Lifecycle | Pre-task hook (`npx hardhat test` / `node`) | `network.connect()` returns a fresh deployment |
| Test runner expectation | Mocha-style (`before`, `it`) | `node:test` with `async describe` |
| Mock compilation for custom-error decoding | Overrides `TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS` | Runs `hre.solidity.build()` during the `hre.created` hook |
| Bytecode sourcing | `hre.artifacts.readArtifact()` | `hre.artifacts.readArtifact()` (same — for variable-address mocks like `MockACL`) |

The Solidity surface (mock contracts, plaintext semantics, `FHE.verifyDecryptResult` acceptance) is **identical** between the two plugins; the differences are all in how Hardhat hosts the plugin.

## Next steps

- [Client](/client-sdk/hardhat-3-plugin/client) — `conn.cofhe.createConfig`, `createClient`, `createClientWithBatteries`.
- [Mock Contracts](/client-sdk/hardhat-3-plugin/mock-contracts) — Viem contract descriptors and plaintext inspection helpers.
- [Logging](/client-sdk/hardhat-3-plugin/logging) — `enableLogs`, `disableLogs`, `withLogs`.
- [Testing](/client-sdk/hardhat-3-plugin/testing) — end-to-end test patterns.
42 changes: 42 additions & 0 deletions client-sdk/hardhat-3-plugin/logging.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: Logging
description: "Inspect FHE operations in your Hardhat 3 test output"
---

The mock contracts emit structured logs for every FHE operation. This makes it easy to see what your contracts are doing under the hood during tests.

## `withLogs(name, fn)` — recommended

Wraps a block of code with logging enabled and prints a labeled box around the output. The `name` appears as the header so you can identify which call produced which operations.

```typescript
await cofhe.mocks.withLogs('counter.increment()', async () => {
await walletClient.writeContract({
...counterContract,
functionName: 'increment',
});
});
```

`withLogs` enables logging before the closure runs and disables it after, so only operations from within that block appear in the output. The previous on/off state is restored when it returns.

## `enableLogs()` / `disableLogs()` — manual

For finer-grained control, you can enable and disable logging manually:

```typescript
await cofhe.mocks.enableLogs();

await walletClient.writeContract({
...counterContract,
functionName: 'increment',
});

await cofhe.mocks.disableLogs();
```

## Default behavior

Logging is controlled by the `cofhe.logMocks` config option (see [Getting Started → Configuration](/client-sdk/hardhat-3-plugin/getting-started#configuration)). When the option is `true` (the default), mock-contract log events are emitted; when `false`, they're suppressed even if you call `enableLogs()`.

`withLogs`, `enableLogs`, and `disableLogs` only flip the runtime gate at the mock-contract level — they do **not** override the config.
73 changes: 73 additions & 0 deletions client-sdk/hardhat-3-plugin/mock-contracts.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
title: Mock Contracts
description: "Inspect plaintext and call mock CoFHE contracts in Hardhat 3 tests"
---

`conn.cofhe.mocks` exposes Viem contract descriptors for every deployed mock contract and helpers for reading the on-chain plaintext that the mock task manager stores. None of this requires the SDK client — the mocks are reachable from any Viem `publicClient` / `walletClient`.

## Contract descriptors

Each mock is exposed as a synchronous `{ address, abi }` object — spread it directly into Viem's `readContract` / `writeContract`:

```typescript
cofhe.mocks.MockTaskManager; // { address: '0x...', abi: [...] }
cofhe.mocks.MockACL; // { address: '0x...', abi: [...] }
cofhe.mocks.MockZkVerifier; // { address: '0x...', abi: [...] }
cofhe.mocks.MockThresholdNetwork; // { address: '0x...', abi: [...] }
cofhe.mocks.TestBed; // { address: '0x...', abi: [...] }
```

### Calling a mock directly

```typescript
const ctHash = await publicClient.readContract({
...cofhe.mocks.TestBed,
functionName: 'numberHash',
});

await walletClient.writeContract({
...cofhe.mocks.MockTaskManager,
functionName: 'setSecurityZones',
args: [0, 1],
});
```

This is the lowest-friction way to assert mock state without going through the SDK.

## Reading plaintext values

Because `MockTaskManager` stores plaintext values on-chain, you can read the underlying plaintext of any encrypted handle directly in tests — no permit needed.

### `getPlaintext(ctHash)`

Returns the plaintext `bigint` for a given ciphertext hash. Accepts either a `bigint` or a hex `string`.

```typescript
const plaintext = await cofhe.mocks.getPlaintext(ctHash);
```

### `expectPlaintext(ctHash, expected)`

Assertion shorthand — throws if the on-chain plaintext doesn't match `expected`:

```typescript
await cofhe.mocks.expectPlaintext(ctHash, 42n);
```

<Warning>
`getPlaintext` and `expectPlaintext` only work on the in-process Hardhat network where `MockTaskManager.mockStorage` exists. They will throw on real CoFHE networks.
</Warning>

## Re-deploying mocks mid-test

Normally you don't need this — mocks are deployed automatically on every `network.connect()`. For advanced scenarios where you want to reset mock state inside a single test:

```typescript
await cofhe.mocks.deployMocks({ deployTestBed: true, silent: true });
```

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `deployTestBed` | `boolean` | `true` | Whether to deploy the `TestBed` utility contract. |
| `gasWarning` | `boolean` | inherits from config | Print the gas-warning line after deployment. |
| `silent` | `boolean` | `false` | Suppress all deployment output (overrides `mocksDeployVerbosity`). |
Loading