导语:标准化让创新成为可能
以太坊上的代币(Token)不是内置功能,而是通过智能合约标准实现的。
在 ERC-20 之前,每个项目都发明自己的代币合约,钱包和交易所不得不为每种代币写适配代码。ERC-20 的出现统一了接口,使得任何兼容的钱包都能立即支持新代币——这是标准化带来的巨大价值。
ERC-721 则将这一思想延伸到独特资产上——每一枚代币都是独一无二的,这就是 NFT(Non-Fungible Token)。
一句话总结:ERC-20 让"货币"标准化,ERC-721 让"独特资产"标准化,二者奠定了以太坊数字经济的基础设施。
1. ERC-20:同质化代币标准
1.1 标准接口
interface IERC20 {
// === 基础信息 ===
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8); // 通常 18
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);
// === 事件 ===
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
1.2 完整实现(安全版)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Context.sol";
contract MyToken is Context, IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
uint8 private constant _decimals = 18;
constructor(string memory name_, string memory symbol_, uint256 initialSupply) {
_name = name_;
_symbol = symbol_;
_mint(_msgSender(), initialSupply * 10 ** _decimals);
}
function name() public view returns (string memory) {
return _name;
}
function symbol() public view returns (string memory) {
return _symbol;
}
function decimals() public pure returns (uint8) {
return _decimals;
}
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) public returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
// === 内部函数 ===
function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from zero");
require(to != address(0), "ERC20: transfer to zero");
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: insufficient balance");
unchecked {
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}
emit Transfer(from, to, amount);
}
function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to zero");
_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
}
function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "ERC20: approve from zero");
require(spender != address(0), "ERC20: approve to zero");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _spendAllowance(address owner, address spender, uint256 amount) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
}
1.3 approve + transferFrom 模式
这个设计是 DeFi 协议的基石:
// 1. 用户授权 DEX 使用 1000 个代币
usdc.approve(dexAddress, 1000 * 10**18);
// 2. DEX 从用户账户转走代币(无需用户私钥)
usdc.transferFrom(userAddress, dexLiquidityPool, 1000 * 10**18);
| 风险 | 防护 |
|---|---|
| 授权额度无限大 | 用户使用完后主动 approve(0) |
| 重放攻击 | 每次授权新额度前先设为 0 |
| 前端钓鱼 | 仔细核对 spender 地址 |
一句话总结:ERC-20 的核心是
balance + allowance双层模型 ——transfer直接转账,approve + transferFrom让第三方代表你操作。
2. ERC-721:非同质化代币(NFT)
2.1 标准接口
interface IERC721 {
// === 所有权 ===
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
// === 转移 ===
function transferFrom(address from, address to, uint256 tokenId) external;
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
// === 元数据(可选扩展) ===
function tokenURI(uint256 tokenId) external view returns (string memory);
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
}
2.2 核心差异:每个 tokenId 都是唯一的
| 特性 | ERC-20 | ERC-721 |
|---|---|---|
| 同质化 | ✅ 所有代币完全等价 | ❌ 每个代币唯一 |
| 余额模型 | balanceOf(addr) 返回总量 | balanceOf(addr) 返回持有的 NFT 数量 |
| 转账参数 | transfer(to, amount) | transferFrom(from, to, tokenId) |
| 元数据 | 无 | tokenURI() 指向 JSON 元数据 |
2.3 NFT 元数据标准
{
"name": "Bored Ape #1234",
"description": "A unique digital collectible",
"image": "ipfs://Qm.../image.png",
"attributes": [
{ "trait_type": "Background", "value": "Blue" },
{ "trait_type": "Eyes", "value": "Laser" },
{ "trait_type": "Rarity Score", "value": 95, "display_type": "number" }
]
}
存储方案对比:
| 方案 | 去中心化 | 持久性 | 成本 | 适用 |
|---|---|---|---|---|
| IPFS + Pinning | ✅ | ⚠️ 需节点保持 | 低 | 主流方案 |
| Arweave | ✅ | ✅ 永久 | 一次性付费 | 高价值资产 |
| 中心化服务器 | ❌ | ❌ | 低 | 不推荐 |
2.4 完整 NFT 合约实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyNFT is ERC721, ERC721URIStorage, Ownable {
uint256 private _tokenIdCounter;
string private _baseTokenURI;
uint256 public constant MINT_PRICE = 0.01 ether;
uint256 public constant MAX_SUPPLY = 10000;
constructor(string memory baseURI) ERC721("MyNFT", "MNFT") Ownable(msg.sender) {
_baseTokenURI = baseURI;
}
function mint() public payable returns (uint256) {
require(msg.value >= MINT_PRICE, "Insufficient payment");
require(_tokenIdCounter < MAX_SUPPLY, "Sold out");
uint256 tokenId = _tokenIdCounter;
_tokenIdCounter++;
_safeMint(msg.sender, tokenId);
_setTokenURI(tokenId, string(abi.encodePacked(_baseTokenURI, _uintToString(tokenId), ".json")));
return tokenId;
}
function withdraw() public onlyOwner {
payable(owner()).transfer(address(this).balance);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
// 覆盖 tokenURI
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
// 辅助:uint to string
function _uintToString(uint256 value) internal pure returns (string memory) {
if (value == 0) return "0";
uint256 temp = value;
uint256 digits;
while (temp != 0) { digits++; temp /= 10; }
bytes memory buffer = new bytes(digits);
while (value != 0) { digits -= 1; buffer[digits] = bytes1(uint8(48 + uint256(value % 10))); value /= 10; }
return string(buffer);
}
}
一句话总结:ERC-721 的核心是每个
tokenId映射到唯一的所有者和元数据 URI,这让数字艺术品、游戏道具、身份凭证的链上确权成为可能。
3. ERC-2981:NFT 版税标准
interface IERC2981 {
function royaltyInfo(uint256 tokenId, uint256 salePrice)
external
view
returns (address receiver, uint256 royaltyAmount);
}
实际应用:
function royaltyInfo(uint256, uint256 salePrice)
public
view
override
returns (address receiver, uint256 royaltyAmount)
{
return (owner(), (salePrice * 500) / 10000); // 5% 版税
}
注意:链上版税是可选项——执行依赖于 NFT 市场的合约代码遵守。Opensea、Blur 等市场的版税执行策略不同。
一句话总结:ERC-2981 为创作者提供了一种标准化的链上版税声明方式,但实际执行仍依赖市场合约的自愿遵守。
4. OpenZeppelin 最佳实践
生产环境不要从零写标准合约,应使用 OpenZeppelin 的审计通过版本:
npm install @openzeppelin/contracts
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
OpenZeppelin 提供的安全功能:
| 工具 | 用途 |
|---|---|
ReentrancyGuard | 防止重入攻击 |
Ownable / AccessControl | 权限管理 |
Pausable | 紧急暂停 |
SafeMath (0.8 前) | 防溢出(0.8 后内置) |
Counters | 安全的计数器 |
ERC20Permit | gasless approve(EIP-2612) |
一句话总结:OpenZeppelin 合约库经过行业审计和数十亿美元 TVL 的考验,是生产级代币合约的唯一理智选择。
5. ERC-1155:多代币标准
在 ERC-20 和 ERC-721 之间,还存在一个中间地带:半同质化代币。游戏道具是最典型的场景——同一类型的装备(如"铁剑")是可互换的,但不同稀有度或附魔的同一装备又是唯一的。
interface IERC1155 {
// 批量余额查询
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
external view returns (uint256[] memory);
// 批量转移
function safeBatchTransferFrom(
address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data
) external;
// 单一转移
function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data)
external;
}
ERC-1155 的核心设计是用 tokenId 区分类型,用 amount 表示数量:
| 场景 | tokenId=1 (铁剑) | tokenId=2 (史诗铁剑 #42) |
|---|---|---|
| ERC-20 风格 | amount=100(100 把铁剑) | 不适用 |
| ERC-721 风格 | 不适用 | amount=1(唯一的 NFT) |
| ERC-1155 | amount=100(100 把铁剑) | amount=1(唯一的史诗铁剑) |
这种灵活性让 ERC-1155 成为链游和元宇宙项目的首选,OpenSea、Rarible 等主流 NFT 市场均支持该标准。
一句话总结:ERC-1155 一个合约管理多种代币类型,兼具 ERC-20 的批量效率和 ERC-721 的唯一性,是游戏和元宇宙资产的标准选择。
5. 总结
| 标准 | 含义 | 核心接口 |
|---|---|---|
| ERC-20 | 同质化代币(货币) | transfer, approve, balanceOf |
| ERC-721 | 非同质化代币(唯一资产) | transferFrom, ownerOf, tokenURI |
| ERC-2981 | NFT 版税 | royaltyInfo |
| ERC-1155 | 多代币标准(半同质化) | 同一合约管理多种代币类型 |
理解这些标准是进入 DeFi 和 NFT 开发的基础:
- DeFi 核心协议与流动性挖矿 — 理解代币如何在金融协议中流转
- 智能合约安全审计与常见漏洞 — 掌握代币合约的安全陷阱
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。