Skip to main content
This page builds one test file for an encrypted counter. It then covers the patterns you add as a contract grows: plaintext assertions, ACPs (Access Control Permissions), and decryption for a transaction. It assumes the setup from Getting Started.

The contract

The examples use this contract. It accepts an encrypted input, increments the stored value, and can publish a decryption result onchain.
contracts/Counter.sol

Encrypt, store, and decrypt

The core test loop encrypts a value, sends it to the contract, and decrypts the stored handle:
test/Counter.test.ts
Run it:
The output lists the stores and decrypts an encrypted count test under Counter, and the summary reads 1 passing (1 nodejs).
The describe callback is async so that it can await network.create() at the top. Every it inside it shares that connection and its mocks, without a before hook.

Start each test from a clean chain

A connection is one chain, so tests in the same describe see each other’s state. When a test needs a clean chain, open a new connection inside it:
Each network.create() call starts a new simulated chain and deploys a new set of mocks. Contracts from other connections do not exist on it.

Assert on plaintext

Reading the plaintext from the mocks is faster than a decryption and needs no ACP:
Use decryptForView when the SDK’s own behavior is under test. See Mock contracts for both plaintext helpers.

Named ACPs

createClientWithBatteries creates and selects a self ACP. For a test that needs a second ACP, create it and select it:
For several signers, create one client per wallet, as shown on the Client page.

Decrypt for a transaction

decryptForTx resolves to { ctHash, decryptedValue, signature }. Choose the ACP mode before execute().

Public handles

After the contract calls FHE.allowPublic, anyone can decrypt the handle without an ACP:
On a handle that is not public, withoutACP() rejects with mocks decryptForTx call failed: NotAllowed.

Handles restricted by the ACL

Pass an ACP, or call withACP() with no argument to use the active one:

Publish the result onchain

Send the value and the signature to the contract, which verifies and stores them with FHE.publishDecryptResult:
decryptedValue is a bigint. viem expects a number for a uint32 argument, so convert it first.

Common pitfalls

HHE1000: Artifact for contract "MockTaskManager" not found means the mock contracts are missing from solidity.npmFilesToBuild. Add them as shown in Getting Started.
Error TS2345 on createClientWithBatteries(walletClient) or client.connect(...) means two copies of viem are installed. Add the viem override from Getting Started and reinstall from a clean node_modules.
The input signature binds the encrypting account and the consuming contract. The call reverts when a different wallet sends the transaction, or when setConsumingContract named a different contract. viem reports the revert by its selector, 0x7ba5ffb5, which is InvalidSigner(address,address).
The SDK types only offer execute() after setConsumingContract(), so TypeScript reports Property 'execute' does not exist. At runtime, the same omission throws CONSUMING_CONTRACT_UNINITIALIZED.
The contract did not call FHE.allowThis on a stored handle, so its next transaction cannot use it. viem reports the revert by its selector, 0x4d13139e, which is ACLNotAllowed(uint256,address). Wrap the call in withLogs to see which grants each operation made.