Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Hardhat 3 Plugin

@cofhe/hardhat-3-plugin is the CoFHE plugin for Hardhat 3. It deploys the full CoFHE mock stack on every network.connect() call and attaches a cofhe namespace to the connection object — no setup boilerplate required.

Installation

Install the package

npm
npm install --save-dev @cofhe/hardhat-3-plugin

Register in your config

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], 
});

Configuration

All options are optional. The cofhe block can be omitted entirely if the defaults are acceptable.

hardhat.config.ts
export default defineConfig({
  plugins: [cofhePlugin, hardhatViem, hardhatNodeTestRunner],
  cofhe: {
    logMocks: false, // log FHE ops to the console (default: false)
    gasWarning: false, // warn that mock gas differs from live FHE (default: false)
    mocksDeployVerbosity: 'v', // '' | 'v' | 'vv' — deployment output (default: 'v')
  },
});
OptionTypeDefaultDescription
logMocksbooleanfalseEnable structured FHE operation logs from mock contracts
gasWarningbooleanfalsePrint a reminder that mock gas costs differ from live FHE
mocksDeployVerbosity'' | 'v' | 'vv''v''' silent · 'v' summary line · 'vv' full deployment log

Usage

The cofhe connection object

Every call to network.connect() deploys all CoFHE mock contracts and attaches a cofhe namespace to the returned connection:

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
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('stores an encrypted value', async () => {
    const client = await cofhe.createClientWithBatteries(walletClient);
    // ...
  });
});

createClientWithBatteries(walletClient?)

Creates a fully initialized CofheClient in one call — configured for Hardhat, connected, and with a self-permit already issued:

// Use the first account in the connection:
const client = await cofhe.createClientWithBatteries();
 
// Or pass a specific wallet client:
const [walletClient] = await viem.getWalletClients();
const client = await cofhe.createClientWithBatteries(walletClient);

For manual setup, use cofhe.createConfig() and cofhe.createClient() separately. See the SDK Client docs.

Mock utilities

Plaintext inspection

In the mock environment every ciphertext has a corresponding plaintext stored on-chain. These helpers let you assert contract state without going through the full decrypt flow:

// Returns the raw plaintext as a bigint
const value = await cofhe.mocks.getPlaintext(ctHash);
 
// Throws if the plaintext doesn't match
await cofhe.mocks.expectPlaintext(ctHash, 42n);

Both accept ctHash as a bigint or hex string.

Logging

FHE operation logs are off by default. Turn them on for a specific block using withLogs:

await cofhe.mocks.withLogs('counter.increment()', async () => {
  await counter.increment();
});

Or toggle manually:

await cofhe.mocks.enableLogs();
await counter.increment();
await cofhe.mocks.disableLogs();

Contract descriptors

Each mock contract is exposed as a { address, abi } object that can be spread directly into Viem's readContract / writeContract:

// All mock contracts are synchronous { address, abi } descriptors:
cofhe.mocks.MockTaskManager;
cofhe.mocks.MockACL;
cofhe.mocks.MockZkVerifier;
cofhe.mocks.MockThresholdNetwork;
cofhe.mocks.TestBed;

Complete example

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { network } from 'hardhat';
import { Encryptable, FheTypes } from '@cofhe/sdk';
 
describe('Encrypted counter', async () => {
  const { viem, cofhe } = await network.connect();
  const publicClient = await viem.getPublicClient();
  const [walletClient] = await viem.getWalletClients();
 
  it('encrypts, stores, and reads back a uint32', async () => {
    const client = await cofhe.createClientWithBatteries(walletClient);
 
    const [enc] = await client
      .encryptInputs([Encryptable.uint32(42n)])
      .execute();
 
    await walletClient.writeContract({
      ...cofhe.mocks.TestBed,
      functionName: 'setNumber',
      args: [enc],
    });
 
    const ctHash = await publicClient.readContract({
      ...cofhe.mocks.TestBed,
      functionName: 'numberHash',
    });
 
    await cofhe.mocks.expectPlaintext(ctHash, 42n);
  });
});

Differences from @cofhe/hardhat-plugin

@cofhe/hardhat-plugin (v2)@cofhe/hardhat-3-plugin
Accesshre.cofheconn.cofhe
Triggernpx hardhat testnetwork.connect()
Test runnerMochaNode.js node:test
Ethers / Viemethers.jsViem
Configimport '...' side-effectplugins: [...] array