内容装饰

在区块链世界中,代币是数字资产的重要载体,而ERC20(Ethereum Request for Comments 20)是以太坊上最标准、应用最广泛的代币技术标准,它定义了一套统一的接口规范,使得代币可以在以太坊生态中无缝流转、交易和被钱包、交易所等工具识别,无论是初学者还是开发者,掌握ERC20代币开发都是进入区块链领域的重要一步,本文将带你从零开始,手把手教你开发一个符合ERC20标准的代币,涵盖环境搭建、代码编写、测试到部署的全流程。

ERC20标准核心规范

在开始开发前,我们需要先理解ERC20的核心要求,ERC20标准定义了以下必须实现的接口函数事件

必需接口函数

  1. name():返回代币全称(如 "USD Coin")。
  2. symbol():返回代币符号(如 "USDC")。
  3. decimals()配图
de>:返回代币精度(小数位数,通常为18)。
  • totalSupply():返回代币总供应量。
  • balanceOf(address owner):查询指定地址的代币余额。
  • transfer(address to, uint256 amount):向指定地址转移代币。
  • transferFrom(address from, address to, uint256 amount):从指定地址转移代币(需授权)。
  • allowance(address owner, address spender):查询授权额度。
  • approve(address spender, uint256 amount):授权第三方地址转移代币。
  • 必需事件

    1. Transfer(address indexed from, address indexed to, uint256 value):代币转移时触发。
    2. Approval(address indexed owner, address indexed spender, uint256 value):授权时触发。

    开发环境准备

    在编写ERC20代币之前,我们需要搭建以下开发环境:

    安装Node.js和npm

    ERC20代币通常使用Solidity语言编写,而Solidity的开发依赖Node.js环境,从nodejs官网下载并安装LTS版本(建议v16+),安装完成后可通过终端运行以下命令验证:

    node -v  # 查看Node.js版本
    npm -v   # 查看npm版本

    安装Hardhat

    Hardhat是一个以太坊开发环境,支持编译、测试、部署Solidity合约,功能强大且易于上手,通过以下命令安装:

    npm init -y  # 初始化npm项目
    npm install --save-dev hardhat

    安装完成后,在项目目录下运行:

    npx hardhat  # 初始化Hardhat项目

    根据提示选择"Create a JavaScript project"(JavaScript项目),并确认安装依赖(如@nomicfoundation/hardhat-toolbox)。

    安装OpenZeppelin合约库

    OpenZeppelin是一个开源的智能合约库,提供了经过审计的ERC20标准实现,可直接复用,避免重复造轮子和安全漏洞,安装命令:

    npm install @openzeppelin/contracts

    编写ERC20代币合约

    创建合约文件

    在Hardhat项目中,合约文件通常存放在contracts/目录下,创建一个名为MyToken.sol的文件:

    touch contracts/MyToken.sol

    编写合约代码

    打开MyToken.sol,编写以下代码(基于OpenZeppelin的ERC20实现):

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.20;
    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    contract MyToken is ERC20 {
        constructor(string memory name, string memory symbol) ERC20(name, symbol) {
            // 初始供应量:1亿代币,18位小数(ERC20默认)
            _mint(msg.sender, 100000000 * 10**decimals());
        }
    }

    代码解析

    • SPDX-License-Identifier: MIT:声明MIT开源协议(可选但推荐)。
    • pragma solidity ^0.8.20:指定Solidity编译器版本(0.8.20及以上,兼容性较好)。
    • import "@openzeppelin/contracts/token/ERC20/ERC20.sol":导入OpenZeppelin的ERC20标准合约。
    • contract MyToken is ERC20:定义MyToken合约,继承自ERC20,自动实现所有ERC20接口。
    • constructor:构造函数,在合约部署时调用。_mint()是ERC20库中的内部函数,用于向部署地址(msg.sender)铸造代币,100000000 * 10**decimals()确保代币精度正确(如18位小数时,实际供应量为1亿*10^18)。

    编译和测试合约

    编译合约

    在终端运行以下命令编译合约:

    npx hardhat compile

    编译成功后,合约的ABI(应用二进制接口)和字节码会生成在artifacts/contracts/MyToken.sol/MyToken.json中,这是后续部署和交互的关键文件。

    编写测试脚本

    测试是确保合约安全性的重要环节,在test/目录下创建myToken.test.js文件(JavaScript测试):

    const { expect } = require("chai");
    const { ethers } = require("hardhat");
    describe("MyToken", function () {
        let MyToken;
        let myToken;
        let owner;
        let addr1;
        let addr2;
        beforeEach(async function () {
            // 获取合约实例
            MyToken = await ethers.getContractFactory("MyToken");
            // 部署合约(参数为name和symbol)
            myToken = await MyToken.deploy("My Token", "MTK");
            await myToken.deployed();
            // 获取测试账户
            [owner, addr1, addr2] = await ethers.getSigners();
        });
        it("Should set the right name and symbol", async function () {
            expect(await myToken.name()).to.equal("My Token");
            expect(await myToken.symbol()).to.equal("MTK");
        });
        it("Should assign the initial supply to the owner", async function () {
            const ownerBalance = await myToken.balanceOf(owner.address);
            expect(ownerBalance).to.equal(100000000 * 10**18); // 1亿代币
        });
        it("Should transfer tokens between accounts", async function () {
            // owner转账1000代币给addr1
            await myToken.transfer(addr1.address, 1000 * 10**18);
            const addr1Balance = await myToken.balanceOf(addr1.address);
            expect(addr1Balance).to.equal(1000 * 10**18);
            // addr1转账500代币给addr2
            await myToken.connect(addr1).transfer(addr2.address, 500 * 10**18);
            const addr2Balance = await myToken.balanceOf(addr2.address);
            expect(addr2Balance).to.equal(500 * 10**18);
        });
        it("Should allow approved user to transferFrom", async function () {
            // owner授权addr1使用1000代币
            await myToken.approve(addr1.address, 1000 * 10**18);
            const allowance = await myToken.allowance(owner.address, addr1.address);
            expect(allowance).to.equal(1000 * 10**18);
            // addr1通过transferFrom从owner账户转移500代币给addr2
            await myToken.connect(addr1).transferFrom(owner.address, addr2.address, 500 * 10**18);
            const addr2Balance = await myToken.balanceOf(addr2.address);
            expect(addr2Balance).to.equal(500 * 10**18);
        });
    });
    • 验证代币名称和符号是否正确。
    • 验证初始供应量是否正确分配给部署者。
    • 测试普通转账功能。
    • 测试授权和transferFrom功能。

    运行测试:

    npx hardhat test

    如果所有测试通过,说明合约逻辑正确。

    部署合约到以太坊网络

    选择网络

    ERC20代币可部署到以太坊主网、测试网(如Goerli、Sepolia)或本地网络(如Hardhat Network),这里以本地Hardhat Network为例,无需消耗真实ETH。

    配置部署脚本

    scripts/目录下创建deploy.js文件:

    async function main() {
        // 获取合约工厂
        const MyToken = await ethers.getContractFactory("MyToken");
        // 部署合约(参数为name和symbol)
        const myToken = await MyToken.deploy("My Token", "MTK");
        // 等待部署完成
        await my

    返回栏目