| name | fhevm-setup |
| description | Used when an AI coding agent needs to bootstrap a Hardhat workspace for FHEVM confidential contracts. Walks from zero to a working project pinned to @fhevm/solidity@0.11.1, @fhevm/hardhat-plugin@0.4.2, @fhevm/mock-utils@0.4.2, and OpenZeppelin confidential-contracts@0.4.x. Covers the hardhat vars secret workflow (no .env), Node runtime requirements, and the exact compile / test / deploy / test:sepolia command sequence. Triggers on FHEVM setup, project bootstrap, Hardhat environment initialization, installing @fhevm/* packages, hardhat vars, or deploying a first confidential contract. |
fhevm-setup
Bootstrap a Hardhat workspace for FHEVM confidential contracts with current
(2026-05) pins. The skill encodes the canonical config — exact dependency
pins, mock coprocessor wiring, secret management via hardhat vars (no
.env), and the hre.ethers.deployContract deployment pattern — so an AI
agent can scaffold without guessing or hitting silent runtime failures.
Two paths to a working workspace:
- Recommended: if the
/create-template skill is available (template-bundle
installed), run it. The wizard copies a pre-configured workspace and patches
metadata interactively. Skip directly to the Bootstrap sequence below
starting at step 2.
- Manual: create a fresh directory, copy the Pinned stack + Minimal
hardhat.config.ts sections below into package.json and
hardhat.config.ts respectively, and proceed with the bootstrap sequence.
The Essential Principles below are the load-bearing constraints regardless
of how you scaffolded.
Essential Principles
1. Use a pre-configured starting point
Hand-assembled hardhat.config.ts files almost always miss a subtlety
(typechain paths, evmVersion: cancun, mock coprocessor gate, exact
@zama-fhe/relayer-sdk pin) and produce silent failures. Either use the
/create-template wizard (when available) or copy the Minimal
hardhat.config.ts + Pinned stack sections below verbatim.
Three known failure modes if you deviate from the canonical config:
- Cloning
zama-ai/fhevm-hardhat-template@v0.4.1 fails — that tag does
not exist (only v0.0.1, v0.1.0, v0.2.0 do). Either clone without a
tag (default branch) or use a known-working snapshot.
hardhat-deploy × ethers-v5 conflict. The upstream template ships
hardhat-deploy, which transitively pulls @ethersproject/properties@5.x
and silently breaks Sepolia broadcasts due to a customData key reject.
See fhevm-antipatterns AP-12b. Drop
hardhat-deploy and use hre.ethers.deployContract + hardhat run scripts/*.ts instead.
- Loose relayer-sdk pin.
@fhevm/hardhat-plugin@0.4.2 requires
@zama-fhe/relayer-sdk@0.4.1 exact (peer dependency). Caret semantics
may resolve to a newer version that the plugin runtime rejects with
Invalid @zama-fhe/relayer-sdk version. Pin exact. See AP-12c.
2. Secrets go through hardhat vars, never .env
Every modern Zama template uses npx hardhat vars set <NAME> — a Hardhat-managed keystore. This skill set treats .env-based secret loading as forbidden. If an agent wants to wire secrets via dotenv, dotenv-safe, or process.env.PRIVATE_KEY in a Hardhat config, that is the wrong pattern — reject it. The three variables the canonical config expects are MNEMONIC, RPC_KEY (the API key for whichever Sepolia JSON-RPC provider hardhat.config.ts is wired against — Alchemy by default, swap the URL if you prefer Infura), and ETHERSCAN_API_KEY.
3. Hardhat 2, not Hardhat 3
@fhevm/hardhat-plugin@0.4.2 declares hardhat ^2.0.0 as a peer dependency and does not load under Hardhat 3. Any scaffold that suggests hardhat@^3 is wrong.
4. Node 20 for contracts, Node 22 for frontend SDKs
The Hardhat + @fhevm/* backend stack works on Node >= 20. The frontend SDKs require Node >= 22 — verified per package: @zama-fhe/sdk@3.0.0 and @zama-fhe/relayer-sdk declare engines.node: ">=22"; @zama-fhe/react-sdk is more permissive at >=18. Frontend wiring is covered by the fhevm-frontend skill and the templates/frontend-{next,vite}/ reference apps. When scaffolding the contracts workspace, pinning "engines": {"node": ">= 20"} is sufficient.
5. Mock mode is implicit on the hardhat network
The plugin injects a mock coprocessor on the hardhat network automatically. No flag, no env var. Tests branch on hre.fhevm.isMock if they need to distinguish mock from real-network behaviour. Running against Sepolia uses the real protocol — that is what npm run test:sepolia is for.
Bootstrap sequence
These seven steps take an empty directory to a fully working FHEVM workspace
with a passing mock test suite.
mkdir <workspace-name> && cd <workspace-name>
npm install
npx hardhat vars set MNEMONIC
npx hardhat vars set RPC_KEY
npx hardhat vars set ETHERSCAN_API_KEY
npm run compile
npm test
npx hardhat run scripts/deploy.ts --network sepolia
npm run test:sepolia
Each step's expected outcome:
| Step | Passes when |
|---|
| Scaffold (wizard or manual) | Workspace dir contains package.json + hardhat.config.ts matching the Pinned stack + Minimal hardhat.config.ts sections below; hardhat-deploy NOT present in package.json |
npm install | node_modules/@fhevm/solidity, @fhevm/hardhat-plugin, @fhevm/mock-utils all present; @zama-fhe/relayer-sdk resolves to exactly 0.4.1 (run npm ls @zama-fhe/relayer-sdk to confirm) |
npx hardhat vars set X | Prompts for a value and writes to the Hardhat vars keystore — no file created in the repo |
npm run compile | Produces artifacts/ and types/; no Solidity warnings at the 0.8.27 pragma |
npm test | Mocha test suite runs; hre.fhevm.isMock === true path is exercised |
npx hardhat run scripts/deploy.ts --network sepolia | Emits a tx via hre.ethers.deployContract(...) and prints the deployed address. Do NOT use npx hardhat deploy — see AP-12b. |
npm run test:sepolia | Same test suite against real coprocessor; slower (~30–120s per test) |
What "secrets via hardhat vars" actually stores
npx hardhat vars set MNEMONIC
npx hardhat vars get MNEMONIC
npx hardhat vars list
npx hardhat vars path
The keystore lives outside the repo (typically ~/.config/hardhat-nodejs/vars.json). It is per-user, not per-repo. Agents that suggest vars set in a Dockerfile or CI step should be reminded that CI secrets need HARDHAT_VAR_<NAME> environment variables instead:
- run: npm run test:sepolia
env:
HARDHAT_VAR_MNEMONIC: ${{ secrets.MNEMONIC }}
HARDHAT_VAR_RPC_KEY: ${{ secrets.RPC_KEY }}
HARDHAT_VAR_ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
Pinned stack (as of May 2026)
Copy these versions into package.json if hand-patching a template — or trust
/create-template. hardhat-deploy is intentionally absent (see AP-12b);
deployment uses hre.ethers.deployContract + hardhat run scripts/*.ts.
{
"devDependencies": {
"@fhevm/hardhat-plugin": "^0.4.2",
"@fhevm/mock-utils": "^0.4.2",
"@nomicfoundation/hardhat-chai-matchers": "^2.1.0",
"@nomicfoundation/hardhat-ethers": "^3.1.3",
"@nomicfoundation/hardhat-network-helpers": "^1.1.2",
"@nomicfoundation/hardhat-verify": "^2.1.3",
"@openzeppelin/contracts": "^5.6.1",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@zama-fhe/relayer-sdk": "0.4.1",
"chai": "^4.5.0",
"chai-as-promised": "^8.0.2",
"ethers": "^6.16.0",
"hardhat": "^2.28.4",
"hardhat-gas-reporter": "^2.3.0",
"mocha": "^11.7.5",
"solidity-coverage": "^0.8.17",
"typechain": "^8.3.2",
"typescript": "^5.9.3"
},
"dependencies": {
"@fhevm/solidity": "^0.11.1",
"@fhevm/mock-utils": "^0.4.2",
"@openzeppelin/confidential-contracts": "0.4.0",
"encrypted-types": "^0.0.4"
},
"engines": {
"node": ">= 20",
"npm": ">= 7.0.0"
}
}
@zama-fhe/relayer-sdk is pinned EXACT, not caret. @fhevm/hardhat-plugin@0.4.2
declares an exact-version peer dependency on 0.4.1 (verified in
node_modules/@fhevm/hardhat-plugin/package.json). Caret would let npm resolve
to 0.4.2 (current latest), which the plugin runtime-rejects with
Invalid @zama-fhe/relayer-sdk version. See AP-12c.
Minimal hardhat.config.ts
import "@fhevm/hardhat-plugin";
import "@nomicfoundation/hardhat-chai-matchers";
import "@nomicfoundation/hardhat-ethers";
import "@nomicfoundation/hardhat-network-helpers";
import "@nomicfoundation/hardhat-verify";
import "@typechain/hardhat";
import "hardhat-gas-reporter";
import "solidity-coverage";
import { HardhatUserConfig, vars } from "hardhat/config";
const MNEMONIC = vars.get("MNEMONIC", "test test test test test test test test test test test junk");
const RPC_KEY = vars.get("RPC_KEY", "");
const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY", "");
const config: HardhatUserConfig = {
solidity: {
version: "0.8.27",
settings: { evmVersion: "cancun", optimizer: { enabled: true, runs: 800 } },
},
networks: {
hardhat: {
},
sepolia: {
url: `https://eth-sepolia.g.alchemy.com/v2/${RPC_KEY}`,
accounts: { mnemonic: MNEMONIC },
chainId: 11155111,
},
},
etherscan: { apiKey: { sepolia: ETHERSCAN_API_KEY } },
typechain: { outDir: "types", target: "ethers-v6" },
};
export default config;
Every contract under contracts/ must inherit ZamaEthereumConfig:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import {FHE, euint64, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
contract MyConfidentialCounter is ZamaEthereumConfig {
euint64 private _count;
function increment(externalEuint64 encrypted, bytes calldata proof) external {
euint64 delta = FHE.fromExternal(encrypted, proof);
_count = FHE.add(_count, delta);
FHE.allowThis(_count);
FHE.allow(_count, msg.sender);
}
}
See api-reference.md in the fhevm-contracts skill for the full set of FHE operations, ACL functions, and input-conversion helpers.
Deploy script template
Replace the missing npx hardhat deploy invocation with a small script that
uses hre.ethers.deployContract (ethers v6, no hardhat-deploy dependency).
import hre from "hardhat";
async function main() {
const Counter = await hre.ethers.getContractFactory("MyConfidentialCounter");
const counter = await Counter.deploy();
await counter.waitForDeployment();
const address = await counter.getAddress();
console.log(`MyConfidentialCounter deployed to: ${address}`);
console.log(`Network: ${hre.network.name}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Then: npx hardhat run scripts/deploy.ts --network sepolia. The tx routes
through @nomicfoundation/hardhat-ethers (ethers v6) which accepts the
customData key that @fhevm/hardhat-plugin injects — unlike hardhat-deploy
which transitively pulls ethers v5 and rejects it.
When to Use
- A user asks "how do I set up a new FHEVM project?"
- An agent is about to scaffold a
hardhat.config.ts that imports @fhevm/*.
- An agent needs to wire secrets for a Sepolia deploy or CI job.
- An agent is resolving "which Node version?" or "Hardhat 2 vs 3?" questions.
- Migrating an existing Hardhat project onto the current FHEVM stack pins.
When NOT to Use
- Writing a contract's Solidity — that is
fhevm-contracts.
- Debugging
ACLNotAllowed, a TFHE→FHE rename, or a SepoliaZamaFHEVMConfig typo — that is fhevm-antipatterns.
- Writing tests for an existing workspace — that is
fhevm-testing.
- Wiring a browser / Next.js frontend — that is
fhevm-frontend.
Routing
Which task does the user's prompt match?
- "Bootstrap a new project from scratch." → Bootstrap sequence above.
- "How do I set secrets?" →
hardhat vars section. Reject any .env suggestion.
- "Which versions / Node version?" → Pinned stack + Essential Principle 4.
- "What should
hardhat.config.ts look like?" → Minimal hardhat.config.ts above.
- "Show me a first contract / deploy script." → The
MyConfidentialCounter snippet.
- "CI / GitHub Actions secret wiring." →
HARDHAT_VAR_* snippet in the hardhat vars section.
If the prompt is about writing a specific contract, testing, or the frontend, route via When NOT to Use.
External references
See fhevm-overview for the canonical Zama list. Setup-specific: