咱们能够用以太坊智能合约来模拟稀有的收藏品,每个通证都遵循以太坊ERC-721标准, 它是DieterShirley在2017年底提出的以太坊改进建议书。ERC721可使智能合约像相似于 ERC20代币同样进行交易, 区别在于,ERC721通证是独一无二的,每个都有惟一的ID,具备不可取代性。git
若是你但愿立刻开始学习以太坊智能合约和应用开发,能够访问汇智网提供的出色的在线互动教程:github
ERC721约定了一些接口函数,使它在必定程度上符合ERC20代币标准。这么作是为了让现有的 钱包更容易显示代币的基本信息。这些函数可让符合ERC721标准的智能合约像比特币或者 以太币这样普通的数字加密币同样,经过智能合约编程的方式定义一些功能让用户实现向他人 发送代币或检查帐户余额等操做。编程
这是一个简明的ERC721智能合约声明:app
contract ERC721 { //与ERC20兼容的接口 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); //全部权相关的接口 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); //通证元数据接口 function tokenMetadata(uint256 _tokenId) constant returns (string infoUrl); //事件 event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); }
该函数应当返回通证的名称。 例如:函数
contract MyNFT { function name() constant returns(string name){ return "My Non-FungibleToken"; } }
该函数应当返回通证的符号,它有助于提升与ERC20的兼容性。例如:学习
contract MyNFT { function symbol() constant returns(string symbol){ return "MNFT"; } }
该函数应当返回区块链上供应的通证总数量,该数量不必定是固定不变的。 例如:区块链
contract MyNFT { //想发行多少取决于你 ;) uint256 private totalSupply = 1000000000; function totalSupply() constant returns (uint256supply){ return totalSupply; } }
该函数用于查询某一地址里的通证余额。例如:ui
contract MyNFT { mapping(address => uint) privatebalances; function balanceOf(address _owner) constant returns(uint balance){ return balances[_owner]; } }
下面这些函数定义了合约如何处理通证的全部权及如何转移全部权。其中最重要的两个函数 是获取(takeOwnership)和转帐(transfer),用来实现用户之间的通证流转,就像银行的提款 和汇款功能。加密
该函数返回通证持有人的地址。由于每个ERC721通证都是不可替代的,所以能够在区块链上 惟一的地址找到,咱们能够用通证的ID来肯定其持有人。3d
contract MyNFT { mapping(uint256 => address) privatetokenOwners; mapping(uint256 => bool) private tokenExists; function ownerOf(uint256 _tokenId) constant returns (address owner) { require(tokenExists[_tokenId]); return tokenOwners[_tokenId]; } }
该函数用来受权给另外一主体表明持有人进行通证转移操做。例如,假设Alice有一个ERC721通证,她能够 调用approve
函数来受权给她的朋友Bob,而后Bob就能够表明Alice行使通证持有人的权利。
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
函数来从另外一个用户的帐户 中提取ERC721通证。
所以,在一个用户被(其余人)受权拥有必定数量的通证的状况下,能够经过该功能将这部分 通证从另外一个用户的帐户中提取出来。
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[oldOwner] += 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); } }
这个函数是可选的,但推荐你实现它。
每个ERC721通证的持有者能够同时持有不止一个通证,由于每一个通证都有惟一的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]; } }
就像咱们以前所说的,使物品具备不可替代性的是它们独一无二的特质。美圆和网球卡不可替代, 由于它们的特征不一样。可是,在区块链上将这些区分每一个通证的特征储存下来成本很高,也不推荐这么作。 为了解决这个问题,咱们能够储存每一个通证的引用(references),例如IPFS哈希或HTTP(S)连接,这些 引用,被称做元数据。元数据是可选的。
tokenMetaData函数应当返回通证的元数据,或者通证数据的连接。
contract MyNFT { mapping(uint256 => string) tokenLinks; 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); }
完整的源代码在这里。