使用 Open Zeppelin、Truffle 和 Pinata。
ERC-721 标准[4]催生了以太坊上的非可替代代币(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[5]
既然存储 NFT 绑定的标的资产的成本太高,以致无法使用区块链让存储,那么有什么替代方案呢?我们可以使用传统的云存储来存储标的资产,如亚马逊的 S3 和微软的 Azure 提供了廉价的存储解决方案。然而,我们所熟知的传统云存储有一个很大的缺陷:他们不是密码学上可以验证的。
NFT 的全部意义可能是对标的资产或数字资产可验证和可控制。
如果我们不能以类似于验证代表资产的代币所有权的方式来验证标的资产本身,我们就失去了最终目标。
解决这两个问题的方法是IPFS[6]。IPFS 是一个分布式存储网络。它的工作方式与云存储类似。你请求内容,就会被返回该内容。然而,最大的不同是,内容的存储利用了全球的存储提供者网络。IPFS 利用了一种叫做内容可寻址的工具。这意味着,你不需要向俄亥俄州的数据中心提出请求,而是对内容本身提出请求。它可能位于俄亥俄州(可能位置比较近)。有了内容可寻址性,你不再需要依赖单一的位置来检索内容。这对于全球区块链应用来说,分布式存储效率更高。
IPFS 还为我们解决了可验证性问题。因为所有的内容都是根据内容本身来定义和存储的,如果一个内容被篡改或改变,我们在试图验证内容时,就会出现不匹配的情况,知道内容是错误的。我们用一个简单的例子来说明一下。
Alice 在 IPFS 上存储了一张猫的图片,该猫的图片由一个内容标识符来表示。为简单起见,我们假设标识符为 C
。
鲍勃请求那张猫的照片,然后给那只可怜的猫画上胡子。当 Bob 上传他的图片时,他将不再拥有相同的标识符。因为他改变了底层数据(从猫到胡子猫),所以 Bob 的标识符可能是 M
。
如果鲍勃想把他的照片冒充爱丽丝的照片任何人都会知道他在撒谎。爱丽丝的标识符与鲍勃的标识符不一致,因此,鲍勃试图冒充爱丽丝的图像是可以验证的假象。
这有一个是视频介绍:https://youtu.be/6b8OANmw2kM
我想你可以看到,用 IPFS 来验证 NFTs 等数字资产,将是很方便的。就是考虑到这一点,让我们看看如何在 IPFS 上创建一个 NFT 并存储相关的标的资产。
对于本教程,你需要准备:
在这里先免责声明一下,作者不是一个专业的智能合约开发者。我知道足够危险,在区块链世界里,危险可能等于赔钱。所以要小心,要做研究,一定要找到最佳实践。本指南的目的是作为一个入门引导,可能有更好的方法来完成我在这里向你展示的内容。
ERC-721 代币由智能合约管理,幸运的是,OpenZeppelin[14]让你写好合约变得容易。我们将使用 OpenZeppelin 的 ERC-721 合约来帮助我们开始。
首先,在终端,创建一个新的项目文件夹:
mkdir mySpecialAsset && cd mySpecialAsset
接下来,我们将使用 NPM 初始化项目目录。
npm init -y
现在,可以利用 Truffle 来初始化智能合约。
truffle init
现在,我们需要获得OpenZeppelin[15]合约。为此,像这样安装 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。我们希望这些资产可以核查,就像我们希望所有权可以核查一样。所以,这里有几件事需要考虑。
让我们先在合约中定义变量,我们将用这些变量来帮助控制以上两点。在合约代码的构造函数上面,添加以下内容:
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 是可用的。
我们将使用 Pinata 将资产添加到 IPFS 中,并确保它保持被 pin。我们还将把 JSON 元数据添加到 IPFS 中,这样我们就可以把它传递给 NFT 代币合约。所以,登录你之前在 Pinata 上创建的账户。在右上方,点击账户下拉菜单,选择账户。在那里你将能够看到 API key, 你可以悬停查看 API secret。
复制 API key 及 API secret,因为我们将在代码中使用它们来上传我们的资产文件。
一旦你有了你的 API Key 和 Secret Key,就可以写一些代码来发布你的资产文件到 IPFS。如果你在做完智能合约工作后感觉很累,不要担心,因为这里超级简单的。其实,如果你想完全跳过代码,Pinata有一个方便的上传功能的 UI[16]。
在你的代码编辑器中,创建一个名为 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();
上传成功后,你会得到这样的结果。
{
IpfsHash: 'QmfAvnM89JrqvdhLymbU5sXoAukEJygSLk9cJMBPTyrmxo',
PinSize: 2936977,
Timestamp: '2020-12-03T21:07:13.876Z'
}
这个哈希值就是你的资产的可验证的表示,它指向你在 IPFS 网络上的资产。如果有人篡改了你的资产,改变了你的资产,哈希值就会不同。在通过我们的智能合约铸造 NFTs 时,应该使用这个哈希值。任何提供公共网关的 IPFS 主机都可以为你显示资产内容。
Pinata 有一个网关,你可以在这里[17]查看我刚才上传的资产。
最后,我们需要做的是创建一个 JSON 文件,代表我们的资产及其元数据。这使得你可能想要列出你的资产的任何服务更容易显示适当的元数据。让我们创建一个简单的 JSON 文件,像这样。
{
"name":"My Kid's Art",
"hash": "QmfAvnM89JrqvdhLymbU5sXoAukEJygSLk9cJMBPTyrmxo",
"by": "Justin Huner"
}
你可以添加任何你喜欢的元数据,但重要的是要包含哈希值。这是对实际资产的参考。现在,用使用 Pinata 上传资产文件的方式上传这个 JSON 文件。当你拿回元数据的 IPFS 哈希值时,要把它保存起来。在创建 NFT 代币时需要这个。
还记得,智能合约采取的是元数据字符串吗?这个字符串将是元数据的 IPFS URL。你要这样构造:
ipfs://YOUR_METADATA_HASH
因此,总结一下,你将向我们之前创建的智能合约函数中传递三个项目。
NFT 是我们处理各类商品所有权的重要改进。它们很容易转让,并简化了建立所有权和证明所有权的过程。不过,缺失的一环,一直是对具体标的物所有权的验证。
通过将资产保存到 IPFS,并将 IPFS 哈希值与资产的 NFT 关联起来,我们可以将资产的可验证所有权扩展到验证标的资产本身的有效性。
Pinata[18]有助于简化这一过程,使 IPFS 上的资产存储变得简单。
本翻译由 Cell Network[19] 赞助支持。
来源:https://medium.com/pinata/how-to-build-erc-721-nfts-with-ipfs-e76a21d8f914
[1]
登链翻译计划: https://github.com/lbc-team/Pioneer
[2]
翻译小组: https://learnblockchain.cn/people/412
[3]
Tiny 熊: https://learnblockchain.cn/people/15
[4]
ERC-721标准: https://eips.ethereum.org/EIPS/eip-721
[5]
Jamila Omar,2017: https://medium.com/ipdb-blog/forever-isnt-free-the-cost-of-storage-on-a-blockchain-database-59003f63e01
[6]
IPFS: https://ipfs.io/
[7]
安装说明: https://docs.ipfs.io/how-to/command-line-quick-start/
[8]
安装: https://www.trufflesuite.com/ganache
[9]
安装: https://learnblockchain.cn/docs/truffle/getting-started/installation.html
[10]
安装: https://nodejs.org/en/
[11]
运行自己的IPFS节点: https://medium.com/pinata/how-to-deploy-an-ipfs-node-on-digital-ocean-c59b9e83098e
[12]
Pinning服务: https://medium.com/pinata/what-is-an-ipfs-pinning-service-f6ed4cd7e475
[13]
在此注册账户: https://pinata.cloud/
[14]
OpenZeppelin: https://openzeppelin.com/
[15]
OpenZeppelin: https://openzeppelin.com/
[16]
有一个方便的上传功能的 UI: https://pinata.cloud/pinataupload
[17]
在这里: https://gateway.pinata.cloud/ipfs/QmfAvnM89JrqvdhLymbU5sXoAukEJygSLk9cJMBPTyrmxo/
[18]
Pinata: https://pinata.cloud/
[19]
Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain