如何使用 Solidity 和 Hardhat 编写 ERC20 代币预售智能合约

2025-06-07

如何使用 Solidity 和 Hardhat 编写 ERC20 代币预售智能合约

介绍

本文将向您介绍 ERC20 代币以及如何使用 Solidity 和 Hardhat 编写 ERC20 代币预售智能合约。

理论

什么是 ERC20 代币?

  • ERC-20 是一种技术标准;它用于以太坊区块链上所有用于代币实现的智能合约,并提供了所有基于以太坊的代币必须遵循的规则列表。
  • 您可以在继续之前检查所有 ERC20功能。

什么是首次代币发行(ICO)?

  • 首次代币发行(ICO)是加密货币行业的一种融资机制,类似于传统金融领域的首次公开募股(IPO)。

智能合约的开发

ERC20代币合约

  • 代币规范
    • 代币名称:MARK 代币
    • 代币符号:MRK
    • 代币十进制:18
    • 总供应量:100,000,000,000
    • 代币类型:ERC20
  • 代币合约

我们将使用 OpenZeppelin ERC20 合约来创建我们的代币,并向合约所有者铸造 1000 亿个代币。

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MKT is ERC20 {
    uint256 private _totalSupply = 100_000_000_000;

    constructor() ERC20("MARK Token", "MKT") {
        _mint(msg.sender, _totalSupply * 10 ** decimals());
    }
}
Enter fullscreen mode Exit fullscreen mode

代币预售合约

  • 预售规格

    • 预售供应量:100亿(10%)
    • 预售期:30天
    • 软顶:300000 USDT
    • 硬顶:1000000 USDT
    • 使用 ETH 和 USDT 购买代币
  • 主要功能

    • 轮次管理
    • 宣称
    • 提取
  • 执行

我们将使用 Chainlink 预言机获取 USDT 和 ETH 的最新价格。您也可以使用 Uniswap 或 PancakeSwap 获取 USDT 和 ETH 的价格。

使用 ETH 购买 MARK 代币
  function buy_with_eth()
        external
        payable
        nonReentrant
        whenNotPaused
        canPurchase(_msgSender(), msg.value)
        returns (bool)
    {

        uint256 amount_in_usdt = (msg.value * get_eth_in_usdt()) / 1e30;
        require(
            round_list[current_round_index].usdt_round_raised + amount_in_usdt <
                round_list[current_round_index].usdt_round_cap,
            "BUY ERROR : Too much money already deposited."
        );

        uint256 amount_in_tokens = (amount_in_usdt *
            round_list[current_round_index].usdt_to_token_rate) * 1e3;

        users_list[_msgSender()].usdt_deposited += amount_in_usdt;
        users_list[_msgSender()].tokens_amount += amount_in_tokens;

        round_list[current_round_index].usdt_round_raised += amount_in_usdt;

        (bool sent,) = round_list[current_round_index].wallet.call{value: msg.value}("");
        require(sent, "Failed to send Ether");

        emit Deposit(_msgSender(), 1, amount_in_usdt, amount_in_tokens);

        return true;
    }
Enter fullscreen mode Exit fullscreen mode

首先,该函数通过修饰符进行了几项检查:

  • nonReentrant 可防止重入攻击
  • whenNotPaused 确保合同没有暂停
  • canPurchase 验证预售是否有效以及购买金额是否有效

接下来,它使用 Chainlink 预言机价格信息计算已发送 ETH 的 USDT 等值

  function get_eth_in_usdt() internal view returns (uint256) {
        (, int256 price, , , ) = price_feed.latestRoundData();
        price = price * 1e10;
        return uint256(price);
    }
Enter fullscreen mode Exit fullscreen mode

并检查购买金额是否在圆形上限之内。

接下来,根据本轮汇率,计算相当于 USDT 的代币数量:

接下来,它更新用户和回合的状态:

  • 记录用户的 USDT 存款和代币分配
  • 更新本轮募集的 USDT 总额
  • 将 ETH 转入本轮的钱包地址

最后,它会发出一个包含购买详情的存款事件,并在交易成功时返回 true。

使用 USDT 购买 MARK 代币

与 ETH 购买类似,我们可以用 USDT 定义 buy 函数,如下所示。唯一的区别在于,它处理直接的 USDT 转账,而不是使用价格预言机进行转换。

 function buy_with_usdt(uint256 amount_)
        external
        nonReentrant
        whenNotPaused
        canPurchase(_msgSender(), amount_)
        returns (bool)
    {
        uint256 amount_in_usdt = amount_;
        require(
            round_list[current_round_index].usdt_round_raised + amount_in_usdt <
                round_list[current_round_index].usdt_round_cap,
            "BUY ERROR : Too much money already deposited."
        );

        uint256 allowance = usdt_interface.allowance(msg.sender, address(this));

        require(amount_ <= allowance, "BUY ERROR: Allowance is too small!");

        (bool success_receive, ) = address(usdt_interface).call(
            abi.encodeWithSignature(
                "transferFrom(address,address,uint256)",
                msg.sender,
                round_list[current_round_index].wallet,
                amount_in_usdt
            )
        );

        require(success_receive, "BUY ERROR: Transaction has failed!");

        uint256 amount_in_tokens = (amount_in_usdt *
            round_list[current_round_index].usdt_to_token_rate) * 1e3;

        users_list[_msgSender()].usdt_deposited += amount_in_usdt;
        users_list[_msgSender()].tokens_amount += amount_in_tokens;

        round_list[current_round_index].usdt_round_raised += amount_in_usdt;

        emit Deposit(_msgSender(), 3, amount_in_usdt, amount_in_tokens);

        return true;
    }
Enter fullscreen mode Exit fullscreen mode
领取令牌
function claim_tokens() external returns (bool) {
        require(presale_ended, "CLAIM ERROR : Presale has not ended!");
        require(
            users_list[_msgSender()].tokens_amount != 0,
            "CLAIM ERROR : User already claimed tokens!"
        );
        require(
            !users_list[_msgSender()].has_claimed,
            "CLAIM ERROR : User already claimed tokens"
        );

        uint256 tokens_to_claim = users_list[_msgSender()].tokens_amount;
        users_list[_msgSender()].tokens_amount = 0;
        users_list[_msgSender()].has_claimed = true;

        (bool success, ) = address(token_interface).call(
            abi.encodeWithSignature(
                "transfer(address,uint256)",
                msg.sender,
                tokens_to_claim
            )
        );
        require(success, "CLAIM ERROR : Couldn't transfer tokens to client!");

        return true;
    }
Enter fullscreen mode Exit fullscreen mode

此功能

  • 检查预售是否已结束
  • 验证用户是否有可领取的令牌,并且之前没有领取过
  • 检索并存储用户可领取的代币数量
  • 将用户的代币余额重置为 0,并标记为已认领
  • 使用代币合约接口将代币转移给用户
  • 索赔成功则返回 true
提现代币
 function withdrawToken(address tokenContract, uint256 amount) external onlyOwner {
        IERC20(tokenContract).transfer(_msgSender(), amount);
    }
Enter fullscreen mode Exit fullscreen mode

此功能

  • 仅限合同所有者通过 onlyOwner 修饰符
  • 允许所有者从合约中提取任何 ERC20 代币
  • 以代币合约地址和金额作为参数
  • 将指定金额转移到所有者的地址
回合管理

我们还需要定义函数来管理回合:

  function start_next_round(
        address payable wallet_,
        uint256 usdt_to_token_rate_,
        uint256 usdt_round_cap_
    ) external onlyOwner {
        current_round_index = current_round_index + 1;

        round_list.push(
            Round(wallet_, usdt_to_token_rate_, 0, usdt_round_cap_ * (10**6))
        );
    }

  function set_current_round(
        address payable wallet_,
        uint256 usdt_to_token_rate_,
        uint256 usdt_round_cap_
    ) external onlyOwner {
        round_list[current_round_index].wallet = wallet_;
        round_list[current_round_index]
            .usdt_to_token_rate = usdt_to_token_rate_;
        round_list[current_round_index].usdt_round_cap = usdt_round_cap_ * (10**6);
    }

  function get_current_round()
        external
        view
        returns (
            address,
            uint256,
            uint256,
            uint256
        )
    {
        return (
            round_list[current_round_index].wallet,
            round_list[current_round_index].usdt_to_token_rate,
            round_list[current_round_index].usdt_round_raised,
            round_list[current_round_index].usdt_round_cap
        );
    }

  function get_current_raised() external view returns (uint256) {
        return round_list[current_round_index].usdt_round_raised;
    }
Enter fullscreen mode Exit fullscreen mode

结论

此 ERC20 代币合约和预售合约是进行代币预售的全面且安全的解决方案。它提供管理预售轮次、充值 USDT、领取代币、提取代币以及轮次管理等功能。该合约设计灵活,可针对不同的预售场景进行定制。

文章来源:https://dev.to/marksantiago02/how-to-write-ico-smart-contract-using-solidity-and-hardhat-4pmg
PREV
学习 JavaScript Promises 什么是 Promises?为什么要使用 Promises?处理多个 Promises
NEXT
如何编写代币价格预言机智能合约