# 一文弄清以太坊代币：ERC-20 & ERC-721

By [choovybi](https://paragraph.com/@choovybi) · 2022-11-10

---

> 文章共2000字，预计阅读时间10分钟。

Web3是个令人眼花缭乱的地方，里面充斥着新颖的词汇：币、代币、NFT、跨链桥、Defi、Cefi，这些都让入门者感到非常困惑。

新手可能花好几天时间，看了好几个项目的白皮书，最终都不明白什么是代币，什么是NFT。有人好不容易清楚了代币的含义，依然会疑惑，以太坊不是已经有了主币ETH，为什么还有个代币wETH？

本文试图从代码角度帮助弄清以太坊上的各种代币，他们有什么功能，以及通常是如何实现这些功能的，[欢迎交流](https://linktr.ee/choovybi)。

TL;DR
-----

**「ERC」全称**「Ethereum Request for Comment」，中文名为「以太坊意见征求稿」，目前经常使用的有**ERC-20**和**ERC-721**两个标准，前者用于发行同质化代币，后者用于发行非同质化代币（即NFT，non-fungible token）。

满足标准的合约所记录的数据就被称为代币，代币和NFT只是被记录在合约中的数据，铸造、转账、销毁操作也只是在合约中修改记录的数值。

ERC标准规定了合约的接口，其他应用（例如OpenSea）可以通过合约的接口来操作代币。

容易误解的是，ERC标准仅仅规定了合约的接口格式，至于合约具体怎样实现这些接口，ERC标准是不做规定的，所以能有各式各样的NFT。

ERC-20
------

**ERC-20**标准规定了以下6个函数和2个事件

    interface IERC20 {
        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
        );
    }
    

**6个函数的含义分别是**

*   `totalSupply()`：获取代币的总发行量
    
*   `balanceOf(address account)`：获取`account`地址代币的余额
    
*   `transfer(address to, uint256 amount)`：函数的调用者直接向`to`地址发送`amount`数量代币
    
*   `allowance(address owner, address spender)`：获取`owner`向`spender`地址批准了多少消费额度
    
*   `approve(address spender, uint256 amount)`：函数的调用者向`spender`地址批准`amount`数量消费额度
    
*   `transferFrom(address from, address to, uint256 amount)`：函数的调用者使用`from`地址给的消费额度向`to`地址发送`amount`数量代币
    

**2个事件的含义分别是**

*   `Transfer`：转账时发出，记录`from`地址向`to`地址发送了`amount`数量代币
    
*   `Approval`：批准消费额度时发出，记录`owner`地址向`spender`地址批准了`value`消费额度
    

其中`approve`、`transferFrom`和`allowance`可能比较难理解，一个简单的例子就是父母给零花钱，花钱的虽然是子女，但钱是从父母钱包里面扣除的。

如果合约实现了以上6个函数和2个事件，我们就可以说满足**ERC-20**标准。通常项目方还会在合约中添加`mint`函数和`burn`函数，用来增发代币和销毁代币，[这里](https://gist.github.com/choovybi/a8cbfaa80cd1899cdf166024dffaccc2)是**ERC-20**合约的一种实现方式。

满足**ERC-20**标准的合约所铸造出来的代币，就可以上线uniswap等去中心化交易所，用来交换别的虚拟货币了。因为去中心化交易所需要调用`transferFrom` 方法，而以太坊的主币ETH并没有这个接口，所以查看ETH在uniswap中具体的交易情况，能发现ETH总是先被存入wETH合约转换成同等数量的wETH代币，之后uniswap再调用`transferFrom` 进行各种代币间的交换。

ERC-721
-------

**ERC-721**标准规定了以下10个函数和3个事件

    interface IERC165 {
        function supportsInterface(bytes4 interfaceID) external view returns (bool);
    }
    
    interface IERC721 is IERC165 {
        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
        );
    
        function balanceOf(address owner) external view returns (uint256 balance);
    
        function ownerOf(uint256 tokenId) external view returns (address owner);
    
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external;
    
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId,
            bytes calldata data
        ) external;
    
        function transferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external;
    
        function approve(address to, uint256 tokenId) external;
    
        function getApproved(uint256 tokenId)
            external
            view
            returns (address operator);
    
        function setApprovalForAll(address operator, bool _approved) external;
    
        function isApprovedForAll(address owner, address operator)
            external
            view
            returns (bool);
    }
    

**ERC-721**标准是继承了**ERC-165**标准的，其中**ERC-165**标准规定了函数`supportsInterface(bytes4 interfaceID)` 用于发布并检测合约实现了什么接口。

**ERC-165**标准使得外部可以查询合约是否支持接口，如果支持，查询接口的版本，以便用户可以调整与合约交互的方式。

**剩下9个函数的含义分别是**

*   `balanceOf(address owner)`：获取`owner`地址代币的余额
    
*   `ownerOf(uint256 tokenId)`：获取`tokenId`号代币的`owner`地址
    
*   `safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data)` ：安全地将`tokenId`号代币从`from`地址发送到`to`地址并附加`data`信息（`data`信息在token发送到合约账户时会用到，可以用`data`来操作接收的合约，安全发送指的是合约会检查`to`地址是不是合法的token接收者）
    
*   `safeTransferFrom(address from, address to, uint256 tokenId)`：同上，`data`信息为空
    
*   `transferFrom(address from, address to, uint256 tokenId)`：将`tokenId`号代币从`from`地址发送到`to`地址，一般用于发送给他人账户，如果`to`地址是合约账户则可能出错
    
*   `approve(address to, uint256 tokenId)`：函数调用者将`tokenId`号代币批准给`to`地址
    
*   `getApproved(uint256 tokenId)`：获取`tokenId`号代币被批准给了哪个地址
    
*   `setApprovalForAll(address operator, bool _approved)`：函数调用者管理第三方`operator`地址的权限，`_approved`表示是否给第三方批准
    
*   `isApprovedForAll(address owner, address operator)`：获取`owner`地址是否批准第三方`operator`地址操作自己所有的代币
    

**3个事件的含义分别是**

*   `Transfer`：转账时发出，记录`from`地址向`to`地址发送了`tokenId` 号代币
    
*   `Approval`：批准时发出，记录`owner`地址向`approved`批准了`tokenId`号代币
    
*   `ApprovalForAll`： 管理第三方权限时发出，记录`owner`地址将第三方`operator`地址的管理权限设置为`approved`
    

由于合约中含有`tokenId` ，所以每个代币都是不尽相同的，这正是「非同质化」的含义。**ERC-721**和**ERC-20**在设计思路上没有太大差别，只不过由于**ERC-721**的代币是非同质化的，所以既可以通过`tokenId` 进行单个批准，也可以将`owner`地址的代币全部进行批准，而**ERC-20**只能批准固定数量。

在实际使用中，不一定需要把九个函数全部实现，[这里](https://gist.github.com/choovybi/eeb697a6a5a9c71cfa1dc3056dd74144)是**ERC-721**合约的一种实现方式。

尾记
--

知道了**ERC-20**和**ERC-721**有什么用呢？

知道了标准，我们就可以通过ERC标准提供的接口直接操作各类代币和NFT，例如，无聊猿是NFT，那么[他的合约](https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d)肯定也符合**ERC-721**标准。

可以通过以太坊浏览器，直接调用`ownerOf`接口查看2022号无聊猿的拥有者（当然使用代码调用接口也可以，只是以太坊浏览器比较直观）

![2022号无聊猿的拥有者](https://storage.googleapis.com/papyrus_images/95ed196b4fe1c68dd337804ed7892c98bc90413a518697c29dae646afcbc1a33.png)

2022号无聊猿的拥有者

我们再在最大的NFT交易市场OpenSea上查看一下2022号无聊猿，发现其拥有者确实是这个`B44618`开头的账户

![OpenSea](https://storage.googleapis.com/papyrus_images/b41e1ad2f28182e98ca1da38c453dbb07a860a06036418db430baa37d5c59a18.jpg)

OpenSea

事实上，OpenSea这类应用，正是通过调用ERC标准提供的各种接口来实现交易的，当我们完全了解ERC标准之后，完全可以自己编写应用来进行代币的交易。

今天就到这里，再见。

[Subscribe](null)

参考文章

[https://ethereum.org/zh/developers/docs/standards/tokens/erc-20/](https://ethereum.org/zh/developers/docs/standards/tokens/erc-20/)

[https://ethereum.org/zh/developers/docs/standards/tokens/erc-721/](https://ethereum.org/zh/developers/docs/standards/tokens/erc-721/)

[https://eips.ethereum.org/EIPS/eip-165](https://eips.ethereum.org/EIPS/eip-165)

---

*Originally published on [choovybi](https://paragraph.com/@choovybi/erc-20-erc-721)*
