干货 | ERC721: Non-fungible Token Standard

hongji   |     |   3566 次阅读

EIP前言

EIP: < 被分配 >
标题: 非同质代币标准
作者: Dieter Shirley <dete@axiomzen.co> 
类型: 标准 
类别: ERC 状态
草稿创建日期: 2017-09-20

简要

一种针对非同质代币的标准接口。

摘要

本标准提出了用于智能合约内非同质代币(Non-fungible tokens,以下简称“NFTs”)操作的标准API实现方法。此外,本标准还提供了用于NFTs所有权监测和转移的基本功能。

动机

标准接口可以使通用应用程序处理以太坊上任意类型NFTs。更重要的是,它使得NFTs在标准化钱包中的也可被监测,并且能够在交易所用于交易。

规范

ERC-20 兼容性

函数 name()

function name() constant returns (string name)

可选 - 建议使用该方法来提升钱包和交易所的可用性,但是接口和其他合约必须不依赖于本方法。

返回由此合约管理的NFTs集合的名称。 - 例如,“我的非同质代币”。

函数 symbol()

function symbol() constant returns (string symbol)

可选 - 建议使用该方法来提升钱包和交易所的可用性,但是接口和其他合约必须不依赖于本方法。

返回一个代表此合约中管理的NFTs集合的短字符串标识符。例如,“MNFT”。这个标识符必须是短的(建议3-8个字符),其中不包括空白字符和换行符,并且应该仅限于大写的拉丁字母(即英语中使用的26个字符)。

函数totalSupply()

function totalSupply() constant returns (uint256 totalSupply)

返回由该合约监测的NFTs的总数量。

函数balanceOf()

function balanceOf(address _owner) constant returns (uint256 balance)

返回由 address _owner 持有的 NFTs 的数量。

基础所有权

函数ownerOf()

function ownerOf(uint256 _tokenId) constant returns (address owner)

返回当前被标记的 _tokenID 代币持有者的地址。如果 _tokenID 并不代表当前合约正在监测的NFTs 中的一个,那么该方法必须 抛出异常 。该方法一定不会返回 0。(如果NFTs 被分配给 0 地址就认为其被销毁,查询这些 NFTs 也应该 抛出异常

函数approve()

function approve(address _to, uint256 _tokenId)

给予地址 _to,ID 为 _tokenId 的 NFT 的所有权。如果 msg.sender != ownerOf(_tokenId) 时,或者如果 _tokenID 并不代表当前合约正在监测的NFTs 中的一个时,亦或 msg.sender == _to 时,该方法必须抛出异常。

在任意给定时间,只有一个地址拥有某 NFT的所有权;使用一个新的地址作为参数 _to 调用 approveTransfer 方法,会撤销先前地址的NFT所有权。如果使用 0 地址作为参数 _to 调用该方法,就会撤销此前任何地址对于该NFT的所有权。

该方法成功完成后必须发出一个 Approval 事件(定义如下),除非当没有未确认授权时,该方法调用者试图去清除所有权。需要特别说明的是,当地址 _to 为 0 且有一些未完成授权时, 则必须触发 Approval 事件。此外,如果地址 _to 是当前已经有所有权的地址,则必须触发 Approval 事件,否则此次调用与不会产生任何影响。(即,“重申”了一个已有所有权授权的 approve() 调用必须触发该事件。)

操作 旧状态 地址 _to 新状态 事件
清除未设置的所有权 Clear 0 Clear None
设置新的所有权 Clear X X Approval(owner, X, tokenID)
改变所有权 X Y Y Approval(owner, Y, tokenID)
重申所有权 X X X Approval(owner, X, tokenID)
清除所有权 X 0 Clear Approval(owner, 0, tokenID)

注:任何 NFT 所有权的变化,无论是直接通过在该接口中定义的 transfertransferFrom 方法,还是通过在合规合约中定义的任何其他机制,都必须清除所有已转移的NFT 的授权。如果有未完成的授权,则必须通过所有权转移隐式清除授权,并且必须触发事件 Approval(0, _tokenId) 。(即如果有所有权转移操作,则操作必须提交与调用 approve(0, _tokenID) 时相同的 Approval 事件。)

函数takeOwnership()

function takeOwnership(uint256 _tokenId)

当且仅当 msg.sender 当前拥有 ID 为 _tokenId 的 NFT 所有权(通过先前调用的 approveTransfer )时,该函数才会将 ID 为 _tokenId 的 NFT 的所有权指派给 msg.sender 。一次成功的转移必须发出 Transferevent 事件(定义如下)。

本方法必须将所有权转移给 msg.sender 或者抛出异常,不会产生其他种结果。失败的原因可能有(但不限于)以下几种:

  • msg.sender 没有 _tokenId 的授权
  • _tokenID 并不代表当前合约正在监测的 NFTs 中的任何一个
  • msg.sender 已经有 _tokenId 的所有权

重要提示:请参阅 approveTransfer 方法中的说明;一次成功的转移必须清除待确认授权。

函数 transfer()

function transfer(address _to, uint256 _tokenId)

当且仅当 msg.sender == ownerOf(_tokenId) 时,该函数才会将 ID 为 _tokenId 的 NFT 的所有权指派给地址 _to 。一次成功的转移操作必须发起 Transer 事件(定义如下)。

本方法必须将所有权转移给地址 _to 或者抛出异常,不会产生其他结果。失败的原因可能有(但不限于)以下几种:

  • sg.sender 不是 _tokenId 的持有者
  • tokenID 并不代表当前合约正在监测的 NFTs 中的任何一个
  • _to 地址为 0 (合规合约可能有其他方法去销毁或烧毁 NFTs,这些方法概念上与 “转移给地址 0 一致”,并且将发起 Transfer 事件反映这一转移事件。然而, transfer(0, tokenID) 必须被视为一种错误。)

一个合规合约必须允许当前代币持有者向其自身“转移”代币,作为一种在事件流中申明所有权的一种方式。(即对于 _to == ownerOf(_tokenID) 的操作是有效的。)这种“无操作转移”必须被认为是一次成功的转移,且因此必须出发一个 Transfer 事件(用相同的地址,表示从地址 _from 到地址 _to 的转移)

重要提示:请参照 approveTransfer 方法中的说明;一次成功的转移必须清空待确认授权。这其中包括当前持有者的无操作转移。

函数tokenOfOwnerByIndex()

function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId)

可选 - 建议使用该方法来提升钱包和交易所的可用性,但是接口和其他合约必须不依赖于本方法。

返回已分配给地址 _owner 的第 n 个 NFT,其中 n 由参数 _index 指定。如果 _index >= balanceOf(_owner) 则该方法必定 抛出异常

建议用法如下:

uint256 ownerBalance = nonFungibleContract.balanceOf(owner);

uint256[] memory ownerTokens = new uint256[](ownerBalance);

for (uint256 i = 0; i < ownerBalance; i++) {
    ownerTokens[i] = nonFungibleContract.tokenOfOwnerByIndex(owner, i);
}

该方法的具体实现必不能假设 NFTs 是由其调用者按照某一特定顺序访问的(特别是不能假设该方法是按照单调递增的循环方式调用的。),并且必须确保对于 tokenOfOwnerByIndex 的调用是完全幂等的(译者注:复杂度相同),除非且直到该合约中调用了某些非 常量 函数的情况。

方法 tokenOfOwnerByIndex 的调用者绝不能假设NFTs的顺序在单个操作之外,或者通过调用任何非 常量 合约方法(直接或间接)维护。

注:当前 Solidity 的局限性主要是没有一种有效的方法可以通过单函数调用返回一个地址持有 的完整 NFTs 列表。调用者不应该假设该方法已经高效实现(从 gas 的角度),并且应该尽量避免在链上调用该方法(即从任何非 常量 合约函数调用,或从任何有可能在链上调用的 常量 合约函数。)。

NFT 中继数据

函数tokenMetadata()

function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl)

可选 - 建议使用该方法来提升钱包和交易所的可用性,但是接口和其他合约必须不依赖于本方法。

该方法返回引用一个外部资源包的多地址字符串,外部资源包中包含关于与 _tokenId 关联的 NFT 的(可选本地化)中继数据。该字符串必须是一条 IPFS 或者 HTTP(S) 基础路径(没有结尾斜杠),可以通过级联获得特定子路经。(由于 IPFS 具有更好的可扩展性、稳定性和不变性,所以将其作为首选格式。)

标准子路经:

  • 名称 name (必须) - name 子路经必须包含特定 NFT 的 UTF-8 编码格式名称(即与合约的 name 方法返回的集合名称区分开)。一个 name 字段的长度应该小于等于 50 个字符,并且在本合约追踪的 NFT 集合中唯一。一个 name 可以包含空白字符,但决不能包含换行符或回车符。一个 name 可以包含数字部分,用于区分同一合约中相似的 NFTs。例如:“欢乐代币 #157”。
  • 图片 image (可选) - 如果存在 image 子路经, 那么它必须指向一个 PNG、JPEF 或 SVG 格式的图片,并且在每个维度上至少有 300 个像素点。图像的长宽比应该在 16:9(风景模式) 和 2:3(人像模式)之间。图像应该使用“安全区域”来构建,这样将图像裁剪到一个最大、中心方块是不会损失任何关键信息的(要达到这个要求的最简单的方法就是使用1:1的图像长宽比。)
  • 描述 description(可选) - 如果存在 description 子路经,那么它必须包含关于此资产的 UTF-8 编码文本描述。该描述可能包含多行,并且应该使用一个换行符来明确划分每一行,连续两个换行符用于将段落分开。描述种可能包含 CommonMark ——用于兼容 Markdown 格式的编写。描述应该小于等于1500个字符。
  • 其他中继数据 other metadata(可选)- 一个合约可能包含任意数量的额外子路经,这些子路经被认为是有用的。可能未来会有独立于此标准的正式或非正式额外中继数据域标准。

每一条中继数据子路经(也包括未在此标准中定义的子路经)必须包含一个子路经, 默认 指向一个包含该中继数据元素的默认(即非本地)版本的文件。例如,中继数据路径为 /ipfs/QmZU8bKEG8fhcQwKoLHfjtJoKBzvUT5LFR3f8dEz86WdVe 的NFT必须包含该NFT的 name (名称),以作为在全路径 /ipfs/QmZU8bKEG8fhcQwKoLHfjtJoKBzvUT5LFR3f8dEz86WdVe/name/default 下可访问的UTF-8格式编码字符串。此外,每条中继数据子路经可能有一条或多条在 ISO 639-1 格式语言代码(与HTML使用的语言代码相同)的子路径中的本地化。例如, /ipfs/QmZU8bKEG8fhcQwKoLHfjtJoKBzvUT5LFR3f8dEz86WdVe/name/en 包含英文版名称, /ipfs/QmZU8bKEG8fhcQwKoLHfjtJoKBzvUT5LFR3f8dEz86WdVe/name/fr 包含法语版名称(注意!即便是本地数据也需要包含一个默认条目)。NFT 中继数据的使用者应该在使用 default (默认)值之前优先使用本地数值。使用者绝不能假设特定 NFT 的所有中继数据子路经是本地相似的。例如,更常见的是,即便 description (描述)是本地化数值,但是 name (名称) 和 image (图片)依旧不是本地化的。

你可以在这里查看本例中引用的中继数据包。

事件

转移

当通过任意机制转移 NFT 所有权时该事件必须要被触发。

此外,创建新 NFT 时也必须为每一新创建的 NFT 触发 Transfer 事件,其中地址 _from 为 0,地址 _to 为新创建的 NFT 所有者的地址(很可能是智能合约本身)。任何 NFT 的删除(或销毁)也必须触发 Transfer 事件,其中地址 _to 为 0,地址 _from 为 NFT 持有者的地址(现在是前任所有者了!)。

注:地址 _from == 地址 _to 的 Transfer 事件是有效的,具体细节请见函数transfer() 文档。

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId)

授权

任何授权方法 approve(address_spender, uint256 _value) 调用成功后必须触发的事件(除非调用者当没有任何待确认授权时尝试清除授权时,可以不触发该事件)。

具体细节请参阅 approve() 方法的文档。

event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId)

基本原理

实用性

许多以太坊智能合约的建议用途都依赖于跟踪单个非同质币(NFTs)。现有或计划中的NFTs 有很多,例如 Decentraland 中的 LAND,与CryptoPunks 项目同名的punks(朋克),以及DmarketEnjinCoin 等系统的游戏内物品。未来的用途包括检测真实世界中的非同质资产,例如房地产(例如 UbitquityPropy 等公司所设想的)。在这些情况下,项目在账本中不是“集中在一起的”,相反,每单位代币必须有独立的所有权并自动跟踪,这非常重要。无论这些项目的性质如何,如果我们有一个标准化的接口,并且建立跨功能的NFTs管理和销售平台,这将使得生态系统更加强大。

NTF IDs

该标准的基础是,每一个 NFT 在跟踪它的合约中,用唯一的一个256 位无符号整数进行标识。每个NFT 的 ID 标号在智能合约的生命周期内不允许改变。元组 ( contract address, asset ID ) 是每个特定 NFT 在以太坊生态系统中的全局唯一且完全合格的标识。虽然某些合约可能觉得 ID 从 0 开始编码,并且对于每一个新 NFT 的 ID 简单增 1 进行编码更加简便,但是使用者绝不能假设 ID 编号具有任何特定模式,并且需要将 ID 编码看做 “黑盒”。

向后兼容性

本标准尽可能遵循 ERC-20 的语义,但由于同质代币与非同质代币之间的根本差异,并不能完全兼容 ERC-20。

截至 2017 年 9 月的非同质代币实现样例:

  • CryptoPunks - 部分兼容 ERC-20,但是并不容易推广,因为其在合约中直接包含了拍卖的功能,并且明确在 name 函数中将 NFTs 称为“punks”。

  • Auctionhouse Asset Interface - @dob 需要为他的 Auctionhouse 去中心化应用程序(当前处于冰盒期)增加一个通用界面。他的“资产”合约非常简单,但是它缺少 ERC-20 的兼容性、approve() 功能,以及中继数据。这项工作在讨论 EIP-173 中也被引用。

(需要注意的是,像 Curio CardsRare Pepe 这样的“限量版、可收集代币”均属于非同质代币。他们本质上是个体可代替代币的集合,每种代币都由它自己的智能合约跟踪自己的总供给量(在某些极端情况下代币总供给量可能为 1)。)

实现

参考实现即将发布……

版权

根据CC0放弃版权和相关权利。


编者按:

  1. 本文原文是用ASD-STE100技术英语标准写成的。STE,即Simplified Technical English,是一种被仔细地限制和标准化过的英语,旨在使技术表达更加清晰、方便人的理解和机器翻译。在原文中,祈使句的动词往往用全大写,如“MUST”,“SHOULD”。在翻译中,这些大写的祈使句动词都用粗体标示出来了。

原文链接: https://github.com/ethereum/EIPs/issues/721#issuecomment-343246872
作者: Dieter Shirley
翻译&校对: stormpang & Elisa


你可能还会喜欢:
EIP6 - 以太坊改进提案 编号6
有关Gas价格的讨论——有关EIP150
科普 | 理解ERC-20 token合约

 
0 人喜欢