Alchemy Road to Web3 第三周中文教程

第三周:通过 Hardhat 和 JavaScript 制作有链上元数据的NFT

本教程只介绍操作步骤,代码含义等不做解释,下面是官方英文详细教程链接:

https://docs.alchemy.com/docs/how-to-make-nfts-with-on-chain-metadata-hardhat-and-javascript

Step1.添加Polygon Mumbai到 Metamask 钱包

1.打开链接 Polygon Mumbai,然后滑动到网页底部,点击右下角按钮完成Polygon Mumbai的添加

post image

2.在水龙头( mumbaifaucet.comfaucet.polygon.technology )获取测试币,用于支付部 署需要的Gas Fee

post image

等待10~20秒,可以收到测试币

Step2.项目的环境配置

1.创建项目文件夹,并导入到VSStudio

1.1 创建文件夹 “ChainBattled”,使用VSStudio的“打开文件夹” 功能

使用VSStudio的“打开文件夹” 功能
使用VSStudio的“打开文件夹” 功能

1.2 选择对应文件夹并打开

打开对应文件夹
打开对应文件夹

2.项目依赖库配置

2.1 在VSStudio打开终端

post image

2.2 在终端输入如下命令 , 安装hardhat

 yarn add hardhat
post image

2.3 在终端输入如下命令 , 初始化hardhat

nxp hardhat init

选择 “Create a JavaScript project”,键盘点击回车确认

post image

终端弹出相关hardhat初始化配置的提示,全点击“回车”

post image

至此,hardhat 初始化结束

post image

2.4 输入如下命令安装 OpenZeppelin 库,这个库可以方便我们创建接下来的合约。

 yarn add @openzeppelin/contracts 
post image

3.在项目中配置Polygon Mumbai网络环境

3.1 终端输入如下命令 安装 dotenv

3.2 终端输入如下命令创建 .env 文件

 touch .env 
post image

3.3 获取 Polygon Mumbai 的 TESTNET_RPC

进入 Alchemy创建app的界面 创建一个 Polygon Mumbai 网络的app,创建app的时候注意网络需要选择 Polygon Mumbai

post image

进入app详情界面,点击 VIEW KEY 按钮,图中的HTTPS就是我们需要的 TESTNET_RPC

post image

3.4 获取 Polygon Mumbai 的 POLYGONSCAN_API_KEY

打开 Polygon注册界面,创建一个账号

post image

账号注册成功后,进入 API KEY配置界面 ,创建API KEY

post image

下图Api Key Token就是我们需要的POLYGONSCAN_API_KEY

post image

3.5 打开 .env文件,配置对应的值

post image

3.6 打开 hardhat.config.js文件,输入下面的代码

require("dotenv").config();
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-etherscan");

module.exports = {
  solidity: "0.8.10",
  networks: {
    mumbai: {
      url: process.env.TESTNET_RPC,
      accounts: [process.env.PRIVATE_KEY]
    },
  },
  etherscan: {
    apiKey: process.env.POLYGONSCAN_API_KEY
  }
};
post image

Step3.编辑代码并发布合约

1.创建合约文件

删除 contracts目录下原有文件,创建文件 ChainBattles.sol

post image

下面是文件源码

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract ChainBattles is ERC721URIStorage {
    using Strings for uint256;
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    mapping(uint256 => uint256) public toknenIdToLevels;

    constructor() ERC721("Chain Battles", "CBTLS") {}

    function generateCharacter(uint256 tokenId)
        public
        view
        returns (string memory)
    {
        bytes memory svg = abi.encodePacked(
            '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350">',
            "<style>.base { fill: white; font-family: serif; font-size: 14px; }</style>",
            '<rect width="100%" height="100%" fill="black" />',
            '<text x="50%" y="40%" class="base" dominant-baseline="middle" text-anchor="middle">',
            "Warrior",
            "</text>",
            '<text x="50%" y="50%" class="base" dominant-baseline="middle" text-anchor="middle">',
            "Levels: ",
            getLevels(tokenId),
            "</text>",
            "</svg>"
        );
        return
            string(
                abi.encodePacked(
                    "data:image/svg+xml;base64,",
                    Base64.encode(svg)
                )
            );
    }

    function getLevels(uint256 _tokenId) public view returns (string memory) {
        uint256 levels = toknenIdToLevels[_tokenId];
        return levels.toString();
    }

    function getTokenURI(uint256 tokenId) public view returns (string memory) {
        bytes memory dataURI = abi.encodePacked(
            "{",
            '"name": "Chain Battles #',
            tokenId.toString(),
            '",',
            '"description": "Battles on chain",',
            '"image": "',
            generateCharacter(tokenId),
            '"',
            "}"
        );
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(dataURI)
                )
            );
    }

    function mint() public {
        _tokenIds.increment();
        uint256 newItemId = _tokenIds.current();
        _mint(msg.sender, newItemId);
        toknenIdToLevels[newItemId] = 0;
        _setTokenURI(newItemId, getTokenURI((newItemId)));
    }

    function raiseLevel(uint256 _tokenId) public {
        require(_exists(_tokenId), "error, the tokenId not exists");
        require(
            ownerOf(_tokenId) == msg.sender,
            "error, you are not the owner"
        );
        toknenIdToLevels[_tokenId] = toknenIdToLevels[_tokenId] + 1;
        _setTokenURI(_tokenId, getTokenURI(_tokenId));
    }
}

2.创建部署文件

打开scripts目录下的deploy.js 文件,复制下面的源码到文件

post image
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");

const main = async () => {
  try {
    const contractFactory = await hre.ethers.getContractFactory("ChainBattles");
    const contract = await contractFactory.deploy();
    await contract.deployed();
    console.log("Contract deployed to:", contract.address);
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};

main();

3.编译发布合约

3.1 在终端输入以下命令编译合约

npx hardhat compile
post image

3.2 输入以下命令在mumbai网络发布合约

post image
 npx hardhat run scripts/deploy.js --network mumbai

部署成功后可以在终端看到部署的合约地址 打开区块浏览器并输入对应合约地址可以看到部署的合约

post image

4.验证并发布合约源码

4.1在终端输入以下命令,其中合约地址需要替换为在步骤3.2中生成的合约地址

npx hardhat verify --network mumbai 0x660842E5eD720264ccC9f70002e78200d58a26b8

按照官方文档的说法,输入上面的命令就能直接成功,然而我在这一步却遇到了下图的网络问题,导致始终无法verify成功。

post image

于是我只能手动在区块浏览器去验证合约,如果你输入verify命令后直接成功了,可以忽略接下来的步骤,直接跳到 Step5.

4.2 打开 artifacts/build-info 文件夹中的文件,文件名和我不一样不用管

post image

复制该文件中如下部分

 "input": {
    "language": "Solidity",
    "sources": {
      "contracts/ChainBattles.sol": {
<<中间部分太长,不展示到这里>>
 "settings": {
      "optimizer": {
        "enabled": false,
        "runs": 200
      },
      "outputSelection": {
        "*": {
          "*": [
            "abi",
            "evm.bytecode",
            "evm.deployedBytecode",
            "evm.methodIdentifiers",
            "metadata"
          ],
          "": [
            "ast"
          ]
        }
      }
    }
  }

整个代码部分我没有复制完全,你只需注意从下图的 ”{“ 开始复制,

post image

复制到下图的 “}” 结束

post image

4.3在contracts目录下创建一个文件 verify.json , 将4.2复制内容拷贝进去并保存

post image

4.4 在区块浏览器进入到刚才创建的合约,合约地址注意使用你生成的合约地址,然后点击图中标注的 “Verify and Publish”

post image

按下图配置相应的选项,然后点击 “continue” 按钮

post image

在下图的界面点击 “选择文件” 按钮,选择 4.3种创建的文件 “verify.json”,然后点击”Click to Upload selected file“

post image

上传成功后出现如下提示

post image

最后滑动到界面底部,点击 “Verify and Publish” 按钮

等待一会儿出现下图界面,就表示认证成功

post image

Step5.交互生成的合约,提交任务表单

5.1 进入合约浏览器查看我们创建的合约,点击如图的mint按钮,通过 MetaMask 签名并执行操作

post image

5.2 进入 OpenSea的个人中心,就可以看到你创建的NFT

post image

至此,Week3的任务完成,接下来就打开任务提交链接

等官方快照完成后在这里Claim 第三周的NFT

后续几周的教程会更新到下面的我的推特,对第三周教程有什么疑问也可以在推特问我。