从 A 到 Z 构建 ERC-20 代币(Hardhat + OpenZeppelin)
Source: Dev.to
免责声明
仅用于教育目的。不是货币,没有内在价值,也未与任何现实世界的品牌或稳定币关联。
我写这篇文章的原因
许多进入加密领域的人会问相同的问题:
- “为什么我的钱包里会出现一个随机代币?”
- “为什么我看到余额却无法卖出?”
- “ERC‑20 代币到底是如何创建的?”
- “如果代币是假的,为什么它看起来很真实?”
真正理解这一点的最佳方式很简单:创建你自己的 ERC‑20 代币一次。当你这样做时,一切突然变得清晰:余额、钱包、授权、假代币、授权。
我们将构建的内容
我们将构建一个简洁的教育性 ERC‑20 代币,具备以下功能:
- OpenZeppelin ERC‑20 实现
- 基于角色的访问控制
- 可暂停的转账
- 铸造功能
- 使用 Hardhat 部署
- 将代币添加到 MetaMask
- 了解钱包如何检测余额
1. 什么是 ERC‑20(通俗英文)
ERC‑20 不是一种货币。至少,ERC‑20 代币需要实现以下函数:
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function totalSupply() external view returns (uint256);
function name() external view returns (string);
function symbol() external view returns (string);
function decimals() external view returns (uint8);
重要
代币必须拥有市场和流动性,才能产生价格和真实价值。没有流动性 → 没有价格 → 没有真实价值。
2. 环境搭建
要求
- Node.js(LTS)
- npm
- MetaMask 钱包
- Sepolia 测试 ETH
- RPC 提供商(Alchemy、Infura 等)
创建项目
mkdir my-token
cd my-token
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
npx hardhat
当出现提示时,选择 Create a JavaScript project。
3. 安装 OpenZeppelin
OpenZeppelin 提供经过审计的、生产级别的智能合约。
npm install @openzeppelin/contracts
4. 编写 ERC‑20 代币合约
Create contracts/MyToken.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/*
MyToken — an educational ERC-20 token.
NOT money. NOT a stablecoin.
*/
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
contract MyToken is ERC20, AccessControl, Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
constructor(uint256 initialSupply)
ERC20("My Educational Token", "MYT")
{
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount)
external
onlyRole(MINTER_ROLE)
{
_mint(to, amount);
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
function _update(address from, address to, uint256 amount)
internal
override
whenNotPaused
{
super._update(from, to, amount);
}
}
关键概念
name和symbol只是字符串。- 合约存储余额。
- 代币只有在被铸造后才存在。
- 如果不铸造,余额将为零。
5. Hardhat 配置
创建(或编辑)hardhat.config.js:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.20",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
};
6. 环境变量
创建一个 .env 文件(切勿提交此文件):
SEPOLIA_RPC=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
PRIVATE_KEY=0xYOUR_PRIVATE_KEY
7. 部署脚本
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying with address:", deployer.address);
const initialSupply = hre.ethers.parseUnits("1000", 18);
const MyToken = await hre.ethers.getContractFactory("MyToken");
const token = await MyToken.deploy(initialSupply);
await token.waitForDeployment();
console.log("Token deployed at:", await token.getAddress());
}
main().catch(console.error);
8. 编译并部署
npx hardhat compile
npx hardhat run scripts/deploy.js --network sepolia
您将收到一个合约地址。
9. 将代币添加到 MetaMask
- 将 MetaMask 切换到 Sepolia 网络。
- 前往 Assets → Import Token。
- 粘贴合约地址。MetaMask 会自动填充符号和小数位。
如果余额显示为 0,请检查:
- 网络是否正确
- 您铸造的地址是否实际收到了代币
- 小数位设置
- 重新加载 MetaMask(清除缓存)
10. 为什么代币会“凭空出现”
钱包 不 存储代币。钱包只是调用:
balanceOf(yourAddress)
因此,任何人都可以在未经请求或许可的情况下向你的地址铸造代币,而你的钱包仍会显示它们。
11. 为什么名称、符号和图标毫无意义
- 没有全局的代币名称注册表。
- 链上没有商标强制执行。
- 没有“官方代币”标识。
任何人都可以部署一个合约,使用任意名称、符号、小数位或徽标。唯一可靠的代币标识是其合约地址。
12. 核心安全教训
了解这些基础会让许多骗局变得显而易见:
- “免费代币” → 无意义的余额
- 假稳定币 → 只是带有好听名字的 ERC‑20 合约
- 授权陷阱 → 危险的权限
知识胜过恐惧。
结论
ERC‑20 代币只是智能合约。创建自己的代币一次是最快了解余额、授权、铸造以及围绕它们的安全陷阱的方式。