Deploy using our tutorial

Step 1: Clone our tutorial for encrypted-erc20-dapp github repo.

git clone https://github.com/Inco-fhevm/tutorial-encrypted-erc20-dapp

Step 2: Replace the contract ABI from "erc20ABI.json" in the src file with your deployed ABI file.

mv path/to/your/deployedABI.json path/to/your/tutorial-encrypted-erc20-dapp/src/abi/erc20ABI.json

Step 3: Setting up FHEVM Instance

Create /utils/fhevm.js:

import { BrowserProvider } from "ethers";
import { createInstance, initFhevm } from "fhevmjs";

let fhevmInstance = null;

export const provider = new BrowserProvider(window.ethereum);

export const createFhevmInstance = async () => {
  if (!fhevmInstance) {
    await initFhevm();
    fhevmInstance = await createInstance({
      chainId: 21097,
      networkUrl: "https://validator.rivest.inco.org/",
      gatewayUrl: "https://gateway.rivest.inco.org/",
      aclAddress: "0x2Fb4341027eb1d2aD8B5D9708187df8633cAFA92",
    });
  }
  return fhevmInstance;
};

export const getFhevmInstance = async () => {
  if (!fhevmInstance) {
    fhevmInstance = await createFhevmInstance();
  }
  return fhevmInstance;
};

Step 4: Add Utility Functions

Create /utils/utils.js:

export const toHexString = (bytes) =>
  bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");

Step 5: Create the Main Component

Create ConfidentialERC20.jsx with the following sections:

import { useEffect, useState } from "react";
import { Contract, ethers } from "ethers";
import {
  Wallet, Lock, RefreshCw, HelpCircle, Key,
  DollarSign, ArrowUpIcon, LogOut,
} from "lucide-react";
import { getFhevmInstance } from "@/utils/fhevm";
import { BrowserProvider } from "ethers";

const CONTRACT_ADDRESS = "YOUR_CONTRACT_ADDRESS";
const mintABI = ["Your_mint_function_abi_here"];

const ConfidentialERC20 = ({ mintABI }) => {
  const [signer, setSigner] = useState(null);
  const [amountMint, setAmountMint] = useState("");
  const [isMinting, setIsMinting] = useState(false);
  const [isDecrypting, setIsDecrypting] = useState(false);
  const [userBalance, setUserBalance] = useState("Hidden");
  const [instance, setInstance] = useState(null);
  
  useEffect(() => {
    const getInstance = async () => {
      const provider = new BrowserProvider(window.ethereum);
      const signer = await provider.getSigner();
      const instance = await getFhevmInstance();
      setInstance(instance);
      setSigner(signer);
    };

    getInstance();
    // Remaining account switch logic ...
  }, []);
   
   if (!instance) return null;
   
  // ... component logic
};

Step 6: Mint Function

Define the mint function to mint tokens by encrypting the amount and sending it to the contract. This process uses the FHEVM instance to create a encrypted input for the minting transaction.

const mint = async (event) => {
  event.preventDefault();
  setIsMinting(true);
  try {
    const contract = new Contract(CONTRACT_ADDRESS, mintABI, signer);
    const input = await instance.createEncryptedInput(
      CONTRACT_ADDRESS,
      await signer.getAddress()
    );
    input.add64(ethers.parseUnits(amountMint.toString(), 6));
    const encryptedInput = input.encrypt();

    const response = await contract._mint(
      encryptedInput.handles[0],
      "0x" + toHexString(encryptedInput.inputProof)
    );
    await response.wait();
    setAmountMint("");
  } catch (e) {
    console.log(e);
  } finally {
    setIsMinting(false);
  }
};

Explanation: This function encrypts the mint amount and then calls the _mint function in the contract, passing the encrypted data. This way, the actual minting amount remains hidden, ensuring confidentiality in token minting.

Step 7: Reading Encrypted Balance

Use the reencrypt function to securely retrieve and decrypt the user's balance. This function uses a public-private key pair along with an EIP-712 signature to ensure secure decryption.

const reencrypt = async () => {
  setIsDecrypting(true);
  try {
    const contract = new Contract(CONTRACT_ADDRESS, erc20ABI, signer);
    const balanceHandle = await contract.balanceOf(await signer.getAddress());

    const { publicKey, privateKey } = instance.generateKeypair();
    const eip712 = instance.createEIP712(publicKey, CONTRACT_ADDRESS);
    const signature = await signer.signTypedData(
      eip712.domain,
      { Reencrypt: eip712.types.Reencrypt },
      eip712.message
    );

    if (balanceHandle.toString() === "0") {
      setUserBalance("0");
    } else {
      const balanceResult = await instance.reencrypt(
        balanceHandle,
        privateKey,
        publicKey,
        signature.replace("0x", ""),
        CONTRACT_ADDRESS,
        await signer.getAddress()
      );
      setUserBalance(balanceResult.toString());
    }
  } catch (e) {
    console.log(e);
  } finally {
    setIsDecrypting(false);
  }
}

Behind the scenes, the instance calls the gateway API to request encryption of the balance. The gateway then checks access permissions through ACL to verify whether the user is authorized to access the ciphertext. If authorized, the gateway makes a secure call to the Key Management Service (KMS) for re-encryption. The KMS re-encrypts the balance and returns it to the client DApp, which then decrypts the balance locally using the user’s private key.

Step 8: Finally, run the following:

npm install
npm run dev

You should see a screen like this popping up at the local host:

Congrats! We have successfully deployed a confidential ERC-20 contract. Now you can press decrypt your balance and it would return the mint amount. Please reach out to us if you have any further questions.

Last updated