添加元数据到智能合约中
ERC721 标准规范 定义了一个名为 ERC721Metadata
的扩展,这个扩展定义了一个函数 接收一个参数并返回一个原数据的 token
链接 这里就不详细介绍 元数据
了 如果需要了解 可以查看 OpenSea文档
我们需要扩展一下之前的 智能合约
(NFTContract.sol)文件 添加一个 baseURL
并且定义一下 setter
和 getter
以供 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 Lab
的 NFT Storage
看你个人选择了 这里我们使用的是 Protocol Lab
的 NFT 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
元数据
添加好之后 顺便将这两个 task
到 package.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
留言