选择页面

如何使用IPFS构建ERC-721 NFTs

原作者:Justin Hunter

翻译:Wen.Q

ERC-721标准催生了以太坊上的不可替代代币(NFT)市场。ERC-721是一个用于创建nft的标准,nft是一种复杂的表示唯一的方式。任何独特的东西都可以成为NFT。一幢房子、一张棒球卡、一件艺术品等。但它的强大之处并不在于它的独一无二与数字化,魅力在于其可验证性。这就是ERC-721标准的亮点所在。

创建ERC-721令牌的问题来自于存储源资产。区块链并不适合存储大量数据。2017年,星际数据库(Interplanetary Database)的贾米拉·奥马尔(Jamila Omar)估计,在以太坊存储1GB数据的成本将超过400万美元。(一般来说,以太坊存储数据的成本约为17500 ETH/GB。) 

如果我们知道存储与NFT关联的资产的成本太高,不能使用区块链,那么还有什么替代方案呢?我们可以使用传统的云存储来存储资产。亚马逊的S3和微软的Azure提供廉价的存储解决方案。然而,我们所知道的传统的云存储有一个重大的缺陷 —— 不能使用加密验证。 

Verifiability (可验证性)

NFT的全部意义在于对实体或数字资产进行数字验证和控制。如果我们不能以类似于验证代表资产的代币所有权的方式来验证基础资产本身,我们就会失去最终目标。

这两个问题的解决方案是 IPFS。IPFS是分布式存储网络,它的工作方式与云存储类似。都是在网络中请求内容并获取内容结果。然而,IPFS最大的区别在于内容是通过使用全球存储提供商网络来存储的。IPFS利用了一种称为内容寻址能力的工具。这意味着您不需要向俄亥俄州的数据中心网址请求某段内容,而是依据内容本身发出请求。内容可能位于俄亥俄州,也可能位于更近的地方。有了内容可寻址性,您就不再需要依赖于单个服务器地址来检索内容。这对全球区块链来说要高效得多。 

IPFS还为我们负责安全验证性。因为所有内容地址都是基于内容本身定义和存储的,所以如果某一段内容被篡改或更改,我们在试图验证内容正确性的时候就会出现不匹配。让我们通过一个简单的例子来更清楚地说明这一点: 

Alice在IPFS上存储了一张猫的图片,该猫图片由内容标识符表示。为简单起见,我们假设标识符是“C”。

Bob找到这张猫图片,然后在那只可怜的猫上画了胡子。当Bob上传他的图片时,他将不再拥有相同的标识符。因为他已经更改了底层数据(从cat更改为mustache cat),所以Bob的标识符可能是“M”。

如果bob想用改过的图片来冒充alice的,任何人都知道他在欺骗。因为alice的标识符与bob的不匹配,bob试图冒充alice作品是可被验证的假作。

我认为在尝试验证 NFT 等数字资产时,您可以开始了解这如何派上用场。下面让我们看看如何创建NFT并在IPFS上存储相关资产。  

开始

提前需要准备的:

  • 一个好的文本编辑器
  • IPFS 安装
  • Ganache -以太坊本地区块链安装
  • Truffle 安装
  • NodeJS 安装
  • Pinata API Key

你可以选择任何你想要的文本编辑器。我个人更喜欢Visual Studio Code。

确定了编辑器之后,让我们确保安装IPFS。直接按照这里IPFS的说明操作(directly from IPFS here)。 

你能够从完整Truffle套件中同时获得 Truffle 和Ganache

这里是下载安装NodeJS的网址: most recent version here。 

最后,您希望确保为其创建NFT的宝贵资产永久存储在IPFS上。这可以通过运行您自己的IPFS节点或使用所谓的IPFS固定服务来实现。为了简单起见,我们将通过Pinata pin服务复制它。在这里注册一个账户。(1. running your own IPFS node;2.  IPFS Pinning Service;3. Pinata Sign up) 

编写智能合约

在这里做一个简短的免责声明。我不是专业的智能合约开发者。但我知道这里面的风险,而在区块链世界,危险就等于失去金钱。所以,一定要小心,做好研究,并找到最佳实现。本指南旨在作为一个教育起点,可能有更好的方法来完成我在这里向你展示的内容。

ERC-721协议由智能合约管理。幸运的是,OpenZeppelin能确保你轻松的写好合约。我们将使用他们的ERC-721合同来帮助我们开始。 

首先,在终端中创建一个新的项目文件夹。 

mkdir mySpecialAsset && cd mySpecialAsset 

接下来,我们将使用NPM初始化我们的项目目录。 

npm init –y 

现在,我们可以利用Truffle来初始化我们的智能合约项目。

truffle init

然后,我们需要使用这些可爱的OpenZeppelin合约,要做到这点,就必要安装OpenZeppenlin的Solidity库。

npm install @openzeppelin/contracts 

我们得给我们的项目起个好名字。假如我将我的称为UniqueAsset,因为我们要确保nft必须与唯一的基础资产相关联。你想怎么称呼你的合同都行。我们想要将合约放在一个合约文件夹中,所以我们将像这样创建我们的文件: 

touch contracts/UniqueAsset.sol 

我们现在可以打开该文件并开始工作。我们需要指定与我们的合约兼容的版本(或稳定性的版本)。我们还需要从Open Zeppelin导入合同框架。接着就可以开始执行合约本身了。让我们一起来看看: 

pragma solidity ^0.6.0;

import “@openzeppelin/contracts/token/ERC721/ERC721.sol”;
import “@openzeppelin/contracts/utils/Counters.sol” 

contract UniqueAsset is ERC721{
  constructor() public ERC721(“UniqueAsset”, “UNA”) {}
}  

我们需要指定我们使用的Solidity版本。在本例中,我使用的是0.6.0。你可以通过编辑Truffle -config.jsfile来确保Truffle使用正确的Solidity版本来编译你的合约。我从Open Zeppelin导入了两个合同:ERC721和Counters。Counter计数器只是帮助我们在发布令牌标识后增加令牌标识。最后,在契约的构造函数中,定义了标记名称和符号。同样,你可以设置它为你想要的任何值。

我们需要在合约中加入一些逻辑,现在就开始吧。 

首先,我们想一下这里要做什么。我们想为特定资产发行nft。我们希望这些资产是可验证的就像我们希望所有权是可验证的一样。我们需要考虑以下几点:

  1. 我们希望将NFT与IPFS内容标识符(hash散列)关联。
  2. 我们不希望创建(或创建)一个NFT,它与另一个NFT映射到相同的IPFS散列(hash)。 

让我们从定义合约中的变量开始,我们将使用这些变量来帮助控制上述两点。在上面的合约代码中,添加以下内容:

 Using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
mapping(string => uint8) hashes; constructor() public ERC721(“UniqueAsset”, “UNA”) {}
 

我们使用计数器来帮助我们增加我们创造的令牌的标识符。我们还创建了一个_tokenIds变量来跟踪我们发出的所有令牌。最后,对于顶级变量,我们将为与令牌相关的IPFS散列创建一个映射。这将有助于防止发出映射到以前与另一个令牌关联的散列的令牌。 

我们需要在合约中添加一个方法,如果还没有为特定IPFS哈希创建令牌,该方法将允许我们为该哈希创建NFT。我们将在构造函数下面这样做。

 function awardItem(address recipient, string memory hash, string memory metadata)
  public
  returns (uint256)require(hashes[hash] != 1);  hashes[hash] = 1;  _tokenIds.increment();  uint256 newItemId = _tokenIds.current();  _mint(recipient, newItemId);  _setTokenURI(newItemId, metadata);  return newItemId;
}

让我们逐行浏览一下。我们的函数接受两个参数:名为 recipient的地址变量、名为hash的字符串变量和名为metadata的字符串变量。地址变量是接收NFT的人的钱包地址。hash的字符串变量是与我们正在为其创建NFT的内容相关联的IPFS散列。metadata的字符串变量应该指向资产的JSON元数据的链接。元数据可能包括资产名称、指向引用该资产的图像的链接,或者您想要的任何其他内容。

然后,在定义函数之后,我们将其公开。这只是意味着可以从智能合约的外部调用它。我们还将函数的返回值定义为uint256类型。 

在函数内部,我们使用Solidity内置命令require来自动拒绝合约的调用,如果其hash之前已被用于另一个NFT。我们通过检查hash映射是否有一个整数为1的值。如果是,则该hash已被使用。

如果没有被使用,我们将通过函数传递的哈希添加到我们的哈希映射并设置为1。 

最后,我们增加_tokenIds变量,因为我们将创建一个新的NFT并铸造相关联的Token,然后返回令牌标识符。 

信息量很大,让我们在不依赖代码的情况下快速总结一下。我们的合约现在接受一个人的以太坊钱包地址和一个IPFS哈希。它检查以确保哈希不匹配先前创建的NFT。如果一切正常,将创建一个特定于该IPFS哈希的新NFT。 

好的,我们已经写好了合同。现在怎么办?

让我们编译并部署它。记得我让你安装的Ganathe,我们现在就用它。通过Ganache -cli或使用桌面客户端启动Ganache(我更喜欢使用桌面客户端)。 

如果您查看项目目录,您将看到一个名为migrations的文件夹。我们需要创建一个新的迁移文件。这些文件非常特殊,在本例中,我们需要它在默认为您创建的现有迁移文件之后运行。为了确保这一点,我们将在迁移文件的开头用2命名。继续并调用新的迁移2-deploy-contract.js。在该文件中,添加一下内容: 

var UniqueAsset = artifacts.require(“UniqueAsset”);

module.exports = function(deployer) {
  deployer.deploy(UniqueAsset);
}; 

完成并保存后,在你的终端项目目录下运行: 

truffle compile

假设您没有碰到任何错误,那么您的合约已经被编译,现在可以部署了。开始运行:

truffle migrate

如果出现错误,可能需要手动设置Ganache正在运行的端口,桌面客户端里很容易找到。在truffle-config.js文件中,您可以找到newworks部分并设置合适的开发端口。

如果一切顺利,你已部署了NFT智能合约。这里再次提醒各位一下,我不是一个智能合约开发专家,肯定会有错过一些更好的实现。如果你正在创建智能合约,你应该自己多研究一下。

你可以通过在Truffle中编写测试来测试你的合同,或者你可以转到Remix,在那里复制你的合同并运行测试。如果进行测试,您会注意到可以生成一个与IPFS哈希关联的NFT,但是如果试图为同一个哈希生成另一个NFT,对合约的调用将会失败。 

现在我们已经解决了智能合约,我们需要将我们的基础资产放到IPFS中,并确保当需要铸造与之相关的NFT时它是可用的。 

将资产添加到 IPFS

我们将使用Pinata将我们的资产添加到IPFS,并确保它保持固定。我们还会向IPFS添加我们的JSON元数据,以便我们可以将其传递到我们的令牌合约。登录您之前在 Pinata 上创建的帐户。在右上角,单击帐户下拉菜单并选择帐户。在那里您将能够看到您的 API ,您可以悬停鼠标以查看您的 API 密钥。 

复制这两个,因为我们将在代码中使用它们来上传我们的资产文件。 

单击New API Key并进行选择。对我来说,我想我只希望这个密钥被使用一次,我只希望它能够访问 pinFileToIPFS 端点,因为这就是我们将资产文件推送到 IPFS 的方式。 

拥有 API Key Secret Key 后,您可以编写一些代码将资产文件添加到 IPFS。如果您在完成所有智能合约工作后感到疲倦,请不要担心,因为这将非常简单。事实上,如果你想完全跳过代码,Pinata UI 中有一个方便的上传功能(convenient upload feature) 

在代码编辑器中,创建一个名为uploadFile.js的新文件。这可以在您创建智能合约的同一个目录中。在我们编写代码之前,最好先准备好你的资产文件。只要确保它保存在你正在使用的电脑上的某个地方。对我来说,我要上传一幅我儿子画的画。

现在我们已经保存了资产并准备上载,让我们来编写代码。我们需要两个依赖项来简化这个过程。在您的终端中,在项目的根目录下,运行以下命令:

npm i axios form-data

现在,在uploadFile.js 文件中,添加以下内容: 

const pinataApiKey = “YOURAPIKEY”;
const pinataSecretApiKey = “YOURSECRETKEY”;
const axios = require(“axios”);
const fs = require(“fs”);
const FormData = require(“form-data”);const pinFileToIPFS = async () => {  const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;let data = new FormData();  data.append(“file”, fs.createReadStream(“./pathtoyourfile.png”));  const res = await axios.post(url, data, {
    maxContentLength: “Infinity”,
    headers: {
      “Content-Type”: `multipart/form-data; boundary=${data._boundary}`
      pinata_api_key: pinataApiKey,
      pinata_secret_api_key: pinataSecretApiKey,
    },
  });   console.log(res.data);
}; pinFileToIPFS(); 

要运行这个文件中的脚本,只需在终端中执行以下命令: 

node uploadFile.js 

成功上传后,你会得到像这样的结果: 

{
IpfsHash: ‘QmfAvnM89JrqvdhLymbU5sXoAukEJygSLk9cJMBPTyrmxo’,
PinSize: 2936977,
Timestamp: ‘2020-12-03T21:07:13.876Z’

该散列是您资产的可验证表示。它指向您在 IPFS 网络上的资产。如果有人篡改了您的资产并对其进行了更改,则哈希值会有所不同。在通过我们的智能合约铸造 NFT 时应该使用该哈希值。任何提供公共网关的 IPFS 主机现在都可以为您显示内容。 

Pinata有一个网关,你可以在这里查看我刚刚上传的资产( view the asset I just uploaded here)。

我们需要做的最后一件事是创建一个表示资产及其元数据的JSON文件。这使得您可能希望列出资产以显示适当元数据的任何服务都更容易。让我们创建一个简单的JSON文件,如下: 

{
“name”:”My Kid’s Art”,
“hash”: “QmfAvnM89JrqvdhLymbU5sXoAukEJygSLk9cJMBPTyrmxo”,
“by”: “Justin Huner”

您可以添加任何您想要的元数据,重要的是要包含散列。这是对实际资产的引用。现在,按照使用Pinata上传资产文件的方式上传此文件。当您获得元数据的IPFS散列时,请放在手边,您在创建令牌时会需要它。 

还记得智能合约接受元数据字符串吗?该字符串将成为元数据的 IPFS URL。你会像这样构造它:

ipfs://YOUR_METADATA_HASH 

总而言之,您将向我们之前创建的智能合约函数传递三个项目: 

  • Recipient Address
  • Asset Hash
  • Metadata URL

综述

NFTs是我们处理各类货物所有权的一项重要改进。它们易于转让,简化了创建所有权和证明所有权的过程。但是,缺少的部分是验证特定项目的所有权。 

通过将资产保存到 IPFS 并将 IPFS 哈希与资产的 NFT 相关联,我们可以将资产的可验证所有权扩展到资产有效性本身的验证。

Pinata 通过简化 IPFS 上的资产存储来帮助简化这一过程。

欢迎大家尝试!