在Web3的世界中,我们会接触到许许多多的token,如ERC20标准的token,即同质化代币,就是我们常说的在交易所可交易的虚拟货币,还有如ERC721,即非同质化代币,也是我们熟知的NFT,这篇文章主要给大家讲讲如何使用Solidity发行各类的ERC20代币。
在
Web3
的世界中,我们会接触到许许多多的token
,如ERC20
标准的token
,即同质化代币,就是我们常说的在交易所可交易的虚拟货币,还有如ERC721
,即非同质化代币,也是我们熟知的NFT
,这篇文章主要给大家讲讲如何使用Solidity
发行各类的ERC20
代币。
下面的各类花式发币法是参考了 崔棉大师的花式发币法,不过其中的Solidity
版本和OpenZeppelin
都使用了最新版本Solidity 8
和OpenZeppelin 5
,写法也都有相应的变化。
ERC20
代币ERC20FixedSupply.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// 固定总量代币
contract ERC20FixedSupply is ERC20 {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 totalSupply // 发行总量
) ERC20(name, symbol) {
_mint(msg.sender, totalSupply);
}
}
固定总量代币即在发行时就发行全部到指定账户,这里是会把全部币增发到合约部署人账户里。后期该币也不能再次增发。
ERC20
代币ERC20WithBurnable.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
// 可销毁代币
contract ERC20WithBurnable is ERC20, ERC20Burnable {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 initialSupply // 初始发行总量
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
}
}
可销毁代币可以在发行之后,通过调用burn(uint256 amount)
方法来销毁自己手上的代币。
ERC20
代币ERC20WithMintable.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// 可增发代币
contract ERC20WithMintable is ERC20, Ownable {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 initialSupply // 初始发行总量
) ERC20(name, symbol) Ownable(msg.sender) {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
可增发代币可以在发行之后,owner
也随时可以调用mint(address to, uint256 amount)
进行代币的增发。
ERC20
代币ERC20WithMintableByAccess.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
// 通过角色控制的可增发代币
contract ERC20WithMintableByAccess is ERC20, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 initialSupply // 初始发行总量
) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
}
普通的可增发代币,一般只有owner
才有增发的权限,如果我们想要让多个属于该角色的账号都有增发权限,就可以使用OpenZeppelin
提供的角色权限控制合约,其他如销毁权限等也可这样控制。
ERC20
代币ERC20WithCapped.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// 有封顶上限总量的代币
contract ERC20WithCapped is ERC20Capped, Ownable {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 initialSupply, // 初始发行总量
uint256 cap // 上限总量
) ERC20(name, symbol) ERC20Capped(cap) Ownable(msg.sender) {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
有封顶上限总量代币一般是在可增发代币的基础上,设置一个总供应量上限,当达到这个上限时不允许再继续增发。
ERC20
代币ERC20WithPausable.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// 可暂停代币
contract ERC20WithPausable is ERC20, ERC20Pausable, Ownable {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 initialSupply // 初始发行总量
) ERC20(name, symbol) Ownable(msg.sender) {
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function _update(
address from,
address to,
uint256 value
) internal override(ERC20, ERC20Pausable) {
super._update(from, to, value);
}
}
可暂停代币可以让owner
通过调用pause()
方法来暂停代币的所有交易功能(如增发、销毁、转移等)。
ERC20
代币可锁仓的ERC20
代币需要我们在发行一个普通ERC20
代币的合约情况下,再发行一个锁仓合约,通过把ERC20
代币发送到锁仓合约进行锁仓,再由锁仓合约去控制解锁的时间和解锁的代币数量,最后会把解锁的代币发送给一个受益者(一般是锁仓的人或合约的部署人)。
这里我以最开始部署的固定总量代币和锁仓合约来演示可锁仓的ERC20
代币
ERC20FixedSupply.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// 固定总量代币
contract ERC20FixedSupply is ERC20 {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 totalSupply // 发行总量
) ERC20(name, symbol) {
_mint(msg.sender, totalSupply);
}
}
ERC20WithVesting.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/finance/VestingWallet.sol";
// 锁仓合约
contract ERC20WithVesting is VestingWallet {
constructor(
address beneficiary, // 受益人
uint64 startTimestamp, // 开始解锁时间戳(秒)
uint64 durationSeconds // 持续解锁时间(秒)
) VestingWallet(beneficiary, startTimestamp, durationSeconds) {}
}
部署完成这两个合约后,调用ERC20FixedSupply
的transfer
方法发送代币给ERC20WithVesting
锁仓合约,锁仓合约会根据我们部署时给构造函数设置的受益人、开始时间、持续时间自动解锁代币,有解锁代币时,我们调用ERC20WithVesting
的release
方法就可把解锁的代币发送给受益人。
ERC20
代币水龙头代币水龙头也是基于普通ERC20
代币合约的情况下,再发行一个水龙头合约,我们在ERC20
代币合约中发送代币给水龙头合约,水龙头合约去控制每次给用户是否发放代币及发送代币的数量等。
ERC20FixedSupply.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// 固定总量代币
contract ERC20FixedSupply is ERC20 {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 totalSupply // 发行总量
) ERC20(name, symbol) {
_mint(msg.sender, totalSupply);
}
}
ERC20Faucet.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// ERC20代币的水龙头合约
contract Faucet {
uint256 public amountAllowed = 100; // 每次可领的代币数
address public tokenContract; // token合约地址
mapping(address => bool) public requestedAddress; // 领取过代币的地址
// SendToken事件
event SendToken(address indexed Receiver, uint256 indexed Amount);
// 部署时设定ERC20合约地址
constructor(address _tokenContract) {
tokenContract = _tokenContract;
}
// 用户领取代币函数
function requestToken() external {
require(
requestedAddress[msg.sender] == false,
"Can't Request Multiple Times!"
);
ERC20 token = ERC20(tokenContract); // 创建合约对象
require(
token.balanceOf(address(this)) >= amountAllowed,
"Faucet Empty!"
);
token.transfer(msg.sender, amountAllowed); // 发送token
requestedAddress[msg.sender] = true; // 记录领取地址
emit SendToken(msg.sender, amountAllowed); // 记录SendTOken事件
}
}
部署完这两个合约后,先通过ERC20
代币合约transfer
代币到水龙头合约,然后其他用户就可以从水龙头合约去领取代币了,这里我们实现是每个用户只能领取一次,也可以实现为每个用户24小时只能领取一次,改一下判定是否领取的逻辑就行。
ERC20
代币空投代币空投也是需要在普通ERC20
代币的基础上部署一个空投合约,通过在ERC20
代币approve
给空投合约可以转移的权限,然后空投合约去处理空投的逻辑。
ERC20FixedSupply.sol:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// 固定总量代币
contract ERC20FixedSupply is ERC20 {
constructor(
string memory name, // 代币名称
string memory symbol, // 代币缩写
uint256 totalSupply // 发行总量
) ERC20(name, symbol) {
_mint(msg.sender, totalSupply);
}
}
ERC20Airdrop.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
// 空投合约
contract ERC20Airdrop {
function multiTransferToken(
address _token,
address[] calldata _addresses,
uint256[] calldata _amounts
) external {
require(
_addresses.length == _amounts.length,
"Lengths of Addresses and Amounts NOT EQUAL"
);
ERC20 token = ERC20(_token);
uint256 _amountSum = getSum(_amounts);
require(
token.allowance(msg.sender, address(this)) >= _amountSum,
"Need Approve ERC20 token"
);
for (uint8 i = 0; i < _addresses.length; i++) {
token.transferFrom(msg.sender, _addresses[i], _amounts[i]);
}
}
// 数组求和函数
function getSum(uint256[] calldata _arr) public pure returns (uint256 sum) {
for (uint256 i = 0; i < _arr.length; i++) sum = sum + _arr[i];
}
}
部署完这两个合约后,首先通过在ERC20
代币合约中approve
给空投合约可以转移代币的权限及代币数量,最后调用空投合约的multiTransferToken(address _token, address[] calldata _addresses, uint256[] calldata _amounts)
,通过传入ERC20
代币地址、空投的地址数组、地址对应空投的代币数来实现空投的逻辑。
ERC20
代币之WETH
WETH
是ETH
通过智能合约包装的版本,我们常见的WETH
、WBTC
、WBNB
都是对区块链原生代币通过智能合约包装的版本,因为如以太坊链原生代币ETH
本身是不符合ERC20
标准的,WETH
可以包装后可以使ETH
用于如DAPP
,或跨链等。
ERC20WETH.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract WETH is ERC20{
// 事件:存款和取款
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
// 构造函数,初始化ERC20的名字和代号
constructor() ERC20("WETH", "WETH"){
}
// 回调函数,当用户往WETH合约转ETH时,会触发deposit()函数
fallback() external payable {
deposit();
}
// 回调函数,当用户往WETH合约转ETH时,会触发deposit()函数
receive() external payable {
deposit();
}
// 存款函数,当用户存入ETH时,给他铸造等量的WETH
function deposit() public payable {
_mint(msg.sender, msg.value);
emit Deposit(msg.sender, msg.value);
}
// 提款函数,用户销毁WETH,取回等量的ETH
function withdraw(uint amount) public {
require(balanceOf(msg.sender) >= amount);
_burn(msg.sender, amount);
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}
}
使用一个智能合约,用户在转入ETH
时给他生成等量的WETH
,用户在取款ETH
时给他销毁等量的WETH
,实现对ETH
的包装。
到此我们就完成了常见的
ERC20
代币的发行,希望小伙伴给我多点赞、收藏哦~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!