Skip to content
This repository was archived by the owner on Jun 25, 2026. It is now read-only.
This repository was archived by the owner on Jun 25, 2026. It is now read-only.

0xc18360217d8f7ab5e7c516566761ea12ce7f9d72 #23

Description

@teheather070

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;

import "./MerkleProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/utils/structs/BitMaps.sol";

/**

  • @dev An ERC20 token for ENS.
  •  Besides the addition of voting capabilities, we make a couple of customisations:
    
  •   - Airdrop claim functionality via `claimTokens`. At creation time the tokens that
    
  •     should be available for the airdrop are transferred to the token contract address;
    
  •     airdrop claims are made from this balance.
    
  •   - Support for the owner (the DAO) to mint new tokens, at up to 2% PA.
    

*/
contract ENSToken is ERC20, ERC20Permit, ERC20Votes, Ownable {
using BitMaps for BitMaps.BitMap;

uint256 public constant minimumMintInterval = 365 days;
uint256 public constant mintCap = 200; // 2%

bytes32 public merkleRoot;
uint256 public nextMint; // Timestamp
uint256 public claimPeriodEnds; // Timestamp
BitMaps.BitMap private claimed;

event MerkleRootChanged(bytes32 merkleRoot);
event Claim(address indexed claimant, uint256 amount);

/**
 * @dev Constructor.
 * @param freeSupply The number of tokens to issue to the contract deployer.
 * @param airdropSupply The number of tokens to reserve for the airdrop.
 * @param _claimPeriodEnds The timestamp at which tokens are no longer claimable.
 */
constructor(
    uint256 freeSupply,
    uint256 airdropSupply,
    uint256 _claimPeriodEnds
)
    ERC20("Ethereum Name Service", "ENS")
    ERC20Permit("Ethereum Name Service")
{
    _mint(msg.sender, freeSupply);
    _mint(address(this), airdropSupply);
    claimPeriodEnds = _claimPeriodEnds;
    nextMint = block.timestamp + minimumMintInterval;
}

/**
 * @dev Claims airdropped tokens.
 * @param amount The amount of the claim being made.
 * @param delegate The address the tokenholder wants to delegate their votes to.
 * @param merkleProof A merkle proof proving the claim is valid.
 */
function claimTokens(uint256 amount, address delegate, bytes32[] calldata merkleProof) external {
    bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
    (bool valid, uint256 index) = MerkleProof.verify(merkleProof, merkleRoot, leaf);
    require(valid, "ENS: Valid proof required.");
    require(!isClaimed(index), "ENS: Tokens already claimed.");
    
    claimed.set(index);
    emit Claim(msg.sender, amount);

    _delegate(msg.sender, delegate);
    _transfer(address(this), msg.sender, amount);
}

/**
 * @dev Allows the owner to sweep unclaimed tokens after the claim period ends.
 * @param dest The address to sweep the tokens to.
 */
function sweep(address dest) external onlyOwner {
    require(block.timestamp > claimPeriodEnds, "ENS: Claim period not yet ended");
    _transfer(address(this), dest, balanceOf(address(this)));
}

/**
 * @dev Returns true if the claim at the given index in the merkle tree has already been made.
 * @param index The index into the merkle tree.
 */
function isClaimed(uint256 index) public view returns (bool) {
    return claimed.get(index);
}

/**
 * @dev Sets the merkle root. Only callable if the root is not yet set.
 * @param _merkleRoot The merkle root to set.
 */
function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner {
    require(merkleRoot == bytes32(0), "ENS: Merkle root already set");
    merkleRoot = _merkleRoot;
    emit MerkleRootChanged(_merkleRoot);
}

/**
 * @dev Mints new tokens. Can only be executed every `minimumMintInterval`, by the owner, and cannot
 *      exceed `mintCap / 10000` fraction of the current total supply.
 * @param dest The address to mint the new tokens to.
 * @param amount The quantity of tokens to mint.
 */
function mint(address dest, uint256 amount) external onlyOwner {
    require(amount <= (totalSupply() * mintCap) / 10000, "ENS: Mint exceeds maximum amount");
    require(block.timestamp >= nextMint, "ENS: Cannot mint yet");

    nextMint = block.timestamp + minimumMintInterval;
    _mint(dest, amount);
}

// The following functions are overrides required by Solidity.

function _afterTokenTransfer(address from, address to, uint256 amount)
    internal
    override(ERC20, ERC20Votes)
{
    super._afterTokenTransfer(from, to, amount);
}

function _mint(address to, uint256 amount)
    internal
    override(ERC20, ERC20Votes)
{
    super._mint(to, amount);
}

function _burn(address account, uint256 amount)
    internal
    override(ERC20, ERC20Votes)
{
    super._burn(account, amount);
}

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions