如何用IPFS构建ERC721 NFT

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

使用Open Zeppelin、Truffle和Pinata。

ERC-721标准催生了以太坊上的非可替代代币(NFT)市场。 ERC-721是一个创建NFT(表示独一无二事物)的标准。 任何独特的事物都可以成为NFT。 一栋房子、一张棒球卡、一件艺术品等。 但其蕴含的能量不仅仅在于事物是独特的、数字化的,更在于其可验证性。 这就是ERC-721标准的闪光点。

创建ERC-721代币的主要问题来自于存储标的资产(译者注:NFT背后代表的事物,可能是一张图片或一段文字或视频)。 区块链并不适合存储大量数据。 2017年,Interplanetary数据库的Jamila Omar估计,在以太坊上存储1GB数据的成本将超过400万美元。

在以太坊上存储数据的成本约为17,500 ETH/GB,按今天的价格计算,约为4,672,500美元。 -- Jamila Omar,2017

既然存储NFT绑定的标的资产的成本太高,以致无法使用区块链让存储,那么有什么替代方案呢? 我们可以使用传统的云存储来存储标的资产,如亚马逊的S3和微软的Azure提供了廉价的存储解决方案。 然而,我们所熟知的传统云存储有一个很大的缺陷:他们不是密码学上可以验证的

可验证性

NFT的全部意义可能是对标的资产或数字资产可验证和可控制。

如果我们不能以类似于验证代表资产的代币所有权的方式来验证标的资产本身,我们就失去了最终目标。

解决这两个问题的方法是IPFS。 IPFS是一个分布式存储网络。 它的工作方式与云存储类似。 你请求内容,就会被返回该内容。 然而,最大的不同是,内容的存储利用了全球的存储提供者网络。 IPFS利用了一种叫做内容可寻址的工具。 这意味着,你不需要向俄亥俄州的数据中心提出请求,而是对内容本身提出请求。 它可能位于俄亥俄州(可能位置比较近)。 有了内容可寻址性,你不再需要依赖单一的位置来检索内容。 这对于全球区块链应用来说,分布式存储效率更高。

IPFS还为我们解决了可验证性问题。 因为所有的内容都是根据内容本身来定义和存储的,如果一个内容被篡改或改变,我们在试图验证内容时,就会出现不匹配的情况,知道内容是错误的。 我们用一个简单的例子来说明一下。

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

鲍勃请求那张猫的照片,然后给那只可怜的猫画上胡子。 当Bob上传他的图片时,他将不再拥有相同的标识符。 因为他改变了底层数据(从猫到胡子猫),所以Bob的标识符可能是 M

如果鲍勃想把他的照片冒充爱丽丝的照片任何人都会知道他在撒谎。 爱丽丝的标识符与鲍勃的标识符不一致,因此,鲍勃试图冒充爱丽丝的图像是可以验证的假象。

这有一个是视频介绍:https://youtu.be/6b8OANmw2kM

我想你可以看到,用 IPFS 来验证NFTs等数字资产,将是很方便的。 就是考虑到这一点,让我们看看如何在IPFS上创建一个NFT并存储相关的标的资产。

入门

对于本教程,你需要准备:

编写智能合约

在这里先免责声明一下,作者不是一个专业的智能合约开发者。 我知道足够危险,在区块链世界里,危险可能等于赔钱。 所以要小心,要做研究,一定要找到最佳实践。 本指南的目的是作为一个入门引导,可能有更好的方法来完成我在这里向你展示的内容。

ERC-721代币由智能合约管理,幸运的是,OpenZeppelin让你写好合约变得容易。 我们将使用OpenZeppelin的ERC-721合约来帮助我们开始。

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

mkdir mySpecialAsset && cd mySpecialAsset

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

npm init -y

现在,可以利用Truffle来初始化智能合约。

truffle init

现在,我们需要获得OpenZeppelin合约。 为此,像这样安装Open Zeppelin的solidity库:

npm install @openzeppelin/contracts

我们需要为NFT代币想一个好名字。 我打算把它的叫做UniqueAsset,因为我们要确保NFTs必须与独特的标的资产相关联。 你可以把你的合约称为任何你喜欢的东西:

touch contracts/UniqueAsset.sol

接着就可以开始编码:

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.js文件,确保Truffle使用正确的Solidity版本编译你的合约。

还需要从Open Zeppelin导入合约ERC721 及 Counters ,Counters用来帮助我们创建递增id的代币。

最后,在我们合约的构造函数中,我们定义了代币名称和符号。

我们需要在合约中加入一些逻辑:

首先,让我们想一想我们要做什么。 我们要针对特定的资产发行NFTs。 我们希望这些资产可以核查,就像我们希望所有权可以核查一样。 所以,这里有几件事需要考虑。

  1. 我们要将NFT与IPFS内容标识符(哈希)关联起来。
  2. 我们永远不希望铸造(或创建)一个与另一个NFT映射到相同IPFS哈希的NFT。

让我们先在合约中定义变量,我们将用这些变量来帮助控制以上两点。 在合约代码的构造函数上面,添加以下内容:

using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
mapping(string => uint8) hashes;

constructor() public ERC721("UniqueAsset", "UNA") {}
...

使用计数器来定义一个_tokenIds变量,以跟踪发行的所有代币。 最后,创建一个映射来关联IPFS哈希与代币。 这将有助于防止发行相同哈希值的代币。

你的合约现在应该是这样的:

pragma solidity ^0.6.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract UniqueAsset is ERC721 {
  using Counters for Counters.Counter;
  Counters.Counter private _tokenIds;
  mapping(string => uint8) hashes;

  constructor() public ERC721("UniqueAsset", "UNA") {}
}

我们需要在合约中添加一个方法,允许为特定的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;

}

这里面有很多内容,让我们一行一行的过。 awardItem函数需要三个参数。 一个名为 recipient的地址变量,一个名为 hash的字符串变量,一个名为 metadata的字符串变量。 地址变量recipient是将收到NFT的人的钱包地址。 hash的字符串变量是与正在创建NFT的内容相关联的IPFS哈希。 而 metadata的字符串变量是指向资产的JSON元数据的链接。 元数据可能包括资产名称、指向该资产的图片链接或其他任何你想要的内容。

然后,在定义了函数之后,要把它变成public。 这只是意味着它可以从智能合约外部调用,另外我们还定义函数的返回值为uint256类型。

在函数里面,使用Solidity内置的 require来自动拒绝合约的调用,如果哈希之前已经被用来铸造NFT。 检查hashes映射是否有匹配的整数为1的哈希值,如果有,那么这个哈希值已经被使用了。

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

最后,我们递增_tokenIds变量,并铸造代币,返回代币标识符。

快速总结一下,合约现在需要一个以太坊钱包地址和一个IPFS哈希。 它会检查以确保哈希值没铸造过NFT。 如果一切正常,就会创建一个新的NFT,来对应该IPFS哈希。

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

让我们编译并部署它,现在要用之前安装的Ganache。 通过 ganache-cli或使用桌面客户端启动Ganache。

在项目目录下,有一个 migrations的文件夹。 需要创建一个新的迁移文件来部署UniqueAsset, 新迁移文件为2-deploy-contract.js。 在该文件中,添加以下内容:

var UniqueAsset = artifacts.require("UniqueAsset");

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

完成并保存后,在终端中,在项目目录下,运行。

truffle compile

假设没有碰到任何错误,你的合约已经编译完成,现在可以部署了。 简单的运行:

truffle migrate

如果出现错误,你可能需要手动设置Ganache运行的端口,和你的 truffle-config.js文件中 networks部分设置的开发网络端口一致。

如果一切顺利,你就部署好了UniqueAsset合约。再次提醒你,我不是智能合约开发专家。

现在我们已经处理好了智能合约,我们需要把标的资产放到IPFS上,并确保在铸造与之相关的NFT时,IPFS是可用的。

在IPFS中添加资产

我们将使用Pinata将资产添加到IPFS中,并确保它保持被pin。 我们还将把JSON元数据添加到IPFS中,这样我们就可以把它传递给NFT代币合约。 所以,登录你之前在Pinata上创建的账户。 在右上方,点击账户下拉菜单,选择账户。 在那里你将能够看到API key, 你可以悬停查看 API secret。

复制API key及API secret,因为我们将在代码中使用它们来上传我们的资产文件。

一旦你有了你的API Key和Secret Key,就可以写一些代码来发布你的资产文件到IPFS。 如果你在做完智能合约工作后感觉很累,不要担心,因为这里超级简单的。 其实,如果你想完全跳过代码,Pinata有一个方便的上传功能的 UI

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

1_3YXhi9TQuGVHMFpa-GJm-w

现在我们已经准备好将要上传的标的资产,让我们来编写代码。 我们需要两个依赖关系来使我们更容易做到这一点。 在你的终端中,在项目的根目录,运行以下内容:

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

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

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

这个哈希值就是你的资产的可验证的表示,它指向你在IPFS网络上的资产。 如果有人篡改了你的资产,改变了你的资产,哈希值就会不同。 在通过我们的智能合约铸造NFTs时,应该使用这个哈希值。 任何提供公共网关的IPFS主机都可以为你显示资产内容。

Pinata有一个网关,你可以在这里查看我刚才上传的资产。

最后,我们需要做的是创建一个JSON文件,代表我们的资产及其元数据。 这使得你可能想要列出你的资产的任何服务更容易显示适当的元数据。 让我们创建一个简单的JSON文件,像这样。

{
  "name":"My Kid's Art",
  "hash": "QmfAvnM89JrqvdhLymbU5sXoAukEJygSLk9cJMBPTyrmxo", 
  "by": "Justin Huner"
}

你可以添加任何你喜欢的元数据,但重要的是要包含哈希值。 这是对实际资产的参考。 现在,用使用Pinata上传资产文件的方式上传这个JSON文件。 当你拿回元数据的IPFS哈希值时,要把它保存起来。 在创建NFT代币时需要这个。

还记得,智能合约采取的是元数据字符串吗? 这个字符串将是元数据的IPFS URL。 你要这样构造:

ipfs://YOUR_METADATA_HASH

因此,总结一下,你将向我们之前创建的智能合约函数中传递三个项目。

  • 收件人地址
  • 资产哈希
  • 元数据URL

合并起来

NFT是我们处理各类商品所有权的重要改进。 它们很容易转让,并简化了建立所有权和证明所有权的过程。 不过,缺失的一环,一直是对具体标的物所有权的验证。

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

Pinata有助于简化这一过程,使IPFS上的资产存储变得简单。


本翻译由 Cell Network 赞助支持。

点赞 3
收藏 6
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
翻译小组
翻译小组
0x9e64...7c84
大家看到好的文章可以在 GitHub 提 Issue: https://github.com/lbc-team/Pioneer/issues 欢迎关注我的 Twitter: https://twitter.com/UpchainDAO