ERC20是一套基于以太坊网络的标准代币发行协议。有了ERC20,开发者们得以高效、可靠、低成本地创造专属自己项目的代币,是以太坊网络第一个真正意义上的应用。ERC20实现了代币转账的基本逻辑:账户余额、转账、授权转账、代币总供给、代币信息(可选):名称,代号,小数位数。
合约只要继承并实现IERC20的所有接口就代表合约满足了ERC20协议的标准。
IERC20接口
IERC20定义了2个事件:Transfer事件和Approval事件,分别在转账和授权时被触发;定义了6个函数,提供了转移代币的基本功能,并允许代币获得批准,以便其他链上第三方使用。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
// 转账时触发
event Transfer(address indexed from, address indexed to, uint256 value);
// 授权时触发
event Approval(address indexed owner, address indexed spender, uint256 value);
// 代币总供应量
function totalSupply() external view returns (uint256);
// 返回账户余额
function balanceOf(address account) external view returns (uint256);
// 转账
function transfer(address to, uint256 amount) external returns (bool);
// 返回授权额度
function allowance(address owner, address spender) external view returns (uint256);
// 授权
function approve(address spender, uint256 amount) external returns (bool);
// 授权转账
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
代币合约
基于IERC20发行一个代币,代币名称My Token,简称MT,铸币函数(mint)和销毁函数(burn)是IERC20中不存在的
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// ERC20代币合约
contract MyToken is IERC20 {
address _owner;
mapping(address => uint) public balances;
mapping(address => mapping(address => uint)) public allowance;
uint public totalSupply;
string public name;
string public symbol;
uint8 public decimals = 18;
modifier onlyOwner() {
require(_owner == msg.sender, "not owner");
_;
}
constructor(string memory _name, string memory _symbol) {
_owner = msg.sender;
name = _name;
symbol = _symbol;
// 启动最初的代币,给合约部署者发放代币
balances[msg.sender] += 10000;
totalSupply += 10000;
}
function balanceOf(address account) external view returns(uint) {
return balances[account];
}
function _transfer(address from, address to, uint amount) internal {
require(to != address(0), "transfer to 0x0");
require(balances[from] >= amount, "balance not enough!");
balances[from] -= amount;
balances[to] += amount;
emit Transfer(from, to, amount);
}
function transfer(address recipient, uint256 amount) external returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
function _approve(address owner, address spender, uint256 amount) internal {
allowance[owner][spender] = amount;
emit Approval(msg.sender, spender, amount);
}
function approve(address spender, uint256 amount) external returns (bool){
_approve(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool){
require(allowance[sender][msg.sender] >= amount, "allowance not enough!");
_approve(sender, msg.sender, allowance[sender][msg.sender] - amount);
_transfer(sender, recipient, amount);
return true;
}
// 铸造代币:从address(0)转到指定地址
// 只有合约拥有者或特殊权限的人才能调用
// 由于是转账,也要触发Transfer事件
function mint(address account,uint amount) external onlyOwner {
require(account != address(0), "mint to 0x0");
balances[account] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
// 销毁代币:将指定地址的币转到address(0)
// 只有合约拥有者或特殊权限的人才能调用
// 由于是转账,也要触发Transfer事件
function burn(address account, uint amount) external onlyOwner {
require(account != address(0), "burn from 0x0");
require(balances[account] >= amount, "no more token to burn");
balances[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
水龙头合约
很多项目早期的时候为了扩大影响力,让别人可以免费领取代币,下面实现一个水龙头合约,限制每个钱包地址只能领取一次,一次100个代币:
整体思路:
1、代币合约向水龙头合约发送一定数量代币
2、水龙头合约调用代币合约的transfer方法转账
contract Faucet {
// 一次领取的数量
uint private constant AMMOUNTONCE = 100;
// 领取记录的映射
mapping(address => bool) public recordAddr;
// 代币的合约地址
address public tokenAddr;
// 构造函数设置代币合约地址
constructor(address _tokenAddr) {
tokenAddr = _tokenAddr;
}
event SendToken(address indexed receiver, uint indexed amount);
// 领取代币
function receive() external {
require(!recordAddr[msg.sender], "already received token!");
IERC20 token = IERC20(tokenAddr);
require(token.balanceOf(address(this)) >= AMMOUNTONCE, "token not enough!");
token.transfer(msg.sender, AMMOUNTONCE);
recordAddr[msg.sender] = true;
emit SendToken(msg.sender, AMMOUNTONCE);
}
}
部署水龙头合约,并传入MT代币的合约地址:
向水龙头合约发送1000个代币:
remix地址切换为0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,并执行水龙头合约receiveToken函数,查询余额已经有100个代币了:
空投合约
所谓空投,就是作为项目方,在项目的首发阶段为了做一些运营活动活跃用户,开发新用户,在初期给用户空投项目方的代币,或者ETH的行为。为了拿到空投资格,用户通常需要完成一些简单的任务,因为每次接收空投的用户很多,项目方不可能一笔一笔的转账。利用智能合约批量发放ERC20代币,可以显著提高空投效率。
// 空投合约
contract Airdrop {
// 接收ETH
receive() external payable {}
// 获取余额
function getBalance() external view returns(uint) {
return address(this).balance;
}
// 计算总量
function getSum(uint[] calldata _amounts) public pure returns(uint _sum) {
for(uint i=0; i<_amounts.length; i++) {
_sum += _amounts[i];
}
}
// 空投ETH
function batchTransferETH(address[] calldata _addresses, uint[] calldata _amounts) public payable returns(bool) {
require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL");
uint sumAmount = getSum(_amounts);
require(address(this).balance >= sumAmount, "amount not enough!");
for(uint i=0; i<_addresses.length; i++) {
payable(_addresses[i]).transfer(_amounts[i]);
}
return true;
}
// 空投token
// 向空投合约授权一定数量代币,用transferFrom函数发送空投
function batchTransferToken(address _tokenAddr, address[] calldata _addresses, uint[] calldata _amounts) public returns(bool) {
require(_addresses.length == _amounts.length, "Lengths of Addresses and Amounts NOT EQUAL");
uint sumAmount = getSum(_amounts);
IERC20 token = IERC20(_tokenAddr);
require(token.allowance(msg.sender, address(this)) >= sumAmount, "token not enough!");
for(uint i = 0; i < _addresses.length; i++) {
token.transferFrom(msg.sender, _addresses[i], _amounts[i]);
}
return true;
}
}
测试空投EHT:
1、向合约转入3个ETH:
2、查看余额:
3、执行batchTransferETH函数,传入参数:[“0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2”, “0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db”]、[1000000000000000000,2000000000000000000],再次查看余额已经为0:
空投的两个地址也接收到了相应数量的ETH:
测试空投代币:
1、部署上面的ERC20合约,先mint 10000 MT代币:
2、利用ERC20代币合约中的approve()函数,给Airdrop空投合约授权5000 个MT代币:
3、执行batchTransferToken函数,传入参数:[“0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2”, “0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db”]、[1000,2000],再次查看余额已经为0:
在ERC20合约中查询余额:
代币空投成功!