精华内容
下载资源
问答
  • ERC721-ElfToken 此回购是使用OpenZepplin的ERC721关于不可替代令牌的。 简单的ERC721合同。 如何安装依赖项 首次安装所需的依赖项: 您将需要本地以太坊节点,我建议ganache-cli 。 您可以从npm安装它。 npm ...
  • erc721-ipfs-uploader 安装 npm install 跑 需要数据和相关images文件夹的CSV文件 npm run start ERC721元数据JSON架构 { " title " : " Asset Metadata " , " type " : " object " , " properties " : { " ...
  • ERC721ContractLibrary.Template 开始使用OpenZeppelin创建ERC721 NFT的模板
  • 我们将在稳固性的帮助下(通过编写智能合约)在区块链上构建数字资产(ERC721令牌),然后我们将使用IPFS部署Dapp。 设定环境 我们需要掌握松露,通过npm install -g truffle 。您还需要拥有Node.js v6 + LTS和npm...
  • 如果您正在寻找功能更丰富,更高级的ERC721实现,请查看 。 结构体 所有合同和测试都在文件夹中。 有多种实现方式,您可以选择: :这是基本的ERC-721令牌实现(支持ERC-165)。 :这为令牌合同实现了可选的ERC-...
  • cd erc721-smart-contract cd smart_contracts npm install 注意:将被下载并安装。 本地部署 全局安装truffle和ganache-cli npm install --global truffle ganache-cli 打开一个终端并执行 ganache-cli 结果...
  • 这是一个非常简单的Python Flask示例应用程序,用于为指定的提供ERC721元数据。 要求 的Python 3 您将需要一台安装了Python 3的计算机。 谷歌云存储 您需要一个带有项目,存储桶和凭据的Google Cloud Storage帐户。...
  • NFTEX是一个去中心化交易所( DEX ),人们可以在其中交易其ERC721 NFT代币。 特征 NFTEX提供三种交易类型。 固定价格订单 荷兰拍卖订单 英文拍卖单 不管您选择什么,都应该放置令牌信息,价格信息和截止日期。 ...
  • EtherGoodsToken 添加高清钱包提供商 如何测试 npm install -g ethereumjs-testrpc( )testrpc 松露测试 在ROPSTEN上发布的合同 看到 编译器:( + commit.c4cbbb05.Emscripten.clang)
  • ERC721游乐场 ERC721 NFT代币的游乐场 不得不将ERC721的导入ERC721ERC721URIStorage ,以更改_setTokenURI功能相当不错的教程
  • ERC721标准与加密猫

    2021-01-20 13:58:22
    这是基于以太坊的 ERC721 标准(不可互换通证)发行的加密数字宠物,每一只猫咪各不相同。 加密猫的正式中文名叫“谜恋猫”,游戏的官网称这些加密猫是“可收藏、可繁殖、讨人喜欢的”。用户可以用以太币换购这种...
  • 以太坊ETL 以太坊ETL可让您将区块链数据转换为方便的格式,例如CSV和关系数据库。 您是否只想立即查询以太坊数据? 使用的。 。 快速开始 安装以太坊ETL: pip3 install ethereum-etl 导出块和事务( , ): ...
  • 一个ERC721 NFT代币和交易市场,用于购买和赠送以太坊为基础的收藏礼物。 礼物有: 发件人:谁在发送礼物 接收者:谁在接收礼物 消息:给接收者的消息 金额:发送给接收方的以太币的价值 样式:代表邮件样式的...
  • ERC721

    千次阅读 2018-08-08 23:39:52
    ERC721 ERC20是标准Token接口,这个规定了Token的基本功能, 是可置换的,Token之间没有区别,所有的token是一样的。ERC721则是针对不可置换Token的智能合约标准接口,(non-fungile tokens)不可置换Token简称NFTs,...

    ERC721

    ERC20是标准Token接口,这个规定了Token的基本功能, 是可置换的,Token之间没有区别,所有的token是一样的。ERC721则是针对不可置换Token的智能合约标准接口,(non-fungile tokens)不可置换Token简称NFTs,操作标准API的实现方法。

    ERC20是标准Token接口,ERC20的Token可以无限细分为10^18份,而ERC721的Token最小的单位为1,无法再分割。

    ERC721Basic.sol

    基于ERC165 合约,检测合约实现的接口,并声明了一些方法。
    检测:
    bytes4(keccak256(“onERC721Received(address, uint256, bytes)”))
    合同中的每个功能都会拥有自己的签名,调用合同时,EVM会使用一系列切换案例来查找被调用的功能签名并相应地执行代码。

    ERC721Holder.sol

    基于ERC721Receiver合约,接收一些地址数字参数

    DeprecatedERC721.sol

    takeOwnership、transfer、tokensOf

    ERC721.sol

    基于ERC721Basic 合约,声明了一些totalSupply、tokenOfOwnerByIndex、name、symbol、tokenURI方法

    ERC721BasicToken.sol

    将给定token ID的所有权转移到另一个地址
    ,如果目标地址是合约,则必须实现“onERC721Received”,
    调用安全的传输,并返回值
    “bytes4(keccak256(“onERC721Received(地址、地址、uint256字节)”))”;否则,
    转账被退回。

    ERC721Receiver.sol

    处理非功能性测试

    ERC721Token.sol

    声明的方法的实现

    ERC721 合约声明:

    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 safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
       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);
    }

    方法

    name:

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

    这个函数作用于告诉外部智能合约以及应用Token的名字

    symbol:

    该功能还有助于提供与ERC20令牌标准的兼容。 它为外部程序提供token的简称或标识。

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

    totalSupply:

    该函数返回区块链上可提供货币的总数。

    contract MyNFT {
      // 任意数
      uint256 private totalSupply = 1000000000;
      function totalSupply() constant returns (uint256 supply){
        return totalSupply;
      }
    }

    balanceOf:

    该函数是根据给定地址搜索账户中的Token的数目。

    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:

    该函数批准或授予另一实体代表所有者转移代币的权利。 例如,如果爱丽丝拥有1个NFT,她可以为她的朋友鲍勃调用approve函数。 调用成功后,鲍勃就可以代表爱丽丝以后对该代币的所有权或对该代币进行操作。

    
    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:

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

    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);
      }
    }

    safeTransferFrom:

    转移NFT所有权,一次成功的转移操作必须发起 Transer 事件。函数的实现需要做一下几种检查:
    1. 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
    1. _from 必须是 _tokenId的所有者
    1. _tokenId 应该是当前合约正在监测的NFTs 中的任何一个
    1. _to 地址不应该为 0
    1. 如果_to 是一个合约应该调用其onERC721Received方法, 并且检查其返回值,如果返回值不为bytes4(keccak256(“onERC721Received(address,uint256,bytes)”))抛出异常。
    一个可接收NFT的合约必须实现ERC721TokenReceiver接口:

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

    transferFrom:

    用来转移NFTs, 方法成功后需触发Transfer事件。调用者自己确认_to地址能正常接收NFT,否则将丢失此NFT。

    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);
      }
    }
    

    tokenOfOwnerByIndex :

    每个人同时可以拥有一个或多个Token,因为每个Token的ID都是唯一的,所以很难跟踪到每个人所拥有的Token的ID。为此,该合约记录了每个人所拥有Token的ID,因此,用户拥有的每个单独的Token都可以通过其索引在用户拥有的Token列表(数组)中检索。tokenOfOwnerByIndex可以帮助我们检索出任意token。

    
    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:

    这方法可以让我们查看Token的元数据或者指向其数据的链接。

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

    事件

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

    Transfer:

    只要Token被转手,就会触发此事件,当token的所有权从某个用户转移到另一个用户手上时该事件会被广播。 广播里会详细说明哪个帐户发送Token,哪个帐户收到Token,以及哪个Token(通过ID)被转手。

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

    Approval:

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

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

    安全考虑

    1).EVM参数传递机制:在调用函数时,如果目标函数有参数,正常情况下我们需要根据ABI指定的参数类型来构造输入。

    例如 transfer(address to, uint256 value) 在调用 transfer() 时,以太坊使用函数签名的哈希值前4字节作为 function selector,计算 sha3(transfer(address,uint256)) 得到 0xA9059CBB,再拼接上to地址,然后拼接value得到完整的calldata,将这段 calldata 附加在交易中发送到目标智能合约地址即可实现函数调用。当以太坊节点收到交易时,将 calldata 与智能合约字节码一同加载到 EVM 中,字节码在编译时生成,也意味着对参数的处理在编译时也已经固定下来了。

    在字节码中,出于动态数组的考虑,只会判断 calldata 是否小于某个最小长度,但是不会检查参数是否过长。编译器会生成一系列 CALLDATALOAD 配合数学运算来分离出函数需要的参数。

    所以当参数过多的时候,字节码、EVM都不会处理,可以直接忽略。黑客利用这一特性可以很容易地针对 CUSTOM_CALL 构造攻击参数。

    2).关于“接收通知调用”正确的代码实现:

    正确的代码实现中,对于“接收通知调用”的处理应该将被通知函数的签名(signature)写死为固定值,避免由攻击者来任意指定的任何可能性。

    正确的通知调用的写法:

    声明Receiver函数,并通过声明的函数进行接收通知调,或者通过 Receiver 函数的签名常量进行接收通知调用。例如Consensys 维护的 Token-Factory 项目:

    function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
            allowed[msg.sender][_spender] = _value;
            Approval(msg.sender, _spender, _value);
    
            //call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
            //receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
            //it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
            if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
            return true;
        }

    应用场景:

    ERC20 是标准的代币协议,它能够用于网站流通价值的积分,虚拟币等等。而ERC721的NFT让数字资产变得更有收藏价值,尤其是在加密货币收藏和网络游戏领域拥有巨大的潜力。ERC20 适用于游戏的流通价值积分,ERC721则在加密货币收藏和网络游戏领域拥有巨大的潜力,非常适合作为加密虚拟资产收藏品。

    由于同质代币与非同质代币之间的根本差异,并不能完全兼容ERC-20。

    展开全文
  • hardhat-erc721-启动器 快速开始 cd hardhat-erc721-starter npm i # list hardhat tasks: npx hardhat 安装安全帽速记 允许您使用速记“ hh” == npx安全帽 npm i -g hardhat-shorthand hardhat-completion install...
  • 该合同是根据带有松露框架的ERC-721不可替代令牌标准编译和部署的。 在部署到以太坊区块链上之后,用户将能够通过DAPP的前端界面与智能合约进行交互来玩游戏。 获取游戏卡:用户可以使用ETH获取游戏卡。 每张游戏...
  • 令牌目录,其中包含几乎所有ERC-20,ERC-721ERC-1155令牌的列表。 令牌列表格式 存在于此存储库中的ERC-20令牌列表遵循。 原始列表是使用的erc20令牌列表。 令牌描述和链接来自Coingecko的API。 此存储库中存在的...
  • 用户创建ERC721的NFT市场实施了Crypto Boy NFT并在以太坊区块链上进行管理。 特征 造币厂定制的ERC721实现了Crypto Boy令牌。 在市场上出售Crypto Boy代币。 设置所需的代币价格。 在保留要出售的令牌和不出售的...
  • 一个简单的网络工具,可让您清除ETH,erc20和erc721令牌的钱包。 特征 发送所有你的ETH 发送您的所有ERC20令牌 发送所有您的ERC721令牌 将您的ETH迁移到第2层(zksync) 将批准的令牌迁移到第2层(zksync) 将您...
  • 车辆ERC721 请安装metamask,然后从从rinkeby获取一些乙醚。 先决条件 安装依赖 $ npm install 部署到rinkeby $ export METAMASK_MNEMONIC="law profit smile clarify ...." # 12 mnemonic words $ truffle ...
  • 什么是ERC-721?现在我们看到的各种加密猫猫狗狗都是基于ERC-721创造...本文就来剖析下什么是ERC721. ERC721是什么 在创建代币一篇,我们讲到过ERC20代币, 和ERC20一样,ERC721同样是一个代币标准,ERC72...

    最新内容会更新在主站深入浅出区块链社区
    原文链接:剖析非同质化代币ERC721-全面解析ERC721标准

    什么是ERC-721?现在我们看到的各种加密猫猫狗狗都是基于ERC-721创造出来的,每只都是一个独一无二的ERC-721代币,不过ERC-721在区块链世界远不止猫猫狗狗,它更大的想象空间在于将物理世界的资产映射到区块链上。本文就来剖析下什么是ERC721.

    ERC721是什么

    创建代币一篇,我们讲到过ERC20代币,
    和ERC20一样,ERC721同样是一个代币标准,ERC721官方简要解释是Non-Fungible Tokens,简写为NFTs,多翻译为非同质代币。

    ERC721 是由Dieter Shirley 在2017年9月提出。Dieter Shirley 正是谜恋猫CryptoKitties背后的公司Axiom Zen的技术总监。因此谜恋猫也是第一个实现了ERC721 标准的去中心化应用。ERC721号提议已经被以太坊作为标准接受,但该标准仍处于草稿阶段。本文介绍的ERC721标准基于最新(2018/03/23官方提议。

    那怎么理解非同质代币呢?

    非同质代表独一无二,谜恋猫为例,每只猫都被赋予拥有基因,是独一无二的(一只猫就是一个NFTs),猫之间是不能置换的。这种独特性使得某些稀有猫具有收藏价值,也因此受到追捧。

    ERC20代币是可置换的,且可细分为N份(1 = 10 * 0.1), 而ERC721的Token最小的单位为1,无法再分割。

    如果同一个集合的两个物品具有不同的特征,这两个物品是非同质的,而同质是某个部分或数量可以被另一个同等部分或数量所代替。

    非同质性其实广泛存在于我们的生活中,如图书馆的每一本,宠物商店的每一只宠物,歌手所演唱的歌曲,花店里不同的花等等,因此ERC721合约必定有广泛的应用场景。通过这样一个标准,也可建立跨功能的NFTs管理和销售平台(就像有支持ERC20的交易所和钱包一样),使生态更加强大。

    ERC721标准

    ERC721最为一个合约标准,提供了在实现ERC721代币时必须要遵守的协议,要求每个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);
    }
    

    接口说明:

    • balanceOf(): 返回由_owner 持有的NFTs的数量。

    • ownerOf(): 返回tokenId代币持有者的地址。

    • approve(): 授予地址_to具有_tokenId的控制权,方法成功后需触发Approval 事件。

    • setApprovalForAll(): 授予地址_operator具有所有NFTs的控制权,成功后需触发ApprovalForAll事件。

    • getApproved()、isApprovedForAll(): 用来查询授权。

    • safeTransferFrom(): 转移NFT所有权,一次成功的转移操作必须发起 Transer 事件。函数的实现需要做一下几种检查:

    1. 调用者msg.sender应该是当前tokenId的所有者或被授权的地址
    2. _from 必须是 _tokenId的所有者
    3. _tokenId 应该是当前合约正在监测的NFTs 中的任何一个
    4. _to 地址不应该为 0
    5. 如果_to 是一个合约应该调用其onERC721Received方法, 并且检查其返回值,如果返回值不为bytes4(keccak256("onERC721Received(address,uint256,bytes)"))抛出异常。
      一个可接收NFT的合约必须实现ERC721TokenReceiver接口:
        interface ERC721TokenReceiver {
            /// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
            function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);
        }
    
    • transferFrom(): 用来转移NFTs, 方法成功后需触发Transfer事件。调用者自己确认_to地址能正常接收NFT,否则将丢失此NFT。此函数实现时需要检查上面条件的前4条。

    ERC165 标准

    ERC721标准同时要求必须符合ERC165标准 ,其接口如下:

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

    ERC165同样是一个合约标准,这个标准要求合约提供其实现了哪些接口,这样再与合约进行交互的时候可以先调用此接口进行查询。
    interfaceID为函数选择器,计算方式有两种,如:bytes4(keccak256('supportsInterface(bytes4)'));ERC165.supportsInterface.selector,多个函数的接口ID为函数选择器的异或值。
    关于ERC165,这里不深入介绍,有兴趣的同学可以阅读官方提案

    可选实现接口:ERC721Metadata

    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.",
            }
        }
    }
    

    tokenURI通常是被web3调用,以便在应用层做相应的查询和展示。

    可选实现接口:ERC721Enumerable

    ERC721Enumerable的主要目的是提高合约中NTF的可访问性,其接口定义如下:

    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。

    补充说明

    NTF IDs

    NTF ID,即tokenId,在合约中用唯一的uint265进行标识,每个NFT的ID在智能合约的生命周期内不允许改变。推荐的实现方式有:

    1. 从0开始,每新加一个NFT,NTF ID加1
    2. 使用sha3后uuid 转换为 NTF ID

    与ERC-20的兼容性

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

    交易、挖矿、销毁

    在实现transter相关接口时除了满足上面的的条件外,我们可以根据需要添加自己的逻辑,如加入黑名单等。
    同时挖矿、销毁尽管不是标准的一部分,我们可以根据需要实现。

    参考实现

    参考实现为订阅用户专有福利,请订阅我的小专栏:区块链技术查看。

    参考文献

    1. EIPS-165
    2. EIPS-721

    欢迎来我的知识星球深入浅出区块链讨论区块链,作为星友福利,星友可加入区块链技术付费交流群。
    深入浅出区块链 - 系统学习区块链,打造最好的区块链技术博客。

    展开全文
  • erc721令牌配方 通过Truffle在Ethereum上运行的基于ERC-721标准的令牌。
  • ERC20与ERC721详解

    2020-03-10 12:20:22
    摘要 数字加密货币大致可以分为原生币(coin)和代币(token)两大类。前者如BTC、ETH等,拥有...最近几个月间还出现了一种被称为ERC721的数字加密资产,例如CryptoKitties(参阅[block #24])。所谓ERC20和ERC72...

    摘要

    数字加密货币大致可以分为原生币(coin)和代币(token)两大类。前者如BTC、ETH等,拥有自己的区块链。后者如Tether、TRON、ONT等,依附于现有的区块链。市场上流通的基于以太坊的代币大都遵从ERC20协议。最近几个月间还出现了一种被称为ERC721的数字加密资产,例如CryptoKitties(参阅[block #24])。所谓ERC20和ERC721究竟是什么呢?

    首先,ERC是Ethereum Request for Comments的缩写,代表以太坊开发者提交的协议提案。它相当于是以太坊版的RFC(ietf.org/standards/rfcs)。ERC后面的数字是议案的编号。

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

    ERC20可能是其中最广为人知的标准了。它诞生于2015年,到2017年9月被正式标准化。协议规定了具有可互换性(fungible)代币的一组基本接口,包括代币符号、发行量、转账、授权等。所谓可互换性(fungibility)指代币之间无差异,同等数量的两笔代币价值相等。交易所里流通的绝大部分代币都是可互换的,一单位的币无论在哪儿都价值一单位。

    与之相对的则是非互换性(non-fungible)资产。比如CryptoKitties中的宠物猫就是典型的非互换性资产,因为每只猫各有千秋,而且由于不同辈分的稀缺性不同,市场价格也差异巨大。这种非标准化资产很长时间内都没有标准协议,直到2017年9月才出现ERC721提案,定义了一组常用的接口。ERC721至今仍旧处于草案阶段,但已经被不少dApp采用,甚至出现了专门的交易所(Y Combinator孵化的http://opensea.io)。

    以下详细拆解ERC20和ERC721协议,技术性偏强,目标读者是以太坊智能合约开发者。

    ERC20

    ERC20定义了三个可选函数:

    // 代币名称。比如"Sleepism Token"。

    function name() view returns (string name);

    // 代币符号。按惯例一般用全大写字母,比如“SLPT”。

    function symbol() view returns (string symbol);

    // 小数位数。不少代币采用与ETH一样的设定,也就是18位。

    // 这只影响用户界面中货币量的显示方式。代币本身都统一用uint256表示。

    function decimal() view returns (uint8 decimals);

    由于这三个函数都是返回常量,在Solidity中也可以用以下简略形式定义。solc编译器会自动生成与以上接口等价的字节码。

    string public name = "Sleepism Token";

    string public symbol = "SLPT";

    uint8 public decimal = 18;

    ERC20定义了六个必须声明的函数:

    // 代币总量。

    function totalSupply() view returns (uint256 totalSupply);

    // 查询指定地址下的代币余额。

    function balanceOf(address _owner) view returns (uint256 balance);

    // 给指定地址转入指定量的代币,转账成功则返回true。

    // 如果源地址没有足够量的代币,函数应该抛出异常。

    // 即使转零个代币,也应该触发Transfer事件(下文中有解释)。

    function transfer(address _to, uint256 _value) returns (bool success);

    // 与transfer类似的转账函数。区别在于可以指定一个转出地址。

    // 如果当前地址得到了转出地址的授权,则可以代理转账操作。

    function transferFrom(address _from, address _to, uint256 _value)

    returns (bool success);

    // 授权指定地址特定的转账额度。

    // 被授权的地址可以多次调用transferFrom函数代替源地址转账,总值不超过_value。

    // 实际使用时,每次重设额度前应该先调用approve(_spender, 0),

    // 等交易被确认后再调用approve(_spender, newAllowance)。

    // 如果直接调用一次approve,被授权地址有机会转出高出指定额度的代币。

    function approve(address _spender, uint256 _value) returns (bool success);

    // 查询指定授权地址剩余的转账额度。

    function allowance(address _owner, address _spender) view returns (uint256 remaining);

    ERC20还定义了两个事件:

    // 转账事件。必须在成功转账(哪怕是零个代币)时触发。

    event Transfer(address indexed _from, address indexed _to, uint256 _value);

    // 授权事件。必须在成功授权时触发。

    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    官方文档参见github.com/ethereum/EIP

    ERC721

    ERC721与ERC20有些相似,但由于它管理的是非互换性资产(non-fungible token,简称NFT),所以函数语义并不一样。合约下每份ERC721资产都拥有一个uint256类型的独立编号(以下代码中的_tokenId)。

    ERC721事件:

    // 转账事件。从_from地址转移_tokenId对应资产的所有权到_to地址时触发。

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

    // 授权转账事件。把_owner地址控制的_tokenId资产授权给_approved地址时触发。

    // 发生转账事件时,对应资产的授权地址应被清空。

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

    // 授权管理事件。_owner地址授权或取消授权_operator地址的管理权时触发。

    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    ERC721函数:

    // 查询_owner地址拥有的NFT总个数。

    function balanceOf(address _owner) external view returns (uint256);

    // 查询_tokenId资产的所属地址。

    function ownerOf(uint256 _tokenId) external view returns (address);

    // 将_from地址所拥有的_tokenId资产转移给_to地址。

    // 调用方必须是资产主人或是已被授权的地址,否则会抛出异常。

    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    // 与transferFrom类似的资产转移函数。

    // 它会额外检查_to地址和_tokenId的有效性,另外如果_to是合约地址,

    // 还会触发它的onERC721Received回调函数。

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    // 与上述接口类似的资产转移函数。

    // 唯一不同点是可以传入额外的自定义参数。

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

    // 把_tokenId资产授权给_approved地址。

    function approve(address _approved, uint256 _tokenId) external payable;

    // 查询_tokenId资产对应的授权地址。

    function getApproved(uint256 _tokenId) external view returns (address);

    // 指定或撤销_operator地址的管理权限。

    function setApprovalForAll(address _operator, bool _approved) external;

    // 查询_operator地址是否已经获得_owner地址的管理权。

    function isApprovedForAll(address _owner, address _operator) external view returns (bool);

    ERC721元数据接口(可选项):

    // 资产名称。比如"Sleepism Collectible"。

    function name() external pure returns (string _name);

    // 资产符号。比如"SLPC"。

    function symbol() external pure returns (string _symbol);

    // 描述_tokenId资产的URI。指向一个符合ERC721元数据描述结构的JSON文件。

    function tokenURI(uint256 _tokenId) external view returns (string);

    元数据描述结构如下所示:

    {

    "title": "Sleepism Collectible Metadata",

    "type": "object",

    "properties": {

    "name": {

    "type": "string",

    "description": "Sleep and his Half-brother Death",

    },

    "description": {

    "type": "string",

    "description": "A painting by John William Waterhouse",

    },

    "image": {

    "type": "string",

    "description": "i.imgur.com/sahhbd.png",

    }

    }

    }

    ERC721枚举扩宽接口(可选项):

    // 合约管理的总NFT数量。

    function totalSupply() external view returns (uint256);

    // 查询第_index个资产的编码。

    function tokenByIndex(uint256 _index) external view returns (uint256);

    // 查询_owner地址拥有的第_index个资产的编码。

    function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256);

    符合ERC721协议的合约还需要符合ERC165规范,实现以下函数:

    // 查询合约是否实现了interfaceID对应的接口。

    function supportsInterface(bytes4 interfaceID) external view returns (bool);

    interfaceID由bytes4(keccak256(函数签名))计算得到。有多个函数时,将全部byte4异或(xor)得到最终结果。详见ERC165标准文档(github.com/ethereum/EIP)。

    例如以下接受转账回调函数接口的interfaceID就是0xf0b9e5ba:

    interface ERC721TokenReceiver {

    function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);

    }

    这是支持ERC721资产的钱包或交易平台需要实现的代码。发放ERC721资产的合约本身不需要处理。

    ERC721官方文档参见github.com/ethereum/EIP

    相关ERC

    ERC223是对ERC20的小改进,增加了tokenFallback函数,更好地处理错误情况。详见github.com/ethereum/EIP

    ERC621在ERC20的基础上添加了increaseSupply和decreaseSupply函数,控制代币总量的增减。详见github.com/ethereum/EIP

    更近期提出的ERC827则是增强了函数回调功能。详见github.com/ethereum/EIP

    以上这些ERC尚未标准化,普及率也不高。目前参考一下即可。

    如果你有疑问欢迎加微信咨询:


    v2-52b6f837be283214a9815bdaf2650b34_b.jpg


    也可以关注我的公众号想我提问:

    v2-0a9159f405d92730d1183d93d381d65d_b.jpg

    展开全文
  • ERC721非同质代币(NFT)介绍

    千次阅读 2020-10-24 09:20:52
    本文简要介绍了以太坊上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

    展开全文
  • 库存演示 快速开始 确保安装MetaMask: git clone git@github.com:asobicoin/inventory-demo.git cd inventory-demo npm i npm run dev 为了更好地使用Vue.js,请安装官方的Vue.js devtools。 ...参考
  • 用松露,细雨和Vue制成的ERC-721示例 ERC-721,其中NFT令牌可以分为多个类别(形状): 界 方格 三角形 等等(合同所有者可以创建新类别) 该示例与的相似,不同的是在这种情况下,令牌(不仅仅是类别)是完全不可...
  • 低密度脂蛋白 最不发达国家 :castle: LDB,即LORDLESS大楼,是LORDLESS中的核心数字资产。 虚拟世界中的大多数交互都依赖于LDB。 LDB的核心信息将存储在区块链上,以确保数字资产的不变性。 -lordless.io ...
  • 全面理解ERC721的实现机制

    千次阅读 2018-05-10 07:36:15
    TL; DR 基本上,由于ERC721的所有权基于唯一索引或ID的所有权,因此... ERC721令牌 围绕对ERC721的兴趣,我已经看到了许多关于可以保存元数据的非可替换标记的材料,但是我发现的材料深度让我寻找更多细节。 我对ERC...
  • @Openzeppelin:ERC721

    千次阅读 2021-07-05 11:51:34
    import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; 符合 ERC721 的合约所需的接口函数。 // FUNCTIONS balanceOf(owner) ownerOf(tokenId) safeTransferFrom(from, to, tokenId) transferFrom...
  • ERC721代码详解

    千次阅读 2018-10-31 11:16:11
    ERC-721是非同质化代币(Non-Fungible Token,缩写为NFT)的标准接口。ERC-721在2017年底首先被CryptoKitties游戏所使用,CryptoKitties的火爆让人们意识到了ERC-721的重要价值。ERC-721是非同质化代币,也就意味着...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,841
精华内容 736
关键字:

erc721