Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.inco.org/llms.txt

Use this file to discover all available pages before exploring further.

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.28;

import {inco, e, ebool, euint256} from "@inco/lightning/src/Lib.sol";
import {asBool} from "@inco/lightning/src/shared/TypeUtils.sol";
import {DecryptionAttestation} from "@inco/lightning/src/lightning-parts/DecryptionAttester.types.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";

contract ConfidentialERC20 is Ownable2Step {
    // Errors
    error InsufficientFees();

    // Events
    event Transfer(address indexed from, address indexed to, euint256 amount);
    event Approval(
        address indexed owner,
        address indexed spender,
        euint256 amount
    );
    event Mint(address indexed to, uint256 amount);
    event EncryptedMint(address indexed to, euint256 amount);

    // State variables
    euint256 public totalSupply;
    string public _name;
    string public _symbol;
    uint8 public constant decimals = 18;

    // Mappings for balances and allowances
    mapping(address => euint256) internal balances;
    mapping(address => mapping(address => euint256)) internal allowances;

    // Constructor to set the token name, symbol, and owner
    constructor() Ownable(msg.sender) {
        _name = "Confidential USD";
        _symbol = "cUSD";
    }

    // Helper functions
    function _requireFee() internal view {
        if (msg.value < inco.getFee()) revert InsufficientFees();
    }

    // Mint function to create tokens and add to the owner's balance
    function mint(uint256 mintAmount) public virtual onlyOwner {
        euint256 amount = e.asEuint256(mintAmount);
        balances[owner()] = e.add(balances[owner()], amount);
        e.allow(balances[owner()], address(this));
        e.allow(balances[owner()], owner());

        totalSupply = e.add(totalSupply, amount);
        e.reveal(totalSupply);
        emit Mint(owner(), mintAmount);
    }

    // Encrypted mint function to create tokens and add to the sender's balance
    function encryptedMint(
        bytes calldata encryptedAmount
    ) public payable virtual /*onlyOwner*/ {
        _requireFee();
        euint256 amount = e.newEuint256(encryptedAmount, msg.sender);
        e.allow(amount, address(this));
        if(euint256.unwrap(balances[msg.sender]) == bytes32(0)) {
            balances[msg.sender] = amount;
        } else {
            balances[msg.sender] = e.add(balances[msg.sender], amount);
        }
        e.allow(balances[msg.sender], address(this));
        e.allow(balances[msg.sender], owner());
        e.allow(balances[msg.sender], msg.sender);

        totalSupply = e.add(totalSupply, amount);
        e.reveal(totalSupply);
        emit EncryptedMint(msg.sender, amount);
    }

    // Transfer function for EOAs using encrypted inputs
    function transfer(
        address to,
        bytes calldata encryptedAmount
    ) public payable virtual returns (bool) {
        _requireFee();
        transfer(
            to,
            e.newEuint256(encryptedAmount, msg.sender)
        );
        return true;
    }

    // Transfer function for contracts
    function transfer(
        address to,
        euint256 amount
    ) public virtual returns (bool) {
        e.allow(amount, address(this));
        ebool canTransfer = e.ge(balances[msg.sender], amount);

        _transfer(msg.sender, to, amount, canTransfer);
        return true;
    }

    // Retrieves the balance handle of a specified wallet
    function balanceOf(address wallet) public view virtual returns (euint256) {
        return balances[wallet];
    }

    // Retrieves the total supply handle
    function getTotalSupply() public view virtual returns (euint256) {
        return totalSupply;
    }

    // Approve function for EOAs with encrypted inputs
    function approve(
        address spender,
        bytes calldata encryptedAmount
    ) public payable virtual returns (bool) {
        _requireFee();
        approve(spender, e.newEuint256(encryptedAmount, msg.sender));
        return true;
    }

    // Approve function for contracts
    function approve(
        address spender,
        euint256 amount
    ) public virtual returns (bool) {
        _approve(msg.sender, spender, amount);
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    // Internal function to handle allowance approvals
    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);
    }

    // Retrieves the allowance handle for a spender
    function allowance(
        address owner,
        address spender
    ) public view virtual returns (euint256) {
        return _allowance(owner, spender);
    }

    // Internal function to retrieve an allowance handle
    function _allowance(
        address owner,
        address spender
    ) internal view virtual returns (euint256) {
        return allowances[owner][spender];
    }

    // TransferFrom function for EOAs with encrypted inputs
    function transferFrom(
        address from,
        address to,
        bytes calldata encryptedAmount
    ) public payable virtual returns (bool) {
        _requireFee();
        transferFrom(
            from,
            to,
            e.newEuint256(encryptedAmount, msg.sender)
        );
        return true;
    }

    // TransferFrom function for contracts
    function transferFrom(
        address from,
        address to,
        euint256 amount
    ) public virtual returns (bool) {
        e.allow(amount, address(this));

        ebool isTransferable = _updateAllowance(from, msg.sender, amount);
        _transfer(from, to, amount, isTransferable);
        return true;
    }

    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;
    }

    // Internal transfer function for encrypted token transfer
    function _transfer(
        address from,
        address to,
        euint256 amount,
        ebool isTransferable
    ) internal virtual {
        euint256 transferValue = e.select(
            isTransferable,
            amount,
            e.asEuint256(0)
        );

        if(euint256.unwrap(balances[to]) == bytes32(0)) {
            balances[to] = transferValue;
        } else {
            balances[to] = e.add(balances[to], transferValue);
        }

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

        balances[from] = e.sub(balances[from], transferValue);
        e.allow(balances[from], address(this));
        e.allow(balances[from], from);

        emit Transfer(from, to, transferValue);
    }

}

Contract Structure

Imports

  • Inco libraries for encryption - OpenZeppelin for ownership management

State

  • Public token details - Encrypted total supply - Encrypted balances and allowances

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.