Testing
This page shows the common patterns for writing Hardhat tests with the CoFHE plugin.
Test setup
Use hre.cofhe.createClientWithBatteries in a before hook. It creates and connects a fully configured CofheClient — including a self-permit — so the client is ready for every test in the suite:
import hre from 'hardhat';
import { CofheClient } from '@cofhe/sdk';
import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
let cofheClient: CofheClient;
let signer: HardhatEthersSigner;
before(async () => {
[signer] = await hre.ethers.getSigners();
cofheClient = await hre.cofhe.createClientWithBatteries(signer);
});See Client for manual setup options.
Encrypt → store → decrypt
The core test loop: encrypt a value, pass it to a contract, then decrypt the stored handle.
// 1. Encrypt the input
const encrypted = await cofheClient
.encryptInputs([Encryptable.uint32(100n)])
.execute();
// 2. Send to contract
const tx = await testContract.setValue(encrypted[0]);
await tx.wait();
// 3. Read the stored handle
const ctHash = await testContract.storedValue();
// 4. Decrypt for display
const decrypted = await cofheClient
.decryptForView(ctHash, FheTypes.Uint32)
.execute();
expect(decrypted).to.equal(100n);For more on each step, see Encrypting Inputs and Decrypting to View.
Reading plaintext directly
In tests you can bypass the normal decrypt flow and read the raw plaintext stored by the mock contracts. This is useful for asserting contract state without needing a permit:
// Get raw plaintext value
const plaintext = await hre.cofhe.mocks.getPlaintext(ctHash);
// Or use the assertion shorthand
await hre.cofhe.mocks.expectPlaintext(ctHash, 100n);See Mock Contracts for details.
Permits
createClientWithBatteries pre-generates a self-permit, so decryptForView and decryptForTx().withPermit() work immediately. For tests that need named permits or multiple signers, create them explicitly:
const permit = await cofheClient.permits.createSelf({
issuer: signer.address,
name: 'My Test Permit',
});
// Select it as the active permit
const permitHash = PermitUtils.getHash(permit);
cofheClient.permits.selectActivePermit(permitHash);Alternatively, you may find it easier to create a cofheClient for each of the multiple signers you need to test.
const [bob, alice] = await hre.ethers.getSigners();
const bobClient = await hre.cofhe.createClientWithBatteries(bob);
const aliceClient = await hre.cofhe.createClientWithBatteries(alice);decryptForTx patterns
decryptForTx returns a { ctHash, decryptedValue, signature } tuple for on-chain submission. The permit mode must be selected explicitly.
Globally allowed values (.withoutPermit())
When a contract calls FHE.allowPublic(handle), anyone can decrypt without a permit:
const result = await cofheClient
.decryptForTx(publicCtHash)
.withoutPermit()
.execute();
expect(result.decryptedValue).to.equal(55n);Access-controlled values (.withPermit())
For handles restricted by ACL policy, supply a permit:
const result = await cofheClient
.decryptForTx(ctHash)
.withPermit(permit)
.execute();
expect(result.decryptedValue).to.equal(99n);Submitting the result on-chain
Pass the result directly to your contract:
await myContract.revealValue(
result.ctHash,
result.decryptedValue,
result.signature
);For a full walkthrough of decryptForTx, see Decrypting to Transact.