ERC20 是标准的以太坊 Token 协议,它也是一个合约代码,只要在该合约内部实现了特定的 6 个方法,就会被系统判定为代币合约,具体总结为:6 个必要接口,2 个必要事件,3 个可选接口,详情如下:
6 个必要接口
// 代币总发行量
function totalSupply() public view returns(uint256)
// 内部维护一个 mapping,返回余额
function balanceOf(address _owner) public view returns(uint 256 balance)
// token 持有人调用,进行转账(写操作,花钱)
// 张三 -> 李四, 100 * 10**18
function transfer(address _to, uint256 _value) public returns(bool success)
// 张三是 token 持有人, 作为 owner, 1w
// 张三将 1w 授权给李四
function approve(address _spender, uint256 _value) public returns(bool success)
allowanace[张三][李四] += 1w
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
// 李四是被授权人,spender
// 王五是接受 token 的人,receiver
// 李四是张三的授权人,李四调用 transferFrom 给王五转账
function transferFrom(address _from, address _to, uint256 _value) public returns(bool success)
2 个必要事件
// 在tranfer 和transferFrom内部使用
event Transfer(address indexed _from, address indexed _to, uint256 _value)
// 在approve方法中使用
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
3 个可选接口
// Token 名称
function name() public view returns (string)
// Token 标识,比如 ETH
function symbol() public view returns (string)
// USDT: 6, 10000000 * 10**6
// WBTC: 8, 10000000 * 10**8
// 其他Token:18, 10000000 * 10**18
// 精度
function decimals() public view returns (uint8)
常见使用场景
- 张三为 Token 持有人,李四为张三的委托人(被授权),王五是收款人
- 张三调用 USDT 的 Approve 方法,授权李四可以支配 50w Token,调用格式为:USDT.Approve(李四, 50w)
- 此时在 USDT 合约内部,会通过 allowance 进行记录:allowance[张三][李四] += 50w
- 此时李四便可以支配张三的 50w USDT,并调用 transferFrom 给王五转转,调用形式为:USDT.transferFrom(张三, 王五, 1w)
- 转账过后,张三的 Token 减少 1w,王五点 Token 增加 1w,李四被授权的额度减少 1w
实战发行 ERC20 Token
我们发行一个 worldCupToken,用于后续对参与竞猜的用户进行奖励,token信息如下:
- Name:World Cup Token
- Symbol:WCT
- Decimals:18
- TotalSupply:1w
首先在 node_modules 目录下安装标准 openzeppeline 包以引入 ERC20
npm i @openzeppelin/contracts
创建 WorldCupToken.sol,如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WorldCupToken is ERC20 {
// 一次性 mint 出来,不允许后续 mint
// name:名称,symbol:token 标识,toalSupply:总发行量
constructor(
string memory name_,
string memory symbol_,
uint256 totalSupply_
) ERC20(name_, symbol_) {
_mint(msg.sender, totalSupply_);
}
}
创建部署脚本 deployMockERc20.ts
import { ethers } from "hardhat";
async function main() {
const totalSupply = ethers.utils.parseUnits('10000000', 18)
console.log('totalSupply:', totalSupply);
const FHTToken = await ethers.getContractFactory('WorldCupToken');
const fht = await FHTToken.deploy("World Cup Token", "WCT", totalSupply);
await fht.deployed();
console.log(`new World Cup Token deployed to ${fht.address}`);
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
编译部署合约:
# new World Cup Token deployed to 0x8fe664FA864C61054D2dec3dEB54b204a427d8A8
npx hardhat run scripts/deployMockERc20.ts --network goerli
// verify
npx hardhat verify --contract contracts/WorldCupToken.sol:WorldCupToken 0x8fe664FA864C61054D2dec3dEB54b204a427d8A8 "World Cup Token" "WCT" "10000000000000000000000000" --network goerli