如何使用 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());
}
}
代币预售合约
-
预售规格
- 预售供应量: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;
}
首先,该函数通过修饰符进行了几项检查:
- 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);
}
并检查购买金额是否在圆形上限之内。
接下来,根据本轮汇率,计算相当于 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;
}
领取令牌
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;
}
此功能
- 检查预售是否已结束
- 验证用户是否有可领取的令牌,并且之前没有领取过
- 检索并存储用户可领取的代币数量
- 将用户的代币余额重置为 0,并标记为已认领
- 使用代币合约接口将代币转移给用户
- 索赔成功则返回 true
提现代币
function withdrawToken(address tokenContract, uint256 amount) external onlyOwner {
IERC20(tokenContract).transfer(_msgSender(), amount);
}
此功能
- 仅限合同所有者通过 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;
}
结论
此 ERC20 代币合约和预售合约是进行代币预售的全面且安全的解决方案。它提供管理预售轮次、充值 USDT、领取代币、提取代币以及轮次管理等功能。该合约设计灵活,可针对不同的预售场景进行定制。
文章来源:https://dev.to/marksantiago02/how-to-write-ico-smart-contract-using-solidity-and-hardhat-4pmg