Level: Beginner | Duration: 2 hoursPrerequisites: Module 01: Introduction to FHE
Overview
Before you can write confidential smart contracts, you need a proper development environment. This module walks you through every step — from installing Node.js and Hardhat to deploying and testing your first FHEVM contract.Learning Objectives
By the end of this module, you will be able to:- Install and configure all required tooling for FHEVM development
- Initialize a Hardhat project with the FHEVM Solidity library
- Understand the
ZamaEthereumConfigconfiguration pattern - Write, compile, and deploy a minimal encrypted contract
- Run basic tests against a local FHEVM node
1. Prerequisites & Tooling
Required Software
| Tool | Version | Purpose |
|---|---|---|
| Node.js | >= 20.x | JavaScript runtime |
| npm / pnpm | Latest | Package manager |
| Hardhat | Latest | Smart contract framework |
| Git | Latest | Version control |
Install Node.js
Download from nodejs.org or use a version manager:2. Project Initialization
3. Hardhat Configuration
Yourhardhat.config.ts needs to be configured for FHEVM development:
hardhat.config.ts
4. Understanding ZamaEthereumConfig
Every FHEVM contract must inherit from a configuration contract that sets up the FHE environment. Zama provides a ready-made config:
What ZamaEthereumConfig does
What ZamaEthereumConfig does
- Configures the FHE co-processor address
- Sets up the ACL (Access Control List) contract address
- Initializes the KMS (Key Management Service) verifier
- Provides the decryption oracle address for public decryption operations
ZamaEthereumConfig handles everything.5. Your First Encrypted Contract
We will build up in two stages: first a simplified version to understand the basics, then the real-world pattern used in production contracts.Stage 1: Simplified Version (for learning)
This stripped-down counter initializes from a plaintext value and increments by a hardcoded encrypted1:
contracts/SimpleCounterBasic.sol
Key observations
Key observations
- Named imports —
{FHE, euint32}and{ZamaEthereumConfig}instead of bareimport "..." euint32— An encrypted 32-bit unsigned integer typeFHE.asEuint32(0)— Converts a plaintext value to a ciphertext on-chainFHE.add()— Adds two encrypted values homomorphicallyFHE.allowThis()— Grants the contract itself permission to use the ciphertext
Stage 2: Production Version (HelloFHEVM.sol)
In a real contract, users do not send plaintext values. Instead, they encrypt input on the client side and pass the ciphertext handle plus a proof to the contract:What changed from Stage 1 to Stage 2?
| Aspect | Simplified (Stage 1) | Production (Stage 2) |
|---|---|---|
| Imports | {FHE, euint32} | {FHE, euint32, externalEuint32} |
| User input | None — hardcoded FHE.asEuint32(1) | externalEuint32 encryptedValue, bytes calldata inputProof |
| Input conversion | FHE.asEuint32() from plaintext | FHE.fromExternal(encryptedValue, inputProof) |
| Access control | Only FHE.allowThis() | FHE.allowThis() and FHE.allow(_counter, msg.sender) |
| Events | None | CounterIncremented |
| Visibility | public | external |
Key takeaway: In production FHEVM contracts, encrypted values always arrive from the client via
externalEuint32 + inputProof. The contract calls FHE.fromExternal() to validate and unwrap them, then uses FHE.allow() to grant specific addresses permission to access the result.6. Compiling the Contract
Common Compilation Errors
| Error | Solution |
|---|---|
Source not found: @fhevm/solidity | Run npm install @fhevm/solidity |
ParserError: pragma solidity | Ensure Solidity version matches (^0.8.24 or later) |
7. Writing a Basic Test
Create a test file attest/HelloFHEVM.test.ts. The @fhevm/hardhat-plugin provides a fhevm helper that lets you create encrypted inputs for testing:
test/HelloFHEVM.test.ts
What is happening in the test
What is happening in the test
fhevm.createEncryptedInput(contractAddr, signerAddr)— creates an encrypted input builder bound to a specific contract and sender.add32(5)— adds a 32-bit unsigned integer with plaintext value5to be encrypted.encrypt()— produceshandles(the ciphertext references) andinputProof(the ZK proof that the ciphertext is valid)contract.increment(encrypted.handles[0], encrypted.inputProof)— calls the contract exactly as a real user would
Run tests
8. Project Structure
After setup, your project should look like this:Summary
| Step | Command / Action |
|---|---|
| Initialize project | npx hardhat init |
| Install FHEVM | npm install @fhevm/solidity |
| Install dev tools | npm install --save-dev @fhevm/hardhat-plugin @fhevm/mock-utils |
| Import FHE library | import {FHE, euint32, externalEuint32} from "@fhevm/solidity/lib/FHE.sol"; |
| Import config | import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol"; |
| Inherit config | contract X is ZamaEthereumConfig { } |
| Compile | npx hardhat compile |
| Test | npx hardhat test |
Next Module: Module 03: Encrypted Types