目录
下面讲的,是最基础、最纯净的 ERC-20 代币雏形。
几乎所有代币合约(USDT、UNI、SHIB...)都是在这个基础上加了更多功能(名字、符号、小数位、授权、黑名单等)演变来的。学完这个,就掌握了"发币 + 转账"两大核心机制!
一、代码
javascript
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
// 关键字 "public" 使变量可以从其他合约中访问。
address public minter;
mapping(address => uint) public balances;
// 事件允许客户端对您声明的特定合约变化做出反应
event Sent(address from, address to, uint amount);
// 构造函数代码只有在合约创建时运行
constructor() {
minter = msg.sender;
}
// 向一个地址发送一定数量的新创建的代币
// 但只能由合约创建者调用
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
// 错误类型变量允许您提供关于操作失败原因的信息。
// 它们会返回给函数的调用者。
error InsufficientBalance(uint requested, uint available);
// 从任何调用者那里发送一定数量的代币到一个地址
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender])
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
二、代码解读
上面的代码实现了:
-
只能由创建者"铸造"(凭空发币)
-
任何人可以互相转账
-
有余额检查、错误提示、事件通知
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
^ 是 Solidity 版本锁定符号,意思是:可以使用 0.8.4 及以上的 0.8.x 版本来编译,但不能使用 0.9.0 及以上版本。
contract Coin {
定义一个叫 Coin 的代币合约。
address public minter; // 谁部署的合约,谁就是"央行"
mapping(address => uint) public balances; // 记录每个地址的代币余额
-
minter:记录合约创建者(只有他能发币)
-
balances:一个映射(类似 Python 的 dict),key 是地址,value 是余额,而且是 public,外部直接能查
event Sent(address from, address to, uint amount);
定义一个事件叫 Sent。
每次有人转账成功,就会"发射"这个事件,前端(网页、MetaMask、ethers.js)可以监听它,实时显示"某人转了多少币给谁"。
constructor() {
minter = msg.sender; // 部署合约的那个人自动成为 minter(央行)
}
构造函数,只在部署时运行一次,把部署者的地址设为 minter。
function mint(address receiver, uint amount) public {
require(msg.sender == minter); // 只有央行能调用!
balances[receiver] += amount; // 给 receiver 发 amount 个币
}
铸币函数(央行专属)
-
比如你部署了合约,你就是 minter
-
你可以调用 mint("某个地址", 1000),就凭空给那个人发 1000 个币
error InsufficientBalance(uint requested, uint available);
自定义错误类型(0.8.4+ 新特性,比 revert 字符串省 gas)
当余额不足时,会返回"想要转多少"和"实际只有多少"两个数字。
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender])
revert InsufficientBalance({ // 余额不足就报错并回滚
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount); // 发射转账事件
}
普通转账函数(任何人都能调用)
- 检查你有没有足够的币,没有就报错
- 扣除发送者余额
- 增加接收者余额
- 发射 Sent 事件(前端可以看到)
三、实操
打开Remix平台
3.1编译

3.2部署

3.3给自己发10000元
因为是我们自己部署的,所以自己就是央行,可以给自己印钱,爽!
复制自己的钱包地址:

左侧拉到最下面,部署的合约部分,展开,在MINT处前面复制的地址,和10000元,点击transact按钮,如下:

完成之后,在balances按钮右侧输入地址,查询余额:

发现已经增加了10000元,成功!
3.4给小弟转7000元
小弟不容易,给他转7000。我们选择账户ACCOUNT的第二个钱包,然后复制地址,再切换回去到自己的钱包(第一个),然后就可以去转账了。

我们还是拉到最下面,这回选择SEND部分,输入小弟账户地址,金额7000【金额不能超过我们自己的总金额】。(这里不要忘了切换回自己的钱包,因为只有我们有钱)
如果下面的日志是绿色✅,则说明成功。

再把小弟的地址输入到balances那边,看看是否已经到账:

发现已经到账,nice!
3.5小弟给对象转4000元
类似的,小弟对象是第三个钱包。要切换到第二个钱包给第三个转账:

原来第三个钱包是0元,第二个为7000,现在转走4000,看看结果如何:

上图可知,4000已经到账。
再看看小弟自己的余额:

还是3000,没错~
到此,基本的发币、转账功能就完成啦!