查看原文
其他

教程 | 剖析ERC721——了解非同质以太坊代币

Gerald Nash 以太坊爱好者 2019-01-23

许多人已经听说过以太坊区块链上的一款新游戏,名为以太猫(CryptoKitties)。由于创意独特新颖并且在以太坊网络上初步表现不错,该款游戏近期在加密货币社区内数次成为头条新闻。在加密猫这款游戏中,玩家可以购买、出售、交易以及饲养电子猫。这些电子猫可被视为“可繁殖的豆豆娃(Beanie Babies,一款玩具公仔的名称),因为每只猫在某种程度上是独一无二的。这种独特性使得加密猫极具收藏价值,因为人们会对好几款猫咪的特征产生兴趣并希望拥有许多只。

-来源:Vice Media(一家北美互联网媒体公司)-

然而,可收藏的对象并不限于电子猫。人类一直以来有收藏东西的历史;这没什么新鲜的。从物理货币到神奇宝贝卡,人们喜爱收集。这是一种出于对稀缺物品的独特兴趣而形成的习惯。商品价值与其稀缺程度有关,与此类似的是,收藏物对于收藏者的价值与其和其它物品相比起来的稀罕程度相关。

我们能够将以太坊代币看作稀有且具有收藏价值的物品,并且其中每个代币都遵循以太坊社区内被称为ERC721的标准。以太坊代币标准721,或称ERC721,是Dieter Shirley在2017年底引入的一项以太坊改进计划。这项提议标准让智能合约能够像ERC20一样按照可交易代币进行操作。ERC721代币是独一无二的,原因在于这些代币是非同质的

ERC:非同质代币标准 · 问题 #721 · 以太坊/EIPs(编者注:中译本见文末超链接)


同质——拥有后述属性的某个事物(例如金钱或商品):在支付债务或结算时,某个部分或数量可以被另一个同等部分或数量所代替。来源:Merriam-Webster(韦氏词典)


同质性实质上是资产(或本文中的代币)的一种特性,其决定在交易或实用过程中同等或相似类型的物品或数量是否可以完全互换。例如,用下方这张美国的5美元纸币可以从一家便利店购买一瓶汽水。

它是有价值的,可以用来购买同等或更少价值的物品。然而,当某个人拿着下面的棒球卡片去购买汽水时,店主不会接受的。

-来源:Baseball Card Stars(棒球明星卡)-

当上面的Carlos Santana棒球卡像上面那张纸币一样价值5美元时,为何店主还是不会接受它呢?这是因为这张棒球卡和这张纸币拥有不同的特征,这些特征规定着两者对特定人群的价值。一名7岁的儿童可能愿意花7美元买这张棒球卡,因为他喜欢卡片上的颜色。但是,店主觉得这张卡片5美元都不值,原因很简单,即它不像美元一样是由美联储发行的。棒球卡片和美元纸币拥有的独特属性使它们失去同质性,因为它们依据交易而具有不同的价值并且并不能总是对其进行可互换式使用。

就具有收藏价值的物品而言,如果同一个集合的两个物品具有不同的特征,这两个物品是非同质的。例如物理货币,金币与铜币之间是非同质的,因为两者相异的特征赋予它们的价值对于收藏者来说是不同的。

ERC721代币可以在任何交易所中使用,但是它们的价值取决于和每个代币相关联的独特性及稀缺性。该标准定义了函数 name 、symboltotalSupplybalanceOfownerOfapprovetakeOwnershiptransfertokenOfOwnerByIndex以及tokenMetadata,还定义了两个事件:Transfer(转移)和Approval(授权)。

注意:此为一份ERC721合约样例的简要声明。

contract ERC721 {   // ERC20 compatible functions   function name() constant returns (string name);   function symbol() constant returns (string symbol);   function totalSupply() constant returns (uint256 totalSupply);   function balanceOf(address _owner) constant returns (uint balance);   // Functions that define ownership   function ownerOf(uint256 _tokenId) constant returns (address owner);   function approve(address _to, uint256 _tokenId);   function takeOwnership(uint256 _tokenId);   function transfer(address _to, uint256 _tokenId);   function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId);   // Token metadata   function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl);   // Events   event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);   event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); }

合约内各字段概述如下。

谨记:下列编码仅为教学目的,未经测试。请勿在生产应用程序中进行执行!

类似ERC20的函数

ERC721定义的一些函数使其和ERC20代币标准存在一致的地方。这样做的目的在于更加方便现有钱包展示有关于代币的简单信息。这些函数使符合该标准的智能合约表现得像诸如比特币或以太坊之类的普通加密货币,方式在于定义使用户执行诸如将代币发送给其他人和核实账户余额等动作的函数。

函数name

该函数用于告诉外部合约和应用程序该代币的名称。该函数的一项实现示例可以如下。

contract MyNFT {  function name() constant returns (string name){    return "My Non-Fungible Token";  } }

函数symbol

该函数同时有助于提供与ERC20代币标准的兼容性。其向外部程序提供该代币的简称或标识符。

contract MyNFT {  function symbol() constant returns (string symbol){    return "MNFT";  } }

函数totalSupply

该函数会返回区块链上可提供的货币的总数量。这种供给不一定要保持不变。

contract MyNFT {  // This can be an arbitrary number  uint256 private totalSupply = 1000000000;  function totalSupply() constant returns (uint256 supply){    return totalSupply;  } }

函数balanceOf

该函数用来获得某个给定地址拥有的代币数量。

contract MyNFT {  mapping(address => uint) private balances;  function balanceOf(address _owner) constant returns (uint balance)  {    return balances[_owner];  } }

所有权函数

这些函数定义了合约处理代币所有权的方式以及转移所有权的方式。其中最值得注意的函数为takeOwnership和transfer,分别担任提取和发送功能,且对用户之间转移代币来说必不可少。

函数ownerOf

该函数返回某代币的所有者的地址。由于每个ERC721代币都是非同质且因此独一无二的,其在区块链上通过一个唯一的ID被查询。我们可以利用其ID判断代币的所有者。

contract MyNFT {  mapping(uint256 => address) private tokenOwners;  mapping(uint256 => bool) private tokenExists;  function ownerOf(uint256 _tokenId)  constant returns (address owner) {    require(tokenExists[_tokenId]);    return tokenOwners[_tokenId];  } }

函数approve

该函数批准或授予另一个实体代表所有者转移代币的权利。例如,如果Alice拥有1个MyNFT(我的非同质代币),她可以为朋友Bob调用 approve 函数。调用成功后,Bob稍后就可以代表Alice获得该代币的所有权或对该代币进行操作。关于所有权转移的更多内容可参见函数 takeOwnership 和 transfer 。

contract MyNFT {  mapping(address => mapping (address => uint256)) allowed;  function approve(address _to, uint256 _tokenId){    require(msg.sender == ownerOf(_tokenId));    require(msg.sender != _to);    allowed[msg.sender][_to] = _tokenId;    Approval(msg.sender, _to, _tokenId);  } }

函数takeOwnership

该函数担任提现功能,因为某个外部方可以调用它来从另一个用户的账户中提取出代币。因此,在某个用户已经被授权获得一定数额的代币并且希望从另一个用户的余额中提取所述代币的时候,可以使用函数 takeOwnership 。

contract MyNFT {  function takeOwnership(uint256 _tokenId){    require(tokenExists[_tokenId]);    address oldOwner = ownerOf(_tokenId);    address newOwner = msg.sender;    require(newOwner != oldOwner);    require(allowed[oldOwner][newOwner] == _tokenId);    balances[oldOwner] -= 1;    tokenOwners[_tokenId] = newOwner;    balances[newOwner] += 1;    Transfer(oldOwner, newOwner, _tokenId);  } }

函数transfer

转移代币的下一个方法就是使用该函数。函数transfer让代币所有者将其代币发送给另一个用户,与独立加密货币类似。然而,只有收款账户事先被打款账户授予获得代币的权利,转账才能开始。

contract MyNFT {  mapping(address => mapping(uint256 => uint256)) private ownerTokens;  function removeFromTokenList(address owner, uint256 _tokenId) private {    for(uint256 i = 0;ownerTokens[owner][i] != _tokenId;i++){      ownerTokens[owner][i] = 0;    }  }  function transfer(address _to, uint256 _tokenId){    address currentOwner = msg.sender;    address newOwner = _to;    require(tokenExists[_tokenId]);    require(currentOwner == ownerOf(_tokenId));    require(currentOwner != newOwner);    require(newOwner != address(0));    removeFromTokenList(_tokenId);    balances[oldOwner] -= 1;    tokenOwners[_tokenId] = newOwner;    balances[newOwner] += 1;    Transfer(oldOwner, newOwner, _tokenId);  } }

函数takenOfOwnerByIndex(可选—推荐)

每个非同质代币所有者可以一次拥有1个以上的代币。每个代币通过唯一的ID来查询,然而,追踪某个用户可能拥有的每个代币也许存在困难。为此,合约记录着每个用户拥有的所有代币的ID。因此,某个用户拥有的每个代币都可以在该用户拥有的代币列表(数组)中通过索引检索到。 tokenOfOwnerByIndex

contract MyNFT {  mapping(address => mapping(uint256 => uint256)) private ownerTokens;  function tokenOfOwnerByIndex(address _owner, uint256 _index) constant returns (uint tokenId){    return ownerTokens[_owner][_index];  } }

元数据函数

就像我们之前说的,非同质物之所以非同质是因为其一系列独特的属性。美元和棒球卡不是同质的,因为它们具有不同的特征。然而,将表明每个代币最典型特征的数据存储在区块链上十分昂贵,我也不建议这样做。为了防止这种情况,我们可以将关联到每个代币属性的索引(如IPFS哈希或HTTP链接)存储在链上,这样一来,链外的程序就可以执行逻辑以获取更多有关该代币的信息。这些索引就是“有关数据的数据”,也可以叫做元数据。

tokenMetadata(可选—推荐)

该函数让我们发现代币的元数据或指向其数据的链接。

contract MyNFT {  function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl) {    return tokenLinks[_tokenId];  } }

事件

无论合约在何时调用事件,事件都会被触发。一旦事件被触发,就向任何正在监听的程序广播这些事件。外部程序监听区块链事件,从而可以在事件被触发后利用事件提供的信息执行逻辑。ERC721标准定义了以下两个事件。

转帐

该事件在代币被转手时得以触发。当代币的所有权从某个用户转移到另一个用户手上时,该事件被广播。其详细说明了哪个账户发送该代币,哪个账户接收该代币,以及哪个代币(通过ID定义)被转移。

contract MyNFT {  event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); }

授权

该事件在某个用户授权另一个用户获得某个代币的所有权时(即,当授权被执行时)得以触发。其详细说明了哪个账户目前拥有该代币,哪个账户获许在未来获得该代币,以及哪个代币(通过ID定义)被授权转移其所有权。

contract MyNFT {  event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); }

我执行的ERC721标准源代码可以在这里找到。

和ERC20类似,该新提议的ERC721标准为新型智能合约扮演非同质物品的角色开辟了一条新的道路。就如加密猫、Decentraland、加密庞克(CryptoPunks)及其他许多应用程序中所见,非同质代币被证明是高需求产品。甚至维基解密都拥有好几只高价值加密猫呢!然而,该标准最终将会进一步扩大加密货币经济并且帮助该领域获得进一步的发展。

谢谢你阅读本文!在推特上关注我们吧。

在我写作的时候, ERC721 还不是一个官方标准,并且仍在持续改进中。


原文链接: https://medium.com/crypto-currently/the-anatomy-of-erc721-e9db77abfc24
作者: Gerald Nash
翻译&校对: 张凌 & Elisa


你可能还会喜欢:

ERC721: Non-fungible Token Standard

教程 | 如何在Ethereum上编写自己的CryptoKitties风格的游戏
白皮书 | 以太猫白皮书

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存