ERC7984 Standard
This example demonstrates how to create a confidential token using OpenZeppelin's smart contract library powered by ZAMA's FHEVM.
// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.24;
import {Ownable2Step, Ownable} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
import {EthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
import {ERC7984} from "@openzeppelin/confidential-contracts/token/ERC7984.sol";
contract ERC7984Example is EthereumConfig, ERC7984, Ownable2Step {
    constructor(
        address owner,
        uint64 amount,
        string memory name_,
        string memory symbol_,
        string memory tokenURI_
    ) ERC7984(name_, symbol_, tokenURI_) Ownable(owner) {
        euint64 encryptedAmount = FHE.asEuint64(amount);
        _mint(owner, encryptedAmount);
    }
}import { expect } from 'chai';
import { ethers, fhevm } from 'hardhat';
describe('ERC7984Example', function () {
  let token: any;
  let owner: any;
  let recipient: any;
  let other: any;
  const INITIAL_AMOUNT = 1000;
  const TRANSFER_AMOUNT = 100;
  beforeEach(async function () {
    [owner, recipient, other] = await ethers.getSigners();
    // Deploy ERC7984Example contract
    token = await ethers.deployContract('ERC7984Example', [
      owner.address,
      INITIAL_AMOUNT,
      'Confidential Token',
      'CTKN',
      'https://example.com/token'
    ]);
  });
  describe('Initialization', function () {
    it('should set the correct name', async function () {
      expect(await token.name()).to.equal('Confidential Token');
    });
    it('should set the correct symbol', async function () {
      expect(await token.symbol()).to.equal('CTKN');
    });
    it('should set the correct token URI', async function () {
      expect(await token.tokenURI()).to.equal('https://example.com/token');
    });
    it('should mint initial amount to owner', async function () {
      // Verify that the owner has a balance (without decryption for now)
      const balanceHandle = await token.confidentialBalanceOf(owner.address);
      expect(balanceHandle).to.not.be.undefined;
    });
  });
  describe('Transfer Process', function () {
    it('should transfer tokens from owner to recipient', async function () {
      // Create encrypted input for transfer amount
      const encryptedInput = await fhevm
        .createEncryptedInput(await token.getAddress(), owner.address)
        .add64(TRANSFER_AMOUNT)
        .encrypt();
      // Perform the transfer
      await expect(token
        .connect(owner)
        ['confidentialTransfer(address,bytes32,bytes)'](
          recipient.address,
          encryptedInput.handles[0],
          encryptedInput.inputProof
        )).to.not.be.reverted;
      // Check that both addresses have balance handles (without decryption for now)
      const recipientBalanceHandle = await token.confidentialBalanceOf(recipient.address);
      const ownerBalanceHandle = await token.confidentialBalanceOf(owner.address);
      expect(recipientBalanceHandle).to.not.be.undefined;
      expect(ownerBalanceHandle).to.not.be.undefined;
    });
    it('should allow recipient to transfer received tokens', async function () {
      // First transfer from owner to recipient
      const encryptedInput1 = await fhevm
        .createEncryptedInput(await token.getAddress(), owner.address)
        .add64(TRANSFER_AMOUNT)
        .encrypt();
      await expect(token
        .connect(owner)
        ['confidentialTransfer(address,bytes32,bytes)'](
          recipient.address,
          encryptedInput1.handles[0],
          encryptedInput1.inputProof
        )).to.not.be.reverted;
      // Second transfer from recipient to other
      const encryptedInput2 = await fhevm
        .createEncryptedInput(await token.getAddress(), recipient.address)
        .add64(50) // Transfer half of what recipient received
        .encrypt();
      await expect(token
        .connect(recipient)
        ['confidentialTransfer(address,bytes32,bytes)'](
          other.address,
          encryptedInput2.handles[0],
          encryptedInput2.inputProof
        )).to.not.be.reverted;
      // Check that all addresses have balance handles (without decryption for now)
      const otherBalanceHandle = await token.confidentialBalanceOf(other.address);
      const recipientBalanceHandle = await token.confidentialBalanceOf(recipient.address);
      expect(otherBalanceHandle).to.not.be.undefined;
      expect(recipientBalanceHandle).to.not.be.undefined;
    });
    it('should revert when trying to transfer more than balance', async function () {
      const excessiveAmount = INITIAL_AMOUNT + 100;
      const encryptedInput = await fhevm
        .createEncryptedInput(await token.getAddress(), recipient.address)
        .add64(excessiveAmount)
        .encrypt();
      await expect(
        token
          .connect(recipient)
          ['confidentialTransfer(address,bytes32,bytes)'](
            other.address,
            encryptedInput.handles[0],
            encryptedInput.inputProof
          )
      ).to.be.revertedWithCustomError(token, 'ERC7984ZeroBalance')
        .withArgs(recipient.address);
    });
    it('should revert when transferring to zero address', async function () {
      const encryptedInput = await fhevm
        .createEncryptedInput(await token.getAddress(), owner.address)
        .add64(TRANSFER_AMOUNT)
        .encrypt();
      await expect(
        token
          .connect(owner)
          ['confidentialTransfer(address,bytes32,bytes)'](
            ethers.ZeroAddress,
            encryptedInput.handles[0],
            encryptedInput.inputProof
          )
      ).to.be.revertedWithCustomError(token, 'ERC7984InvalidReceiver')
        .withArgs(ethers.ZeroAddress);
    });
  });
});import { ethers } from "hardhat";
import type { ERC7984Example } from "../../types";
import type { ERC7984Example__factory } from "../../types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
export async function deployERC7984ExampleFixture(owner: HardhatEthersSigner) {
  // Deploy ERC7984Example with initial supply
  const ERC7984ExampleFactory = (await ethers.getContractFactory(
    "ERC7984Example",
  )) as ERC7984Example__factory;
  const ERC7984Example = (await ERC7984ExampleFactory.deploy(
    owner.address, // Owner address
    1000, // Initial amount
    "Confidential Token",
    "CTKN",
    "https://example.com/token",
  )) as ERC7984Example;
  const ERC7984ExampleAddress = await ERC7984Example.getAddress();
  return {
    ERC7984Example,
    ERC7984ExampleAddress,
  };
}Last updated