Hardhat Guide
Complete Contract Implementation
Hardhat Guide
Complete Contract Implementation
Full implementation of the confidential ERC20 token
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:
- 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;
- Deploy using the following command:
pnpm hardhat ignition deploy ./ignition/modules/ConfidentialToken.ts --network baseSepolia
- 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.
Was this page helpful?
On this page