Here’s the complete implementation of the confidential ERC20 token contract. You can find this code in ConfidentialERC20.sol if you’re using the Hardhat template.

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;



import "@inco/lightning/src/Lib.sol";

import "@openzeppelin/contracts/access/Ownable2Step.sol";



contract ConfidentialERC20 is Ownable2Step {

    // Events

    event Transfer(address indexed from, address indexed to);

    event Approval(address indexed owner, address indexed spender);

    event Mint(address indexed to, uint256 amount);

    event UserBalanceDecrypted(address indexed user, uint256 decryptedAmount);



    // State variables

    uint256 public _totalSupply;

    string public _name;

    string public _symbol;

    uint8 public constant decimals = 18;



    // Encrypted state

    mapping(address => euint256) internal balances;

    mapping(address => mapping(address => euint256)) internal allowances;

    mapping(uint256 => address) internal requestIdToUserAddress;



    constructor() Ownable(msg.sender) {

        _name = "Confidential USD";

        _symbol = "cUSD";

    }



    // Minting functions

    function mint(uint256 mintedAmount) public virtual onlyOwner {

        balances[owner()] = e.add(

            balances[owner()],

            e.asEuint256(mintedAmount)

        );

        e.allow(balances[owner()], address(this));

        e.allow(balances[owner()], owner());

        _totalSupply += mintedAmount;

        emit Mint(owner(), mintedAmount);

    }



    function _mint(bytes calldata encryptedAmount) public virtual {

        balances[msg.sender] = e.add(

            balances[msg.sender],

            e.newEuint256(encryptedAmount, msg.sender)

        );

        e.allow(balances[msg.sender], address(this));

        e.allow(balances[msg.sender], owner());

        e.allow(balances[msg.sender], msg.sender);

    }



    // Transfer functions

    function transfer(

        address to,

        bytes calldata encryptedAmount

    ) public virtual returns (bool) {

        transfer(to, e.newEuint256(encryptedAmount, msg.sender));

        return true;

    }



    function transfer(

        address to,

        euint256 amount

    ) public virtual returns (bool) {

        ebool canTransfer = e.ge(balances[msg.sender], amount);

        _transfer(msg.sender, to, amount, canTransfer);

        return true;

    }



    // View functions

    function balanceOf(address wallet) public view virtual returns (euint256) {

        return balances[wallet];

    }



    // Approval functions

    function approve(

        address spender,

        bytes calldata encryptedAmount

    ) public virtual returns (bool) {

        approve(spender, e.newEuint256(encryptedAmount, msg.sender));

        return true;

    }



    function approve(

        address spender,

        euint256 amount

    ) public virtual returns (bool) {

        _approve(msg.sender, spender, amount);

        emit Approval(msg.sender, spender);

        return true;

    }



    function _approve(

        address owner,

        address spender,

        euint256 amount

    ) internal virtual {

        allowances[owner][spender] = amount;

        e.allow(amount, address(this));

        e.allow(amount, owner);

        e.allow(amount, spender);

    }



    // Allowance functions

    function allowance(

        address owner,

        address spender

    ) public view virtual returns (euint256) {

        return _allowance(owner, spender);

    }

    

    function _allowance(

        address owner,

        address spender

    ) internal view virtual returns (euint256) {

        return allowances[owner][spender];

    }



    // TransferFrom functions

    function transferFrom(

        address from,

        address to,

        bytes calldata encryptedAmount

    ) public virtual returns (bool) {

        transferFrom(from, to, e.newEuint256(encryptedAmount, msg.sender));

        return true;

    }



    function transferFrom(

        address from,

        address to,

        euint256 amount

    ) public virtual returns (bool) {

        ebool isTransferable = _updateAllowance(from, msg.sender, amount);

        _transfer(from, to, amount, isTransferable);

        return true;

    }



    // Internal helper functions

    function _updateAllowance(

        address owner,

        address spender,

        euint256 amount

    ) internal virtual returns (ebool) {

        euint256 currentAllowance = _allowance(owner, spender);

        ebool allowedTransfer = e.ge(currentAllowance, amount);

        ebool canTransfer = e.ge(balances[owner], amount);

        ebool isTransferable = e.select(

            canTransfer,

            allowedTransfer,

            e.asEbool(false)

        );

        _approve(

            owner,

            spender,

            e.select(

                isTransferable,

                e.sub(currentAllowance, amount),

                currentAllowance

            )

        );

        return isTransferable;

    }



    function _transfer(

        address from,

        address to,

        euint256 amount,

        ebool isTransferable

    ) internal virtual {

        euint256 transferValue = e.select(

            isTransferable,

            amount,

            e.asEuint256(0)

        );

        euint256 newBalanceTo = e.add(balances[to], transferValue);

        balances[to] = newBalanceTo;

        e.allow(newBalanceTo, address(this));

        e.allow(newBalanceTo, to);



        euint256 newBalanceFrom = e.sub(balances[from], transferValue);

        balances[from] = newBalanceFrom;

        e.allow(newBalanceFrom, address(this));

        e.allow(newBalanceFrom, from);



        emit Transfer(from, to);

    }

}

Contract Structure

Imports

  • Inco libraries for encryption
  • OpenZeppelin for ownership management

State

  • Public token details
  • Encrypted balances and allowances
  • Request tracking

Functions

  • Standard ERC20 interface
  • Encrypted variants for privacy
  • Internal helpers

Deployment

To deploy the contract using Hardhat Ignition:

  1. Create a deployment module in ignition/modules/ConfidentialToken.ts:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";



const ConfidentialERC20Module = buildModule("ConfidentialERC20Module", (m) => {

  const confidentialERC20Module = m.contract("ConfidentialERC20");

  return { confidentialERC20Module };

});



export default ConfidentialERC20Module;
  1. Deploy using the following command:
pnpm hardhat ignition deploy ./ignition/modules/ConfidentialToken.ts --network baseSepolia
  1. Run tests to verify the deployment:
pnpm hardhat test --network baseSepolia

The contract will be deployed with:

  • Name: “Confidential USD”
  • Symbol: “cUSD”
  • Decimals: 18
  • Owner: The deploying address (msg.sender)

Make sure to save the deployed contract address for future interactions. You’ll need it when integrating with your frontend or other contracts.