当前位置:首页>资讯>NFT>如何基于ERC-721的NFT应用开发?ERC标准是什么?

如何基于ERC-721的NFT应用开发?ERC标准是什么?

ERC是指以太坊已正式化的提案, ERC-721其实就代表721号提案. 通过各种流程的审议与测试后, NFT系统开发:shucang360,一个非正式的EIP就可以转变成ERC, 成为标准化的协议.

1. 什么是ERC?


ERC是指以太坊已正式化的提案, ERC-721其实就代表721号提案. 通过各种流程的审议与测试后, 一个非正式的EIP就可以转变成ERC, 成为标准化的协议.

从开发者的角度来说, ERC就是需要实现的一些接口. 这些接口的底层实现可以参考OpenZeppelin

在 EIP-1 中具体阐明了EIP的作用与生命周期. 具体审议流程如下:

标准化提案的作用就是给开发者一些模版帮助开发, 同时也给开发者一些标准的限制.

  • 开发者不用从底层开始完整进行设计与实现, 而只需根据一些标准快速开发DApp. 如今借助这些标准, 甚至有些网站做到只需要填4个数值就可以实现发币的功能.
  • 开发者可以遵循这些标准, 这样能做到整个生态的互通以及其他软件适配上的便利. 比如: MyEtherWallet不用频繁更新就可以支持多个币种.

2. 有哪些ERC?


以下是部分会被频繁使用到的ERC标准.

Fun fact: 在查询 以太坊EIP官方文档 时, 可以发现GitHub仓库内的文件是按照 字符串顺序排序 的, 因此EIP-2排在EIP-1996之后, 检索时需要注意.

ERC-20:

同质化代币的基础标准, 在2015年被Vitalik提出, 旨在标准化一些常见去中心化应用. 其中他探讨了函数的变量命名规则, 以及同质化代币的一些必要方法和事件.

以下是ERC-20的具体内容:

contract ERC20Interface {
    function name() public view returns (string)
    function symbol() public view returns (string)
    function decimals() public view returns (uint8)

    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
    function transfer(address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
  • namesymboldecimals 这三个方法非必需, 作用是提高可用性, 与实际功能无关.
  1. name(): 返回代币的名称, 比如: Ethereum.
  2. symbol(): 返回代币的代号, 比如: BTC.
  3. decimals(): 返回整数, 表示1个代币能被几位小数表示.
  • ERC-20中的必要方法.
  1. totalSupply() : 返回代币的总量.
  2. balanceOf(): 输入账户地址, 返回地址内的代币余额.
  3. transfer()transferFrom(): 必须触发Transfer事件(包括数额为0). 从总量中转账给账户地址/从一个账户转账给另一个账户.
  4. allowance(): 在进行转账时, 查询剩余可转账额度.
  5. approve(): 设置消费者代币转移额度限制, 成功后触发Approve事件. 当重写限制时, 必须先重写为0, 再设置真实参数, 以避免类似攻击.

通过OpenZeppelin的IERC-20实现, 我们可以了解通过应用ERC-20所完成的业务逻辑, 具体内容参考此文章:

– transfer() 转账逻辑:

1. 验证付款方与收款方地址是否合法

2. 查询付款方余额

3. 验证付款方余额对于转账金额是否充足

4. 在付款方地址中减去转账金额

5. 在收款方地址中加上转账金额

6. 触发Transfer事件

7. 正式完成交易

– transferFrom() 授权转账逻辑:

  1. 调用 transfer() 进行转账
  2. 查询操作者在此账户内可转账额度
  3. 验证剩余额度是否足够本次交易, 不足则回滚交易
  4. 正式完成交易
  • mint()burn() 代币增发销毁逻辑:
  1. 验证地址是否合法
  2. 增加/减少总量和地址内余额(销毁时会验证账户内余额是否大于销毁数额)
  3. 触发Transfer事件

3. ERC-721 概述


非同质代表独一无二,CryptoKitties为例, 每只猫都拥有独一无二的基因. 一只猫就是一个NFT. 猫和猫之间是不能置换的. 这种独特性使得某些稀有猫具有收藏价值, 也因此受到追捧.

以下是ERC-721的具体内容, 我们先简要介绍一下, 之后会在实际开发过程当中深入探讨:

pragma solidity ^0.4.20;
        interface ERC721 /* is ERC165 */ {
            event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
            event Approval(address indexed _owner, address indexed _approved, uint256 indexed _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);
        }

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

        interface ERC721TokenReceiver {
            function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes _data) external returns(bytes4);
         }

通过OpenZeppelin的IERC-721实现, 我们可以了解ERC-721的接口底层设计, 具体内容可以参考此文章:

  • 与IERC-20类似的设计这里就不再赘述.
  • 查询与查找函数:
  1. ownerOf(): 输入tokenId, 返回物品现主人的地址
  2. tokenURI(): 输入tokenId, 检索对应的token, 返回memory中的定位符 Storage可以理解为永久存储, 比如状态变量; Memory类似内存, 会被回收, 越过作用域不可被访问.
  • transferFrom() 转账逻辑:
  1. 验证转账是否被允许(参数:from, to, tokenId)
    1. 验证tokenId是否存在
    2. 如果操作者是token现主人 and 此操作者允许管理此代币 and 操作者允许管理此主人所有代币
  1. 调用 _transfer() 函数
    1. 验证from是否是token现主人 可能 有疑问 为什么刚刚验证了是否是主人/允许操作者, 却还要验证是否是主人. 是因为操作者和from不一定为同一人, 因此验证是必要的.
      _isApprovedOrOwner(_msgSender(), tokenId)
      _transfer(from, to, tokenId)
    2. 验证接收地址B是否为空
    3. 清空from的授权转账权限
    4. 更新from和to的资产种类
    5. 将token归属权交给to safeTransferFrom() 在这步额外检查to账户地址是否符合ERC-721规范, 否则回滚交易.
    6. 触发Transfer事件
  • mint()burn() 代币增发销毁逻辑中额外增加了将mapping中的tokenId创建与消除.

伴随NFT市场的火热, 社区在ERC-721的基础上增加了ERC-1155, ERC-8899等协议. 它们同样服务于非同质化代币, ERC-1155引入一个中央智能合约包的概念, 可以做到将不同代币打包交易. ERC-8899可以做到将NFT与FT打包交易. 这样的升级版提案大大便利了实际交易, 丰富了交易场景, 拓宽了NFT生态的能力圈.

4. 简要介绍ERC-721的实现


ERC-721与ERC-20的差别主要还是在于同质化与非同质化. ERC-20的Token可以无限细分, 而ERC-721的Token最小的单位为1, 无法再分割, 而且ERC-721内每一个Token完全不同, 并且每个Token对不同的用户都有不同的价值. 因此在底层实现与设计方面有所区别.

二. 实际开发NFT应用

后面格式乱了推荐到 ERC标准以及基于ERC-721的NFT应用开发 | Blogs 观看

在本节我们将基于ERC-721标准开发一个NFT应用, 应用采用React框架构建客户端页面, 智能合约使用Solidity进行编写, 实现功能为: 通过以太坊钱包发行多个NFT(独特HEX颜色的纯色图片).

项目的完整源码

什么是HEX颜色? 不同的HEX字符代表了不同的颜色, 比如#000000代表纯黑, #FF0000代表红色. 我们使用这样的方式来输入字符串, 生成独一无二的纯色图片作为NFT.

1. 安装依赖


首先确保环境中安装了Node.js, 我们将通过它来安装其他依赖.

在ganache官网(https://www.trufflesuite.com/ganache)安装与系统兼容的ganache.
  • ganache的作用是快速在本地网络搭建以太坊区块链, 方便之后的开发与测试.
// 在命令行输入以下内容, 通过npm下载安装truffle
$ npm install -g truffle
  • truffle是以太坊开发的必备工具, 帮助开发者可以快速编译测试智能合约.
  • npm是node附带的包管理器, -g代表了在全局环境安装truffle

如果npm报错, 请尝试 sudo npm install -g truffle, 并且输入密码.

// 通过git克隆客户端模版
$ git clone https://github.com/fewwwww/ERC-721-starter.git
  • 模版中包括: 前端框架React的基本模版以及存储智能合约的文件夹.
通过IDE打开文件夹, 打开IDE中的终端, 运行 
$ npm install $ npm i solc --save 来下载依赖.

注意, 下载生成的 node_modules 不需要上传到github或分享给协作者.

  • 下载完成后, 通过package.json文件中的scripts来运行客户端项目.
点击 package.json中的"start": "react-scripts start"旁按钮 或 使用终端:  $ react-scripts start

客户端会在 http://localhost:3000 运行. 在终端输入 control + c 可以终止运行.

React是一个开源的UI组件库, 通过它, 我们可以用更简单的JavaScript的语法操作页面中的元素, 并且它附带了一系列的工具能让我们实时预览客户端, 便利开发的流程.

  • 搭建本地区块链
点击 package.json中的"start": "react-scripts start"旁按钮 
或 
使用终端:  $ react-scripts start

2. 开发合约


如果你在本节合约编译期间遇到solidity版本冲突问题, 最好直接依照文中的老板本进行开发.
完全解决需要苛刻的网络环境, 或者参照此链接.

我们首先会进行合约的开发.

src/contracts中我们可以看到已经有一个Migrations.sol的合约文件, 它的作用是把我们的其他合约与truffle进行桥接.

  1. 由于我们的NFT是一个一个的颜色图片, 所以我们需要新建颜色图片NFT的智能合约.
    在contracts文件夹内新建一个Color.sol文件

我们将按照ERC-721标准进行开发, 这大大便利了我们的开发流程. 在这个基础上, 我们可以借助 OpenZeppelin 所实现的ERC-721标准进一步地做到快速开发.

  1. 在IDE的终端中输入:$ npm install @openzeppelin/contracts@2.3.0 --save

如果安装不成功, 可以尝试$ sudo npm install @openzeppelin/contracts

安装成功后可以在package.json中看到

"dependencies": {
  "@openzeppelin/contracts": "^2.3.0",
  ...
  1. 我们已经下载完成了OpenZeppelin, 所以在Color.sol内我们可以引入OpenZeppelin库中的代码, 同时让我们的合约继承引入的对象:
import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol";
 contract Color is ERC721Full {
} 
  1. Color智能合约中添加构造函数, 并把代币名称和代号传入给父合约:
contract Color is ERC721Full {
     constructor() ERC721Full('Color', 'COLOR'  public{} 
}
  1. 尝试编译合约:
$ truffle compile

编译成功会显示:

> Compiled successfully using:
   - solc: 0.5.16+commit.9c3226ce.Emscripten.clang
  1. 在部署之前, 我们需要在migrations文件夹中添加迁移文件. 这步的作用是让Color.sol被部署.
在/migrations中创建2_deploy_contracts.js, 内容为:
 const Color = artifacts.require("Color");
 module.exports = function(deployer) {
     deployer.deploy(Color); 
 };
  1. 迁移部署合约:
$ truffle migrate

注意部署时要在ganache中保持部署网络运行.

运行成功会显示:

Summary
=======
> Total deployments:   1
> Final cost:          0.00450474 ETH
  1. truffle控制台中查看部署情况:
truffle(development)> Color.deployed()

可以看到很多合约的信息. 为了后续开发的便利, 我们可以把这些信息保存到一个变量里.

由于Color.deployed()返回的是一个Promise对象, 所以我们可以使用以下语法保存:

color = await Color.deployed()
或
Color.deployed().then((result) => {color = result})

Truffle是用JavaScript语言编写的. Promise是JavaScript中的异步对象. 它有三种状态: pending, resolved, rejected.
比如在浏览器中输入http://baidu.com按下回车后, 进度条加载状态就相当于Promise的pending, 成功显示就相当于resolved状态, 网络不好没打开就相当于rejected状态. 而我们所需要的返回值/结果是http://baidu.com打开后的网页.
如果直接使用color = Color.deployed()进行赋值会让color成为一个处在pending状态的Promise对象, 而不是Promise对象所返回出的结果.

  1. console中查询合约信息:
 truffle(development)> color.name() 'Color'
 truffle(development)> color.address '0x87514286d09b0Fb4A8bb4133c53378F35670DE6e'
 truffle(development)> color.symbol() 'COLOR'
  1. 由于我们的应用功能为让用户创建16进制颜色图片的NFT, 所以我们需要在Color.sol合约中添加铸币方法:
    在开发合约的过程中, 最好同步编写测试. 本文篇幅有限因此跳过测试的内容.

    我们首先在合约中创建一个colors变量, 类型为由字符串组成的列表, 存储所有16进制的颜色字符.
    contract Color is ERC721Full { string[] public colors; ...
    铸币方法的流程为:
    1. 颜色字符串传入 mint
    2. 检测颜色是否唯一
    3. 生成颜色的id
    4. 调用ERC-721中的 _mint 方法铸币
    5. 确保此颜色在后续不被创建
  2. 为了做到第二步, 我们需要使用Solidity中的mapping数据结构来记录已铸造的颜色.
    mapping类似python中的dictionary
    contract Color is ERC721Full { string[] public colors; mapping(string => bool) _colorExists; ...
  3. 接下来我们按照上面的流程写mint方法:
    “` …… function mint(string memory _color) public {
    // 将新铸造的color加入colors列表, 返回数组的新长度作为id uint _id = colors.push(_color); // 调用ERC-721中的_mint方法, 铸币 // _mint(address to, uint256 tokenId) _mint(msg.sender, _id); //将color作为key, true作为value, 传入_colorExists _colorExists[_color] = true; }
    “`
  4. 至此我们就完成了合约的编写, 我们来将合约重新部署. 由于合约是无法修改的, 所以我们使用如下命令重新部署:
    注意要在ganache中打开部署网络

    $ truffle migrate --reset

至此我们简单的铸币功能合约就已经完成. 实际DApp开发过程中还有其他功能需要开发, 流程与此类似.

3. 开发Web客户端

我们所使用的React是一个JavaScript库, 它能让我们快速地开发网页应用.

  1. 首先我们进入/src/components/App.js中, 可以看到模版中自带了一些HTML标签和样式. 其中的class就是一个组件, 组成了页面的所有部分. 我们来运行客户端.
    点击 package.json中的"start": "react-scripts start"旁按钮 或 使用终端: $ react-scripts start
  2. 由于在JavaScript语言的特性中, class的对象设计是 不完美 的. 所以React支持用无状态Function组件配合Hooks来编写组件. 我们把App.js改成如下内容:
    “` import React from ‘react’; import ‘./App.css’;
    const App = () => { return (
    ERC-721 Color Edit src/components/App.js and save to reload.




    ); }
    export default App;
    “`
  3. 我们需要通过Web3来连接Web客户端与我们之前部署的区块链
  • 我们首先要把MetaMask客户端连接我们的本地区块链网络.

在MetaMask中连接我们的ganache端口
如果因为端口不同无法连接, 可以尝试将truffle-config.jsganache里的端口修改为匹配MetaMask的值.

  • ganache中导出账号到MetaMask点击ganache中任意一行最右侧的 图标, 复制私钥 在MataMask中点击头像, 选择导入账户, 粘贴私钥并进入
    注意ganache中的账户以及私钥仅用于开发测试.
  • App.js中加载Web3

我们首先引入Web3, 然后写一个异步加载Web3的函数. “` import Web3 from “web3”;
const App = () => { const loadWeb3 = async () => { if (window.ethereum) { window.web3 = new Web3(window.ethereum) await window.ethereum.enable() } else if (window.web3) { window.web3 = new Web3(window.web3.currentProvider) } else { window.alert(‘Non-Ethereum browser detected. You should consider trying MetaMask!’) } } “` – 我们需要调用加载Web3的方程
我们使用React中的Hooks, 在App.js组件第一次挂载时调用方程.
“` … useEffect(() => { loadWeb3() },[])
return ( …
“`
由于我们使用的是无状态Function组件, 所以需要Hooks这样的功能来时Function组件拥有生命周期和记忆.
请使用装有MetaMask的浏览器启动http://localhost:3000/, 如果是在Safari等没有MetaMask的浏览器内打开, 会弹出警告.

  1. 获取区块链内的数据并增加新数据

我们需要调用并获得类似/src/abis/Color.json内的区块链信息, 才能在客户端展示内容.

1. 我们需要设置一个状态变量来存储当前账户信息

    ```
    ...
    const App = () => {
      const [account, setAccount] = useState('')
    ...
    ```

   此处account的默认值是空字符串, setAccount是改变account值的唯一方法

   当状态变量比如account改变时, 会重新渲染.

2. 我们写一个方法拿到当前账户信息, 并且在组件第一次挂载时加载

    ```
    ...
    const loadBlockchainData = async () => {
      const web3 = window.web3
      const accounts = await web3.eth.getAccounts()
      setAccount(accounts[0])
    }

    useEffect(() => {
     loadWeb3()
     loadBlockchainData()
    },[])
    ...
    ```

   `loadBlockchainData()`可以异步加载当前所有账户, 并把状态中的当前账户设置为所有账户内的第一个. 在组件第一次挂载时会加载当前账户.

3. 我们需要在HTML中显示当前账户数据

    ```
    ...
       ERC-721 Color
       </a>
       <ul className="navbar-nav px-3">
         <li className="nav-item text-nowrap d-none d-sm-none d-sm-block">
           <small className="text-white"><span id="account">{account}</span></small>
         </li>
       </ul>
     </nav>
    ...
    ```

   在复杂的HTML与CSS中, `{account}`代表一个可变的之前状态中的`account`变量.

得到账号的全部流程如下: 页面组件挂载 => 加载Web3 => 通过Web3加载所有账号 => 将状态变量变为当前账号 => 重新渲染, 页面右上角为正确账号

  1. 通过类似的方法加入页面的其他布局(输入框和NFT显示区域)
    ... </nav> <div className="container-fluid mt-5"> <div className="row"> <main role="main" className="col-lg-12 d-flex text-center"> <div className="content mr-auto ml-auto"> <h1>铸造新NFT</h1> <form> <input type='text' className='form-control mb-1' placeholder='如#FFFFFF的HEX颜色' /> <input type='submit' className='btn btn-block btn-primary' value='提交' /> </form> </div> </main> </div> <hr/> <div className="row text-center"> 内容 </div> </div> ...
  • 我们需要拿到本地区块链网络ID, 从而进入正确地址来拿到ABI中的合约与数据, 来把所有NFT拿取并显示

以下中大多都是Web3的api, 所以原理不过多阐述, 具体内容可以通过看完整的项目源码来理解.
// 引入Color的abi import Color from '../abis/Color.json' ... // 状态变量 // 存储合约 const [contract, setContract] = useState(null) // 存储总量 const [totalSupply, setTotalSupply] = useState(0) // 存储所有颜色NFT const [colors, setColors] = useState([]) ... setAccount(accounts[0]) // 拿到网络ID const networkId = await web3.eth.net.getId() // 通过网络ID拿到网络中的数据 const networkData = Color.networks[networkId] // 如果网络中数据不为空 if(networkData) { // 拿到Color合约中的abi数据 const abi = Color.abi // 拿到合约地址 const address = networkData.address // 拿到合约内容并存入contract变量 const contract = new web3.eth.Contract(abi, address) // 将合约状态设置为新合约 setContract(contract) // 拿到合约中的NFT总量 const totalSupply = await contract.methods.totalSupply().call() // 将总量状态设置为新总量 setTotalSupply(totalSupply) // 暂时存储colors let tempColors = [] // 遍历总量-1次, 每次在colors状态中添加新的color for (let i = 1; i <= totalSupply; i++) { const color = await contract.methods.colors(i - 1).call() // 在新colors中加入拿到的color tempColors = [...tempColors, color] } // 最终将colors状态更新 setColors(tempColors) // 如果合约没被部署到这个网络 } else { // 弹出警告 window.alert('Smart contract not deployed to detected network.') } }
得到数据的流程如下: 通过Web3拿到网络ID => 如果Color.json中存在此网络 => 拿到Color.json中的abi数据, 拿到合约地址 => 拿到具体合约内容与数据 => 拿到NFT总量与数据

  • 在页面中渲染我们拿到的各个NFT
    ... </div> <hr/> {/* 通过列表形式渲染所有的color, 每个颜色是color中声明的颜色 */} <div className="row text-center"> { colors.map((color, key) => { return( <div key={key} className="col-md-3 mb-3"> <div className="token" style={{ backgroundColor: color }}></div> <div>{color}</div> </div> ) })} </div> ... 在App.css中添加样式, 让各个NFT更加美观 .token { height: 150px; width: 150px; border-radius: 50%; display: inline-block; }

在这个时候我们已经可以通过truffle console来铸币, 并且通过Web客户端显示颜色

- 完成我们的Web端输入并提交的功能

    1. 首先将HTML与样式更新

        ```
        <main role="main" className="col-lg-12 d-flex text-center">
          <div className="content mr-auto ml-auto">
            <h1>铸造新NFT</h1>
              <input
                type='text'
                className='form-control mb-1'
                placeholder='如#FFFFFF的HEX颜色'
                />
                <input
                  type='submit'
                  className='btn btn-block btn-primary'
                  value='提交'
                  />
                </div>
              </main>
          ```
    2. 给我们的文字输入框和提交按钮绑定上输入和点击事件

        - 我们需要再创建一个状态保存暂时用户输入的Hex颜色字符串
            ```
            const [tempColor, setTempColor] = useState('')
            ```
        - 当文字输入框内用户输入的文字变化时, 重新设置`tempColor`, 让输入框内的显示文字与`tempColor`状态绑定, 变化时自动重新渲染页面
            ```
            <input
              type='text'
              className='form-control mb-1'
              placeholder='如#FFFFFF的HEX颜色'
              value={tempColor}
              onChange={(event) =>
                setTempColor(event.target.value)}
            />
            ```
        - 提交按钮
            ```
             <input
               type='submit'
               className='btn btn-block btn-primary'
               value='提交'
               onClick={() =>
                {
               mint(tempColor)
                }}
               />
            ```
          在点击后, 会触发 **mint()** 方法
        - 完成 **mint()** 方法
          ```
          ...
            const mint = (color) => {
              contract.methods.mint(color).send({ from: account })
                .once('receipt', (receipt) => {
                  const newColors = [...colors, color]
                  setColors(newColors)
                })
              setTempColor('')
            }
          ...
          ```
          此时每当输入并提交新的HEX颜色后刷新, 可以看到新增了刚输入的NFT颜色
        - 通过修改调用loadBlockchainData()的时机, 省略刷新操作

          当colors状态变化后(mint触发后), 重新拉取区块链上的数据
            ```
            useEffect(() => {
              loadWeb3()
              loadBlockchainData()
            },[colors])
            ```

至此我们完成了Web客户端与区块链的开发, 尝试输入#000000或其他HEX颜色, 并在MetaMask中支付手续费, 可以看到页面上有新的NFT生成.

4. 总结

项目的完整源码

项目启动与使用方式: 1. 安装依赖, 开启ganache网络 2. 在项目文件中配置truffle.config.js 3. 使用truffle编译部署solidity智能合约 4. 运行React的Web客户端 5. 连接ganache中的虚拟账户到MetaMask 6. 在Web端输入HEX颜色, 生成新的NFT

项目开发流程: 1. 安装各种依赖, 开启ganache网络 2. 使用OpenZeppelin的ERC-721库开发Solidity智能合约 3. 通过truffle编译部署合约 4. 通过Web3让Web端与区块链进行通信 5. 完成前端页面的开发 6. ✅NFT系统应用开发:shucang360

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
NFT

<strong>非同质化代币NFT是什么?</strong>

2023-2-19 20:16:19

区块链推广

打破信任壁垒,区块链推动信任经济的崛起

2021-11-19 8:11:47

重要说明

本站资源大多来自网络,如有侵犯你的权益请联系管理员 区块链Bi站  或给邮箱发送邮件834379394@qq.com 我们会第一时间进行审核删除。 站内资源为网友个人学习或测试研究使用,未经原版权作者许可,禁止用于任何商业途径!请在下载24小时内删除!


如果你遇到支付完成,找不到下载链接,或者不能下载,或者解压失败,先不要忙,加客服主的QQ:834379394 (客服有可能有事情或者在睡觉不能及时的回复您,QQ留言后,请耐心等待即可!)

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索