docs/solidity-guides/hardhat/write_task.md
In this section, you'll learn how to write a custom FHEVM Hardhat task.
Writing tasks is a gas-efficient and flexible way to test your FHEVM smart contracts on the Sepolia network. Creating a custom task is straightforward.
task:decrypt-count tasks included in the file tasks/FHECounter.ts file, located in the fhevm-hardhat-template repository.{% stepper %} {% step %}
Let’s start with a simple example: fetching the current counter value from a basic Counter.sol contract.
If you're already familiar with Hardhat and custom tasks, the TypeScript code below should look familiar and be easy to follow:
task("task:get-count", "Calls the getCount() function of Counter Contract")
.addOptionalParam("address", "Optionally specify the Counter contract address")
.setAction(async function (taskArguments: TaskArguments, hre) {
const { ethers, deployments } = hre;
const CounterDeployment = taskArguments.address
? { address: taskArguments.address }
: await deployments.get("Counter");
console.log(`Counter: ${CounterDeployment.address}`);
const counterContract = await ethers.getContractAt("Counter", CounterDeployment.address);
const clearCount = await counterContract.getCount();
console.log(`Clear count : ${clearCount}`);
});
Now, let’s modify this task to work with FHEVM encrypted values.
{% endstep %} {% step %}
First, comment out the existing logic so we can incrementally add the necessary changes for FHEVM integration.
task("task:get-count", "Calls the getCount() function of Counter Contract")
.addOptionalParam("address", "Optionally specify the Counter contract address")
.setAction(async function (taskArguments: TaskArguments, hre) {
// const { ethers, deployments } = hre;
// const CounterDeployment = taskArguments.address
// ? { address: taskArguments.address }
// : await deployments.get("Counter");
// console.log(`Counter: ${CounterDeployment.address}`);
// const counterContract = await ethers.getContractAt("Counter", CounterDeployment.address);
// const clearCount = await counterContract.getCount();
// console.log(`Clear count : ${clearCount}`);
});
Next, rename the task by replacing:
task("task:get-count", "Calls the getCount() function of Counter Contract")
With:
task("task:decrypt-count", "Calls the getCount() function of Counter Contract")
This updates the task name from task:get-count to task:decrypt-count, reflecting that it now includes decryption logic for FHE-encrypted values.
{% endstep %} {% step %}
Replace the line:
// const { ethers, deployments } = hre;
With:
const { ethers, deployments, fhevm } = hre;
await fhevm.initializeCLIApi();
{% hint style="warning" %}
Calling initializeCLIApi() is essential. Unlike built-in Hardhat tasks like test or compile, which automatically initialize the FHEVM runtime environment, custom tasks require you to call this function explicitly.
Make sure to call it at the very beginning of your task to ensure the environment is properly set up.
{% endhint %}
{% endstep %} {% step %}
getCount from the FHECounter contractReplace the following commented-out lines:
// const CounterDeployment = taskArguments.address
// ? { address: taskArguments.address }
// : await deployments.get("Counter");
// console.log(`Counter: ${CounterDeployment.address}`);
// const counterContract = await ethers.getContractAt("Counter", CounterDeployment.address);
// const clearCount = await counterContract.getCount();
With the FHEVM equivalent:
const FHECounterDeployment = taskArguments.address
? { address: taskArguments.address }
: await deployments.get("FHECounter");
console.log(`FHECounter: ${FHECounterDeployment.address}`);
const fheCounterContract = await ethers.getContractAt("FHECounter", FHECounterDeployment.address);
const encryptedCount = await fheCounterContract.getCount();
if (encryptedCount === ethers.ZeroHash) {
console.log(`encrypted count: ${encryptedCount}`);
console.log("clear count : 0");
return;
}
Here, encryptedCount is an FHE-encrypted euint32 primitive. To retrieve the actual value, we need to decrypt it in the next step.
{% endstep %} {% step %}
Now replace the following commented-out line:
// console.log(`Clear count : ${clearCount}`);
With the decryption logic:
const signers = await ethers.getSigners();
const clearCount = await fhevm.userDecryptEuint(
FhevmType.euint32,
encryptedCount,
FHECounterDeployment.address,
signers[0],
);
console.log(`Encrypted count: ${encryptedCount}`);
console.log(`Clear count : ${clearCount}`);
At this point, your custom Hardhat task is fully configured to work with FHE-encrypted values and ready to run!
{% endstep %} {% step %}
npx hardhat node
npx hardhat deploy --network localhost
npx hardhat task:decrypt-count --network localhost
{% endstep %} {% step %}
npx hardhat deploy --network sepolia
npx hardhat task:decrypt-count --network sepolia
{% endstep %} {% endstepper %}