Write FHEVM-enabled Hardhat Tasks

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.

Prerequisite

1

A Basic Hardhat Task.

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 CounterDeployement = taskArguments.address
      ? { address: taskArguments.address }
      : await deployments.get("Counter");
    console.log(`Counter: ${CounterDeployement.address}`);

    const counterContract = await ethers.getContractAt("Counter", CounterDeployement.address);

    const clearCount = await counterContract.getCount();

    console.log(`Clear count    : ${clearCount}`);
});

Now, let’s modify this task to work with FHEVM encrypted values.

2

Comment Out Existing Logic and rename

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 CounterDeployement = taskArguments.address
    //   ? { address: taskArguments.address }
    //   : await deployments.get("Counter");
    // console.log(`Counter: ${CounterDeployement.address}`);

    // const counterContract = await ethers.getContractAt("Counter", CounterDeployement.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.

3

Initialize FHEVM CLI API

Replace the line:

    // const { ethers, deployments } = hre;

With:

    const { ethers, deployments, fhevm } = hre;

    await fhevm.initializeCLIApi();
4

Call the view function getCount from the FHECounter contract

Replace the following commented-out lines:

    // const CounterDeployement = taskArguments.address
    //   ? { address: taskArguments.address }
    //   : await deployments.get("Counter");
    // console.log(`Counter: ${CounterDeployement.address}`);

    // const counterContract = await ethers.getContractAt("Counter", CounterDeployement.address);

    // const clearCount = await counterContract.getCount();

With the FHEVM equivalent:

    const FHECounterDeployement = taskArguments.address
      ? { address: taskArguments.address }
      : await deployments.get("FHECounter");
    console.log(`FHECounter: ${FHECounterDeployement.address}`);

    const fheCounterContract = await ethers.getContractAt("FHECounter", FHECounterDeployement.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.

5

Decrypt the encrypted count value.

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,
      FHECounterDeployement.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!

6

Step 6: Run your custom task using Hardhat Node

Start the Local Hardhat Node:

  • Open a new terminal window.

  • From the root project directory, run the following:

npx hardhat node

Deploy the FHECounter smart contract on the local Hardhat Node

npx hardhat deploy --network localhost

Run your custom task

npx hardhat task:decrypt-count --network localhost
7

Step 7: Run your custom task using Sepolia

Deploy the FHECounter smart contract on Sepolia Testnet (if not already deployed)

npx hardhat deploy --network sepolia

Execute your custom task

npx hardhat task:decrypt-count --network sepolia

Last updated