Skip to main content
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 with createCofheClient(), then bind it to an address with connect(pkey):
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():
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.

Encrypting inputs

The client mirrors the JS SDK’s encryptInputs API — one method per encrypted Solidity type: All produce signed EncryptedInput shapes — drop straight into the InEuintN parameter on the contract under test.

Decrypting

The plugin exposes both decryption flows the SDK supports:

decryptForTx_withoutPermit — public-decrypt 3-step flow

Mirrors the production flow when a contract calls FHE.publishDecryptResult:
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

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.

Permits

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

Self-permit (most common)

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)

permit_importShared reverts unless the calling client’s account() matches export.recipient — preventing Alice from importing a permit shared to someone else.

Common pitfalls

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.
euint32.unwrap(counter.count()) returns the current handle. Storing it in a local then asserting after a write reads the old handle.
Re-fetch after each state change.
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.
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:

Next steps

  • Testing — full test-writing patterns.
  • CofheTest — the test base contract that creates clients.