ERC721非同质代币(NFT)介绍

一、什么是ERC721

ERC721是一个开放的、用来描述以太坊上建立非同质或者唯一代币的标准(协议),NFT就是non-fungible tokens的首字母缩写。

当前色大多数代币(ERC20代币)都是同质的,这意味着这种代币的每一个token都是相同的,而ERC721每一个代币都是唯一的。

二、标准定义

ERC-721标准定义约定了一个智能合约必须实现的最小接口,它包括代币管理、持有和交易功能。然而它并不包括代币元数据的相关内容,也缺少对一些实用的功能支持。

请记住,这里提到的是最小接口,这就意味着它只有基本功能。然而在实际运用中,你不能仅依赖于这些接口,你必须增加一些额外的功能来支持代币应用。

官方网址: http://erc721.org/

这里贴出标准定义:

pragma solidity ^0.4.20;

/// @title ERC-721 Non-Fungible Token Standard
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
///  Note: the ERC-165 identifier for this interface is 0x80ac58cd
interface ERC721 /* is ERC165 */ {
    
    
    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

    /// @dev This emits when the approved address for an NFT is changed or
    ///  reaffirmed. The zero address indicates there is no approved address.
    ///  When a Transfer event emits, this also indicates that the approved
    ///  address for that NFT (if any) is reset to none.
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

    /// @dev This emits when an operator is enabled or disabled for an owner.
    ///  The operator can manage all NFTs of the owner.
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    /// @notice Count all NFTs assigned to an owner
    /// @dev NFTs assigned to the zero address are considered invalid, and this
    ///  function throws for queries about the zero address.
    /// @param _owner An address for whom to query the balance
    /// @return The number of NFTs owned by `_owner`, possibly zero
    function balanceOf(address _owner) external view returns (uint256);

    /// @notice Find the owner of an NFT
    /// @dev NFTs assigned to zero address are considered invalid, and queries
    ///  about them do throw.
    /// @param _tokenId The identifier for an NFT
    /// @return The address of the owner of the NFT
    function ownerOf(uint256 _tokenId) external view returns (address);

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT. When transfer is complete, this function
    ///  checks if `_to` is a smart contract (code size > 0). If so, it calls
    ///  `onERC721Received` on `_to` and throws if the return value is not
    ///  `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    /// @param data Additional data with no specified format, sent in call to `_to`
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

    /// @notice Transfers the ownership of an NFT from one address to another address
    /// @dev This works identically to the other function with an extra data parameter,
    ///  except this function just sets data to ""
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
    ///  TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
    ///  THEY MAY BE PERMANENTLY LOST
    /// @dev Throws unless `msg.sender` is the current owner, an authorized
    ///  operator, or the approved address for this NFT. Throws if `_from` is
    ///  not the current owner. Throws if `_to` is the zero address. Throws if
    ///  `_tokenId` is not a valid NFT.
    /// @param _from The current owner of the NFT
    /// @param _to The new owner
    /// @param _tokenId The NFT to transfer
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    /// @notice Set or reaffirm the approved address for an NFT
    /// @dev The zero address indicates there is no approved address.
    /// @dev Throws unless `msg.sender` is the current NFT owner, or an authorized
    ///  operator of the current owner.
    /// @param _approved The new approved NFT controller
    /// @param _tokenId The NFT to approve
    function approve(address _approved, uint256 _tokenId) external payable;

    /// @notice Enable or disable approval for a third party ("operator") to manage
    ///  all of `msg.sender`'s assets.
    /// @dev Emits the ApprovalForAll event. The contract MUST allow
    ///  multiple operators per owner.
    /// @param _operator Address to add to the set of authorized operators.
    /// @param _approved True if the operator is approved, false to revoke approval
    function setApprovalForAll(address _operator, bool _approved) external;

    /// @notice Get the approved address for a single NFT
    /// @dev Throws if `_tokenId` is not a valid NFT
    /// @param _tokenId The NFT to find the approved address for
    /// @return The approved address for this NFT, or the zero address if there is none
    function getApproved(uint256 _tokenId) external view returns (address);

    /// @notice Query if an address is an authorized operator for another address
    /// @param _owner The address that owns the NFTs
    /// @param _operator The address that acts on behalf of the owner
    /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

interface ERC165 {
    
    
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

interface ERC721TokenReceiver {
    
    
    /// @notice Handle the receipt of an NFT
    /// @dev The ERC721 smart contract calls this function on the
    /// recipient after a `transfer`. This function MAY throw to revert and reject the transfer. Return
    /// of other than the magic value MUST result in the transaction being reverted.
    /// @notice The contract address is always the message sender.
    /// @param _operator The address which called `safeTransferFrom` function
    /// @param _from The address which previously owned the token
    /// @param _tokenId The NFT identifier which is being transferred
    /// @param _data Additional data with no specified format
    /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
    /// unless throwing
    function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
 }

三、标准详情

ERC-721标准其实可以分为三个接口,ERC721接口、ERC165接口、ERC721TokenReceiver接口。

3.1、 IERC721

IERC721接口定义了三个事件,分别是交易事件、授权事件和全部授权事件。事件是用来使轻客户端更方便的追踪以太坊状态的改变。

IERC721接口定义了9个函数。分别应用于用户余额、代币所有者、安全交易、普通交易、授权、全部授权、查询授权、查询是否全部授权。

这里详细解释一下安全交易。安全交易是基于接收到代币后必须作出一个响应(执行某些代码)的思想。如果没有响应,则认为代币交易未成功。当然,因为只有合约才会执行代码,所以安全交易只对接收者是合约有效。安全交易函数提供了一个参数data,代表传递给接收合约的数据。无信息传递时,data也可以为空。

它有一个同名函数,显然另一个是重载函数,重载函数和原函数的区别在于它并不提供参数data,从注释中可以看到它其实就相当于另一个函数的data为空。

除了有安全交易函数,还有普通交易函数,这就意味着ERC721并不强制使用安全交易。

3.2、 IERC165

IERC165接口约定了合约支持的接口,比如支持ERC721。每个支持的接口都有一个对应的bytes4值与之相对应,这个是固定的,是常量(有一个计算方式)。

3.3、 IERC721TokenReceiver

它定义了合约接收代币后执行的函数,其中一个参数就是IERC721安全交易中的data

四、一些附加功能

前面提到过,ERC721标准只是最小接口,直接拿来实际运用还存在很多问题,比如没有增发功能(用户没有获取代币来源)等。需求不同,有时可能还需要燃烧功能。为了能在应用端正确显示用户所拥有的NFT代币,还需要可列举功能。因此这里存在许多附加标准(功能),可以根据自己需求增加并组合。

4.1、 燃烧功能 IERC721Burnable

IERC721Burnable,燃烧其实就是代币从用户账户中减去,并将代币的owner设置为空地址

4.2、可列举功能 IERC721Enumerable

IERC721Enumerable 可列举增加的三个函数分别为:代币发行总量、按索引获取用户代币和按总索引获取代币。但是具体实现时有一些细节点:

  1. 构造器,在构造器中增加可列举接口支持,参阅上面提到的IRC165.
  2. 每个地址都对有一个整数数组来存储它的token,这一点使用Vyper实现不了,因为Vyper没有动态数组。
  3. 因为上述第2点,所以需要修改增发、交易和燃烧方法来改变那个动态数组的内容。

4.3、 元数据 IERC721Metadata

IERC721Metadata增加了名称、符号和代币uri函数。但是具体实现时有一些细节点:

  1. 构造器中增加元数据接口支持
  2. 在燃烧时要清除对应的元数据

4.4、 其它接口 mintable 和 pausable

其它功能还有增发和暂停等。增发就是增加一个角色控制外部增发接口(或者某种条件下自动增发),暂停也是一样,只是增加一个外部暂停接口(或者某种条件自动触发暂停)。

五、常用组合

一般常用的组合为ERC721标准+ IERC721Enumerable + IERC721Metadata,再加上自定义的mintable。

六 、示例合约

所有的这一切你都不用自己写,有人写出了示例合约并做成了模块,使用时只需要导入就行了。例如:

import 'openzeppelin-solidity/contracts/token/ERC721/ERC721Full.sol';

实用技巧,你需要再导入一个 MinterRole 就可以自定义增发实现了。如果你想发行的代币序号从1开始排列的话,直接使用 ERC721Enumerable 中的代币数组长度计数就好,不需要重新做一个变量计数。如果想有其它的实现方式,需要自己实现。

七、依赖库安装

导入模块(依赖库)中的示例合约需要提前安装对应的库,比如在truffle工程中使用上面的库,需要执行:npm install @openzeppelin/contracts

猜你喜欢

转载自blog.csdn.net/weixin_39430411/article/details/109254074