NFT的起源:ERC721详解及样例

admin 10℃ 0评论

我们所创造的NFT本质上依然是一张以太坊合约,NFT代币和FT代币(ERC20代币)所区别的地方在于合约创造出的数据结构不同,NFT其带来的深远影响才刚刚开始。一切都要从ERC721这个起点说起。

ERC是Ethereum Request for Comments的缩写,代表以太坊开发者提交的协议提案。

打算制定以太坊新标准的开发者可以在https://github.com/ethereum/EIPs/issues先创建一个EIP(Ethereum Improvement Proposal,以太坊改进提案),详细描述协议。经过公开审议之后,获得广泛认同的提案会被标准化,列入https://github.com/ethereum/EIPs。有些改动触及区块链共识,比如增加虚拟机操作符,属于核心层变更。而另一些提案则不涉及修改以太坊本身的代码,只是约定俗成的上层协议,它们通常被归类为ERC标准。
猫本聪,知乎

ERC-721定义了智能合约必须实现的最小接口,以允许对唯一令牌(NFT)进行管理,拥有和交易。

完整定义:

https://eips.ethereum.org/EIPS/eip-721

所谓的接口,本质上是人为定义的一组规则,未来的软件编写通常以此为依据,和贸易规则,ISO标准等都是一样的。


Every ERC-721 compliant contract must implement the ERC721 and ERC165 interfaces(每个ERC721标准合约需要实现ERC721及ERC165接口),接口定义如下:

pragma solidity ^0.4.20;
interface ERC721 /* is ERC165 */ {
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);
function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool);}

?ERC165同样是一个合约标准,这个标准要求合约提供其实现了哪些接口,这样再与合约进行交互的时候可以先调用此接口进行查询。

 interface ERC165 { function supportsInterface(bytes4 interfaceID) external view returns (bool); }

一个可接收NFT的合约必须实现ERC721TokenReceiver接口:

interface ERC721TokenReceiver {        /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`        function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);    }

?(可选接口)ERC721Metadata 接口用于提供合约的元数据:name , symbol 及 URI(NFT所对应的资源)。

interface ERC721Metadata /* is ERC721 */ {    function name() external pure returns (string _name);    function symbol() external pure returns (string _symbol);    function tokenURI(uint256 _tokenId) external view returns (string);}
  • ?name(): 返回合约名字。

  • symbol(): 返回合约代币符号。

  • tokenURI(): 返回_tokenId所对应的外部资源文件的URI(通常是IPFS或HTTP(S)路径)。外部资源文件需要包含名字、描述、图片,其格式的要求如下:

{"title": "Asset Metadata","type": "object","properties": {"name": {"type": "string","description": "Identifies the asset to which this NFT represents" },"description": {"type": "string","description": "Describes the asset to which this NFT represents" },"image": {"type": "string","description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." } }}

没错儿,上一篇帖子里包含Beeple作品下载地址的描述文件就是按照上述格式编写的。

(可选接口)ERC721Enumerable的主要目的是提高合约中NFT的可访问性,方便对其细节进行查询

interface ERC721Enumerable /* is ERC721 */ {    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(): 通过索引返回对应的tokenId。

  • tokenOfOwnerByIndex(): 所有者可以一次拥有多个的NFT, 此函数返回_owner拥有的NFT列表中对应索引的tokenId。


以下是一个ERC721合约的实现样例及具体功能说明(为方便阅读主体部分,有删减和增加注释,原始文件请查阅以下链接):

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol

contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {    using Address for address;    using Strings for uint256;    //NFT名字    string private _name;    //NFT符号(缩写名)    string private _symbol;    //solidity里的映射可以理解为python里的字典,建立键-值的对应关系,可以通过键来查找值,键必须是唯一的,但值可以重复。    //定义方式为:mapping(键类型=>值类型),例如mapping(address=>uint)  public  balances,这个映射的名字是balances,权限类型为public,键的类型是地址address,值的类型是整型uint
//构造NFT代币们,每个NFT有唯一编号,对应其所有人地址 mapping (uint256 => address) private _owners; //构造地址-余额对应,方便查询每个地址的余额(有多少个NFT代币) mapping (address => uint256) private _balances; //构造授权 mapping (uint256 => address) private _tokenApprovals; mapping (address => mapping (address => bool)) private _operatorApprovals;
//构造器,通过赋予值来写入相应数据 constructor (string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; } //ERC165接口 function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); } //返回由_owner 持有的NFTs的数量 function balanceOf(address owner) public view virtual override returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _balances[owner]; } //返回tokenId代币持有者的地址 function ownerOf(uint256 tokenId) public view virtual override returns (address) { address owner = _owners[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } //NFT名 function name() public view virtual override returns (string memory) { return _name; } //NFT缩写代号 function symbol() public view virtual override returns (string memory) { return _symbol; } //NFT源数据地址 function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI(); return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ''; } //授予地址to具有_tokenId的控制权 function approve(address to, uint256 tokenId) public virtual override { address owner = ERC721.ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner");
require(_msgSender() == owner || isApprovedForAll(owner, _msgSender()), "ERC721: approve caller is not owner nor approved for all" ); _approve(to, tokenId); } //查询地址授权(单个NFT) function getApproved(uint256 tokenId) public view virtual override returns (address) { require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId]; } //授予地址operator具有所有NFTs的控制权 function setApprovalForAll(address operator, bool approved) public virtual override { require(operator != _msgSender(), "ERC721: approve to caller");
_operatorApprovals[_msgSender()][operator] = approved; emit ApprovalForAll(_msgSender(), operator, approved); } //查询地址授权(所有的NFT) function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { return _operatorApprovals[owner][operator]; } //单个NFT转移,与safeTransferFrom()不同之处在于调用者必须自己确认_to地址能正常接收NFT,否则将丢失此NFT function transferFrom(address from, address to, uint256 tokenId) public virtual override { //solhint-disable-next-line max-line-length require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId); } //调用下一个函数,转移NFT所有权 function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override { safeTransferFrom(from, to, tokenId, ""); } //调用者需要同时是NFT的拥有者,调用下方函数 function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); _safeTransfer(from, to, tokenId, _data); } //如果目标地址是一个合约,则调用其onERC721Received方法,否则抛出异常 function _safeTransfer(address from, address to, uint256 tokenId, bytes memory _data) internal virtual { _transfer(from, to, tokenId); require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } //检查某ID是否存在对应NFT代币,NFT代币从筑造起存在,如若被销毁则不存在 function _exists(uint256 tokenId) internal view virtual returns (bool) { return _owners[tokenId] != address(0); } //检查spender是否有权控制编号为tokenId的NFT代币 function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { require(_exists(tokenId), "ERC721: operator query for nonexistent token"); address owner = ERC721.ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } //铸造编号为tokenId的NFT代币并将其转移到地址to function _safeMint(address to, uint256 tokenId) internal virtual { _safeMint(to, tokenId, ""); } function _safeMint(address to, uint256 tokenId, bytes memory _data) internal virtual { _mint(to, tokenId); require(_checkOnERC721Received(address(0), to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); } //铸币函数 function _mint(address to, uint256 tokenId) internal virtual { require(to != address(0), "ERC721: mint to the zero address"); require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId);
_balances[to] += 1; _owners[tokenId] = to;
emit Transfer(address(0), to, tokenId); } //销毁函数 function _burn(uint256 tokenId) internal virtual { address owner = ERC721.ownerOf(tokenId); // Clear approvals _approve(address(0), tokenId);
_balances[owner] -= 1; delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId); } //转移编号为tokenId的NFT function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); // Clear approvals from the previous owner _approve(address(0), tokenId);
_balances[from] -= 1; _balances[to] += 1; _owners[tokenId] = to;
emit Transfer(from, to, tokenId); }
function _approve(address to, uint256 tokenId) internal virtual { _tokenApprovals[tokenId] = to; emit Approval(ERC721.ownerOf(tokenId), to, tokenId); }
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool){ if (to.isContract()) { try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { return retval == IERC721Receiver(to).onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC721: transfer to non ERC721Receiver implementer"); } else { // solhint-disable-next-line no-inline-assembly assembly { revert(add(32, reason), mload(reason)) } } } } else { return true; } }}

简单总结一下,我们所创造的NFT本质上依然是一张以太坊合约,但是其独特的数据结构使得该合约可以创造带有独特序号的代币,这也是NFT代币和FT代币(ERC20代币)所区别的地方,至于功能函数都还在不断完善中,你看,所有代币的本质就是区块链上的一组数据,但是其带来的深远影响才刚刚开始。

文章转载自微信公众号:NFT大爆炸

转载请注明:每日区块链-火币-OKEX-币安-比特币 » NFT的起源:ERC721详解及样例

喜欢 (0)
发表我的评论
取消评论
表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址