如何创建和使用ERC-721代币?
ERC-721标准已经存在一段时间了。 最初是由区块链游戏流行起来的,现在越来越多地被用于其他应用,比如:Defi。
但ERC-721到底是什么呢?
非同质代币(NFT)是一种具有唯一性识别的代币。 non-fungible这个词意味着你不能用一个NFT来代替另一个NFT。 每一个NFT都是独一无二的。 相比比特币或ERC-20等任何可互换的代币,你收到的一个币(coin)/代币(token)并不重要,它们都具有相同的价值。
以太坊上 Cryptokitties 将其变成了现在流行的ERC-721标准。
ERC-721的特点
让我们来探讨一下ERC721标准的确切定义,所有的特征和功能都是它的一部分。
1. NFT的实现
核心是有一个从 uint256 =>address 的映射。 这与从 address =>uint256的可互换代币(如ERC-20)映射相反。
为什么会有这种反向映射?
在同质化代币中,用所有者映射到余额。 而在ERC-721中,我们其实也有这个映射(见下面的 balanceOf
),但我们更重要的是需要一种方法唯一识别代币。 通过从uint256到地址的映射,我们可以给每一个被铸造的代币分配一个id,并将这个id准确地映射给所有者。 当创建一个新的NFT代币时,会创建一个新的id(最常见的是从id 1开始,然后每ci计数+1任何后面的id),并将所有者设置为 ownerMapping[id] = receiverAddress
。
我们来看看实际的ERC-721函数
访问器函数
function ownerOf(uint256 tokenId) external view returns (address);
返回对应NFT id的所有者。 在ERC-721的实现中,我们只需要返回 ownerMapping[id]
。
function balanceOf(address owner) external view returns (uint256);
返回所有者的余额,即这个地址拥有多少NFT代币的总余额。 在我们的实施中,我们需要在铸币或转移NFT时跟踪余额。
转账函数
function safeTransferFrom(address from, address to, uint256 tokenId) external payable;
转账函数有三种形式。 这个可能是最常用的一个,它将把给定ID的代币转移到接收地址。 只有当from地址是所有者或被授权(approve)时才有效(授权见下文),并且要么接收者不是智能合约要么实现721接收者接口的智能合约(见下文的ERC-165和如何接收代币),否则交易将回退。
另外两个变体的转移函数只是一个附加字节数据字段传递给接收钩子,另一个非安全转移函数不调用转移钩子。
授权
function approve(address approveTo, uint256 tokenId) external payable; function getApproved(uint256 tokenId) external view returns (address); function setApprovalForAll(address operator, bool isApproved) external; function isApprovedForAll(address owner, address operator) external view returns (bool);
授权函数允许某人授权另一个地址使用代币,可以是一个特定的代币,也可以是所有代币。
一旦你授权了另一个地址,它就拥有了完全转移控制权。
2. 元数据(可选)
对于ERC-721智能合约来说,元数据扩展是可选的。 它包括一个名称和符号,就像它许多其他代币标准中一样(如ERC-20)。 此外,还可以为一个代币定义 tokenURI
, 每个代币都应该有自己的URI。
interface ERC721Metadata { function name() external view returns (string name); function symbol() external view returns (string symbol); function tokenURI(uint256 tokenId) external view returns (string); }
这个 tokenURI
链接到一个元数据文件。 在游戏的情况下,文件可以只托管在游戏提供商的服务器上。 本着去中心化的精神,在很多情况下,会使用IPFS来存储这个文件。 只要确保使用 IPFS pinning 不丢失文件即可。 元数据文件通常定义名称、描述和图片。 它可能是这样的:
{ "name": "Buzz", "description": "Paper collage, using salvaged and original watercolour papers", "image": "https://ipfs.infura.io/ipfs/QmWc6YHE815F8kExchG9kd2uSsv7ZF1iQNn23bt5iKC6K3/image" }
3. 枚举(可选)
另一个额外的可选接口是枚举, 它包含了按索引获取到对应的代币。
interface ERC721Enumerable { function totalSupply() external view returns (uint256); function tokenByIndex(uint256 _index) external view returns (uint256); function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); }
通过 totalSupply
函数,可以确定当前总共有多少个NFT,当然不包括销毁的。 tokenByIndex
是从所有代币列表返回第n个代币, tokenOfOwnerByIndex
返回所有者的代币列表中第n个代币。
你可能已经注意到了,如果你从不销毁代币,从1开始递增计算代币id,那么前两个函数可以反映代币id。
4. ERC-165和如何接收代币
如果你不知道EIP-165是什么,请看这里。 简而言之,它是智能合约定义自己支持哪些接口的一种方式。 那么比如说,智能合约是否支持接收ERC-721代币? 如果支持,则该合约的 supportsInterface
函数必须存在并返回true。
在ERC-721中,智能合约接收者必须实现 onERC721Received
函数。 你可以使用 onERC721Received
函数来实现一些接收代币后要处理的逻辑,或者只是确认接收者合约支持ERC-721。 但要注意,如果使用非安全转移函数,钩子函数不会被调用。作为调用确认,需要在钩子函数中返回 魔术数字 。
如何创建 ERC-721 NFT?
现在我们看看如何实现721代币合约。 这里基于Openzeppelin合约来实现。
这是一个基本的NFT例子合约,允许所有者铸造新的代币。 下面代码的导入的语法是假设在Remix中使用,如果你通过npm安装了Openzeppelin合约,导入语句需要调整一下。
// SPDX-License-Identifier: MIT pragma solidity 0.7.6; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Counters.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol"; contract NftExample is ERC721, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIds; constructor() ERC721("NFT-Example", "NEX") {} function mintNft(address receiver, string memory tokenURI) external onlyOwner returns (uint256) { _tokenIds.increment(); uint256 newNftTokenId = _tokenIds.current(); _mint(receiver, newNftTokenId); _setTokenURI(newNftTokenId, tokenURI); return newNftTokenId; } }
根据你的用例,可以考虑使用一些其他的铸币机制。 当然,_setTokenURI是可选的。
你也可以选择使用 预设好的铸币机制 。 一个简单的合约 MyContract is ERC721PresetMinterPauserAutoId
就可以得到一个NFT合约,该合约预设有:
- 使用Openzeppelin 访问控制 机制进行铸币、暂停和管理。
- 启用暂停和取消暂停功能
强烈建议阅读 Openzeppelin ERC-721 合约和文件,了解更多细节。
如何在合约中收到NFT?
如前所述,如果你使用safeTransferFrom功能向智能合约发送NFT,除非合约特别添加了对接收721代币的支持,否则它将会回退。
让我们看看如何添加这种支持:
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721Holder.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721Receiver.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/introspection/ERC165.sol"; contract Receiver721Example is IERC721Receiver, ERC165, ERC721Holder { constructor() { _registerInterface(IERC721Receiver.onERC721Received.selector); } function doSomethingWith721Token(IERC721 nftAddress, uint256 tokenId) external { // do something here } }
ERC721Holder
实现了一个基本的 onERC721Received
函数,该函数返回魔术数字以确认是否支持接收ERC-721代币。 确保你真的在合约里实现一个能够处理相应代币的函数(译者注:否则代币可能被锁死在合约)。
NFT和ERC-721的使用案例。
最常见的是区块链游戏,比如最初的Cryptokitties。 NFT很适合游戏,因为可以很容易地将游戏中的道具用NFT表示。 附加的元数据文件还可以为NFT添加更多的游戏内信息。
但适用于NFT不仅仅是游戏,我们现在越来越多可以看到NFT的存在。 请看 Rarible ,可以看到许多其他的使用案例。 数字艺术似乎是目前NFT的主要驱动力。 可以用来购买域名或者其他有数字访问权的实用内容。 更多的市场包括有 OpenSea 、 SuperRare 和 Axie Infinity 。
我觉得那些在Defi 领域的人非常有趣。 比如你可以购买某些保险(比如用 yinsure (来自yearn)来购买)。 NFT给你确定的保险权限,可以用来在去中心化的治理下进行理赔和支付。
我们也考虑过在 Injective Protocol 将ERC-721标准用于衍生品代币化头寸,不过我们最终决定不这么做,因为它并不完美。 不过理论上是可行的,如果大家有兴趣的话,以后可以继续讨论。
一般来说,任何可以独有的内容都可以变成NFT。 未来我们会看到更多的使用场景。
未来和跨链的 NFT
如前所述,未来我们可能会在以太坊和其他区块链上看到更多NFT的使用场景。 有一个新的标准正在制定中,叫 跨链 NFT 。 来自Cosmos的 Interchain Foundation 正在为处理多个网络 的ERC-721 NFT的制定这个标准。 对于任何区块链来说,在有了这个桥梁,这将是一个很有用的标准。