ERC-20 代币实战:从铸造到转账的完整开发指南

📅 2026/7/19 1:57:11
ERC-20 代币实战:从铸造到转账的完整开发指南
1. 引言什么是 ERC-20ERC-20 是以太坊上最流行的代币标准它定义了一套统一的接口规则使得任何遵循该标准的代币都能与钱包、交易所和去中心化应用DApp无缝交互。简单来说ERC-20 为代币的创建和管理提供了“通用说明书”。本文将带你从零开始实战开发一个具备铸造Mint和转账Transfer功能的 ERC-20 代币。你将学习到ERC-20 标准的核心接口。如何使用 Solidity 和 Hardhat 编写安全的代币合约。如何实现铸造功能为特定地址生成新代币。如何实现并测试转账功能。将合约部署到测试网并进行验证。2. 环境与工具准备在开始编码前请确保你的开发环境已就绪。2.1 安装 Node.js 和 npm访问 Node.js 官网 下载并安装 LTS 版本。安装完成后在终端运行以下命令验证node--versionnpm--version2.2 初始化 Hardhat 项目我们将使用 Hardhat 作为开发、测试和部署框架。mkdirerc20-tutorialcderc20-tutorialnpminit-ynpminstall--save-dev hardhat npx hardhat init在初始化向导中选择“Create a JavaScript project”并安装所有推荐的依赖。2.3 安装 OpenZeppelin 合约库OpenZeppelin 提供了经过严格审计、安全可靠的合约实现我们将基于它来构建我们的代币。npminstallopenzeppelin/contracts3. ERC-20 标准核心接口解析ERC-20 标准规定了代币合约必须实现的最小接口。理解这些接口是开发的基础。3.1 必须实现的函数totalSupply(): 返回代币的总供应量。balanceOf(address account): 返回指定account地址的代币余额。transfer(address recipient, uint256 amount): 从调用者地址向recipient地址转账amount数量的代币。allowance(address owner, address spender): 返回owner授权给spender使用的代币数量。approve(address spender, uint256 amount): 授权spender可以从调用者地址转出最多amount数量的代币。transferFrom(address sender, address recipient, uint256 amount): 在授权额度内从sender地址向recipient地址转账amount数量的代币。3.2 必须触发的事件Transfer(address indexed from, address indexed to, uint256 value): 当代币被转账包括铸造和销毁时触发。Approval(address indexed owner, address indexed spender, uint256 value): 当授权成功时触发。4. 编写我们的代币合约MyToken我们将创建一个名为MyToken的可铸造 ERC-20 代币。4.1 创建合约文件在contracts/目录下创建MyToken.sol文件。4.2 合约代码详解// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; // 导入 OpenZeppelin 的 ERC-20 实现和拥有者权限控制 import openzeppelin/contracts/token/ERC20/ERC20.sol; import openzeppelin/contracts/access/Ownable.sol; /** * title MyToken - 一个可铸造的 ERC-20 代币 * dev 继承自 ERC20 和 Ownable只有合约所有者可以铸造新代币。 */ contract MyToken is ERC20, Ownable { /** * dev 构造函数初始化代币名称和符号并将合约部署者设为所有者。 * param initialOwner 初始合约所有者地址拥有铸造权限。 */ constructor(address initialOwner) ERC20(MyToken, MTK) Ownable(initialOwner) { // 构造函数体可以为空或者执行一些初始设置 // 例如可以在这里进行初始铸造_mint(initialOwner, 1000000 * 10 ** decimals()); } /** * dev 铸造新代币的函数仅所有者可调用。 * param to 接收新铸造代币的地址。 * param amount 要铸造的代币数量以最小单位计例如 wei。 */ function mint(address to, uint256 amount) public onlyOwner { _mint(to, amount); } }代码解析ERC20(MyToken, MTK): 继承 OpenZeppelin 的ERC20合约并传入代币名称和符号。Ownable(initialOwner): 继承Ownable合约实现所有权管理。initialOwner在部署时被设为所有者。onlyOwner: 这是一个函数修饰器modifier确保只有合约所有者才能调用mint函数。_mint(to, amount): 调用 ERC-20 内部函数安全地向地址to铸造amount数量的代币并自动触发Transfer事件。5. 编译与本地测试编写测试是确保合约安全可靠的关键步骤。5.1 编写测试脚本在test/目录下创建MyToken.test.js文件。const{expect}require(chai);const{ethers}require(hardhat);describe(MyToken,function(){letMyToken;letmyToken;letowner;letaddr1;letaddr2;beforeEach(asyncfunction(){// 获取合约工厂MyTokenawaitethers.getContractFactory(MyToken);// 获取测试账户[owner,addr1,addr2]awaitethers.getSigners();// 部署合约将部署者owner设为初始所有者myTokenawaitMyToken.deploy(owner.address);awaitmyToken.waitForDeployment();});describe(部署与初始化,function(){it(应该设置正确的名称和符号,asyncfunction(){expect(awaitmyToken.name()).to.equal(MyToken);expect(awaitmyToken.symbol()).to.equal(MTK);});it(应该将部署者设为所有者,asyncfunction(){expect(awaitmyToken.owner()).to.equal(owner.address);});});describe(铸造功能,function(){it(只有所有者可以铸造代币,asyncfunction(){// 所有者铸造成功awaitexpect(myToken.connect(owner).mint(addr1.address,1000)).to.emit(myToken,Transfer).withArgs(ethers.ZeroAddress,addr1.address,1000);expect(awaitmyToken.balanceOf(addr1.address)).to.equal(1000);// 非所有者铸造失败awaitexpect(myToken.connect(addr1).mint(addr2.address,500)).to.be.revertedWithCustomError(myToken,OwnableUnauthorizedAccount);});});describe(转账功能,function(){beforeEach(asyncfunction(){// 先给 addr1 铸造一些代币用于测试转账awaitmyToken.connect(owner).mint(addr1.address,1000);});it(应该允许用户转账代币,asyncfunction(){awaitexpect(myToken.connect(addr1).transfer(addr2.address,300)).to.emit(myToken,Transfer).withArgs(addr1.address,addr2.address,300);expect(awaitmyToken.balanceOf(addr1.address)).to.equal(700);expect(awaitmyToken.balanceOf(addr2.address)).to.equal(300);});it(转账金额不能超过余额,asyncfunction(){awaitexpect(myToken.connect(addr1).transfer(addr2.address,1500)).to.be.revertedWithCustomError(myToken,ERC20InsufficientBalance);});});});5.2 运行测试在项目根目录下运行npx hardhattest如果一切正常你将看到所有测试用例通过。6. 部署到测试网让我们将合约部署到 Sepolia 测试网。6.1 配置网络和私钥在hardhat.config.js中添加 Sepolia 网络配置。从 Alchemy 或 Infura 获取一个 Sepolia RPC URL。将你的测试账户私钥切勿使用主网私钥添加到环境变量或配置中。一个简化的hardhat.config.js示例require(nomicfoundation/hardhat-toolbox);require(dotenv).config();/** type import(hardhat/config).HardhatUserConfig */module.exports{solidity:0.8.20,networks:{sepolia:{url:process.env.SEPOLIA_RPC_URL||,accounts:process.env.PRIVATE_KEY?[process.env.PRIVATE_KEY]:[],},},};6.2 编写部署脚本在scripts/目录下创建deploy.jsasyncfunctionmain(){const[deployer]awaitethers.getSigners();console.log(使用账户部署合约:,deployer.address);constMyTokenawaitethers.getContractFactory(MyToken);// 部署时将部署者地址作为 initialOwner 传入constmyTokenawaitMyToken.deploy(deployer.address);awaitmyToken.waitForDeployment();console.log(MyToken 合约已部署到地址:,awaitmyToken.getAddress());console.log(代币名称:,awaitmyToken.name());console.log(代币符号:,awaitmyToken.symbol());console.log(合约所有者:,awaitmyToken.owner());}main().catch((error){console.error(error);process.exitCode1;});6.3 执行部署运行以下命令进行部署npx hardhat run scripts/deploy.js--networksepolia部署成功后控制台会输出合约地址。你可以使用 Etherscan Sepolia 查看你的合约。7. 与合约交互部署后你可以通过 Hardhat Console 或编写前端 DApp 与合约交互。7.1 使用 Hardhat Console 铸造和转账npx hardhat console--networksepolia在控制台中// 连接到已部署的合约constMyTokenawaitethers.getContractFactory(MyToken);constmyTokenawaitMyToken.attach(YOUR_DEPLOYED_CONTRACT_ADDRESS);// 获取当前账户部署者const[owner,addr1]awaitethers.getSigners();// 所有者铸造 1000 MTK 给 addr1consttxawaitmyToken.mint(addr1.address,ethers.parseUnits(1000,18));awaittx.wait();console.log(铸造成功);// 检查 addr1 的余额constbalanceawaitmyToken.balanceOf(addr1.address);console.log(addr1 余额:,ethers.formatUnits(balance,18),MTK);// addr1 向 owner 转账 500 MTKconsttx2awaitmyToken.connect(addr1).transfer(owner.address,ethers.parseUnits(500,18));awaittx2.wait();console.log(转账成功);7.2 前端集成简要你可以使用 ethers.js 或 web3.js 库在网页中连接用户钱包如 MetaMask并调用合约的mint和transfer函数。核心逻辑与上述控制台脚本类似。8. 安全注意事项与最佳实践使用经过审计的库始终优先使用 OpenZeppelin 等成熟库避免重复造轮子。权限控制像mint这样的敏感操作必须严格限制访问权限如onlyOwner。正确处理小数ERC-20 使用decimals变量处理小数默认是 18。在交互时注意单位转换如parseUnits,formatUnits。充分测试覆盖正常路径、边界情况和异常情况如余额不足、未授权访问。考虑升级性如果未来可能需要修复 bug 或添加功能可以考虑使用可升级合约模式如 OpenZeppelin Upgrades。主网部署前务必在测试网上进行全面测试并考虑进行第三方审计。9. 总结通过本文你已完成了一个具备铸造和转账功能的完整 ERC-20 代币项目。你学会了ERC-20 标准的核心。基于 OpenZeppelin 编写安全合约。使用 Hardhat 进行开发、测试和部署。实现并测试了核心的铸造与转账逻辑。下一步你可以尝试为代币添加更多功能如销毁Burn、暂停Pausable、快照Snapshot或实现代币销售ICO逻辑。区块链开发的世界广阔而有趣祝你探索愉快相关资源OpenZeppelin Contracts DocumentationHardhat DocumentationEthereum Official WebsiteSolidity Documentation