添加元数据到智能合约中

ERC721 标准规范 定义了一个名为 ERC721Metadata 的扩展,这个扩展定义了一个函数 接收一个参数并返回一个原数据的 token 链接 这里就不详细介绍 元数据 了 如果需要了解 可以查看 OpenSea文档

我们需要扩展一下之前的 智能合约(NFTContract.sol)文件 添加一个 baseURL 并且定义一下 settergetter 以供 OpenZeppelin 调用

完整代码如下

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.1;

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

contract NFTContract is ERC721 {
  using Counters for Counters.Counter;

  Counters.Counter private currentTokenId;

  /// @dev Base token URI used as a prefix by tokenURI().
  string public baseTokenURI;

  constructor() ERC721("NFTContract", "NFT") {
    baseTokenURI = "";
  }

  function mintTo(address recipient) public returns (uint256) {
    currentTokenId.increment();
    uint256 newItemId = currentTokenId.current();
    _safeMint(recipient, newItemId);
    return newItemId;
  }

  /// @dev Returns an URI for a given token ID
  function _baseURI() internal view virtual override returns (string memory) {
    return baseTokenURI;
  }

  /// @dev Sets the base token URI prefix.
  function setBaseTokenURI(string memory _baseTokenURI) public {
    baseTokenURI = _baseTokenURI;
  }
}

添加完之后我们所铸造(mint)的 NFT 会自动调用 tokenURI() 并返回 baseTokenURI + tokenId

当然你也可以直接硬编码在 _baseURI 函数中 return 写死 baseTokenURI 地址 这样就不需要调用 setBaseTokenURI 函数 可以省下一笔 Gas fee 不过这样也有弊端 不够灵活

NFT 元数据(metadata)其实你可以存储在任何地方 自己搭建的服务器 国外的亚马逊云 国内的腾讯云 阿里云 七牛云等等 都可以 不过这种方式和 去中心化 的理念不符 所以不太推荐

去中心化的解决方案:

免费的可以使用 Protocol LabNFT Storage

收费的可以使用 Arweave 或者是 Pinata

看你个人选择了 这里我们使用的是 Protocol LabNFT Storage

打开 NFT Storage 注册并登录后 下载 NFT UP 上传工具 下载地址 https://github.com/nftstorage/nftup/releases

登录注册推荐使用 Github 登录 我使用邮箱注册没有成功 不太清楚是什么原因 可以自己尝试下邮箱注册

下载好 NFT UP 上传工具后打开 将要发行的所有 NFT 的图片文件全部拖到工具里 进行上传 上传时需要输入一个 API Key 这个自己在 NFT Storage 里创建即可

文件名建议使用数字方便后面和 json 文件对应 用做生成对应的 tokenID 看个人习惯

上传成功后 工具会显示上传的信息 打开 https://nft.storage/files/ 即可查看上传的文件 点击 CID 可以进入查看文件列表

也可以直接使用工具返回的 Gateway URL 信息 作为前缀 后面拼接上 / 文件名就是图片地址 (记得带后缀)

接下来开始创建 包含 IPFS 图像的元数据 json 文件 用来描述 文件名对应上传的图片文件名 .json 后缀可以不用添加 例如 1 2 3

内容大概有这些信息 其他信息请参考 OpenSea文档

{
  "name": "NFT名称",
  "description": "描述",
  "image": "图片地址(IPFS文件列表中复制)",
  "external_url": "外部链接地址",
  "attributes": [
    {
      "trait_type" : "特征类型",
      "value" : "特征值"
    },
    ...
  ]
}

这里踩过坑 文件内容错误的情况下元数据是无效的 一定要仔细看文档

json 文件都创建好之后 和上传图片方式一样上传到 NFT Storage

上传完 json 文件后会生成一个 Gateway URL 将这个地址添加到 env 中 后面我们需要调用

BASE_URL = "Gateway URL"

记得在 Gateway URL 后面加个 / 不然地址会拼接的不正确

scripts 文件夹中的 helpers.js 文件中添加一个函数 getBaseUrl 完整的 helpers.js 文件内容如下

const { ethers } = require("ethers");
const { getContractAt } = require("@nomiclabs/hardhat-ethers/internal/helpers");

// Helper method for fetching environment variables from .env
function getEnvVariable(key) {
  if (process.env[key]) {
    return process.env[key];
  }
  throw `${key} is not defined and no default value was provided`;
}

// Helper method for fetching a connection provider to the Ethereum network
function getProvider() {
  return ethers.getDefaultProvider(getEnvVariable("NETWORK"), {
    alchemy: getEnvVariable("ALCHEMY_KEY"),
  });
}

// Helper method for fetching a wallet account using an environment variable for the PK
function getAccount() {
  return new ethers.Wallet(getEnvVariable("ACCOUNT_PRIVATE_KEY"), getProvider());
}

// Helper method for fetching a contract instance at a given address
function getContract(contractName, hre) {
  const account = getAccount();
  return getContractAt(hre, contractName, getEnvVariable("NFT_CONTRACT_ADDRESS"), account);
}

// Helper method for fetching a IPFS Gateway URL
function getBaseUrl() {
  return getEnvVariable("BASE_URL")
}

module.exports = {
  getEnvVariable,
  getProvider,
  getAccount,
  getContract,
  getBaseUrl,
}

scripts 文件夹中的 mint.js 文件中添加两个 task 完整的 mint.js 文件内容如下

const { task } = require("hardhat/config");
const { getContract, getBaseUrl } = require("./helpers");
const fetch = require("node-fetch");

task("mint", "Mints from the NFT contract").addParam("address", "The address to receive a token").setAction(async function (taskArguments, hre) {
  const contract = await getContract("NFTContract", hre);
  const transactionResponse = await contract.mintTo(taskArguments.address, {
    gasLimit: 500_000,
  });
  console.log(`Transaction Hash: ${transactionResponse.hash}`);
});

task("set-base-token-uri", "Sets the base token URI for the deployed smart contract").setAction(async function (taskArguments, hre) {
  const contract = await getContract("NFTContract", hre);
  const transactionResponse = await contract.setBaseTokenURI(getBaseUrl(), {
    gasLimit: 500_000,
  });
  console.log(`Transaction Hash: ${transactionResponse.hash}`);
});

task("token-uri", "Fetches the token metadata for the given token ID").addParam("tokenId", "The tokenID to fetch metadata for").setAction(async function (taskArguments, hre) {
  const contract = await getContract("NFTContract", hre);
  const response = await contract.tokenURI(taskArguments.tokenId);

  const metadata_url = response;
  console.log(`Metadata URL: ${metadata_url}`);

  const metadata = await fetch(metadata_url).then(res => res.json());
  console.log(`Metadata fetch response: ${JSON.stringify(metadata, null, 2)}`);
});

set-base-token-uri 是调用 setBaseTokenURI 设置我们的 baseTokenURI

token-uri 是获取对应的 token 元数据

添加好之后 顺便将这两个 taskpackage.json 中的 scripts 内的脚本里吧 (顺便也添加下部署到公链上的脚本)

"set-base-token-uri:rinkeby": "NODE_ENV=development npx hardhat set-base-token-uri",
"token-uri:rinkeby": "NODE_ENV=development npx hardhat token-uri --token-id",
"set-base-token-uri:ethereum": "NODE_ENV=production npx hardhat set-base-token-uri",
"token-uri:ethereum": "NODE_ENV=production npx hardhat token-uri --token-id"

命令行执行一下 npm run set-base-token-uri:rinkeby 设置好 baseTokenURI 后 (需要支付 Gas Fee (矿工费)) 再进行铸造(mint)就可以在 OpenSea测试网 进行查看你铸造(mint)的 NFT 了 这时候你会发现已经可以看到图片和属性了 说明我们的 NFT 已经和元数据关联成功了

可能需要等待一段时间才会显示 存在一定的延迟

到这里完整的部署铸造命令执行顺序

1、npm run compile:xxx  编译智能合约
2、npm run deploy:xxx 部署智能合约 (需要支付 Gas Fee (矿工费))
3、将合约地址添加到 env 中
4、上传 图片和元数据 生成 Gateway URL 并添加到 env 中
5、npm run set-base-token-uri:xxx 设置 baseTokenURI (需要支付 Gas Fee (矿工费))
6、npm run mint:xxx 铸造(mint)NFT (需要支付 Gas Fee (矿工费))

到这里我们已经将元数据(metadata)添加到 智能合约 中了 下一篇文章我们再优化下 智能合约 让用户可以自己铸造(mint)

本篇文章代码 Github 地址 https://github.com/stephenml/nft-contract/tree/day-03

最后修改日期: 2022年7月10日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。