# Alchemy Road to Web3 第三周中文教程

By [LaiLai](https://paragraph.com/@lailai-2) · 2022-08-15

---

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

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

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

### Step1.添加Polygon Mumbai到 Metamask 钱包

1.打开链接 [Polygon Mumbai](https://mumbai.polygonscan.com/)，然后滑动到网页底部，点击右下角按钮完成Polygon Mumbai的添加

![](https://storage.googleapis.com/papyrus_images/ab5f24ca6d3314d2e9ca40fbd1690f6c97ea7a2b5cc65d218c11f868f478f612.png)

2.在水龙头( [mumbaifaucet.com](https://mumbaifaucet.com/) 或 [faucet.polygon.technology](https://faucet.polygon.technology/) )获取测试币，用于支付部 署需要的Gas Fee

![](https://storage.googleapis.com/papyrus_images/7ccb64cc6cd9f7b5f597001a8b02eec10d5a916eb9b70c49f99011a3f9e00862.png)

等待10～20秒，可以收到测试币

### Step2.项目的环境配置

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

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

![使用VSStudio的“打开文件夹” 功能](https://storage.googleapis.com/papyrus_images/37cb636207a508e1a7a0d497213d39e11536df913d563befb46837b7744cefc7.png)

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

1.2 选择对应文件夹并打开

![打开对应文件夹](https://storage.googleapis.com/papyrus_images/ba4011b8ae28459c55870e27e78cd8dba72a3e35d87572499188e06883224298.png)

打开对应文件夹

**2.项目依赖库配置**

2.1 在VSStudio打开终端

![](https://storage.googleapis.com/papyrus_images/83fd9b4b93211a51bf92f70d053f01d758aa04daae37728776b7037285a6e9f7.png)

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

     yarn add hardhat
    

![](https://storage.googleapis.com/papyrus_images/ef9feba9e1fee2f874b0938195e08890b0ed316f4d32fe477a3dd5bf5f8bf374.png)

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

    nxp hardhat init
    

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

![](https://storage.googleapis.com/papyrus_images/0f65cbb0c22fc093e09338fbca934c9ce644ac8beb5cee814ca879fe57b1e157.png)

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

![](https://storage.googleapis.com/papyrus_images/bab874bf3297c60567516507a8bad95e833becd9fd49f5d263eeddb5d1b4e6a4.png)

至此，hardhat 初始化结束

![](https://storage.googleapis.com/papyrus_images/a2726bb2d73256e4d876394503c3669b694f136efa3fc5461f1281c0d554025e.png)

2.4 输入如下命令安装 [OpenZeppelin](https://docs.openzeppelin.com/contracts/4.x/) 库,这个库可以方便我们创建接下来的合约。

     yarn add @openzeppelin/contracts 
    

![](https://storage.googleapis.com/papyrus_images/bd40e339ee472cd34b2da185ede6c457136c9702e32d18e5a7890cd4d75c4e68.png)

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

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

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

     touch .env 
    

![](https://storage.googleapis.com/papyrus_images/8fd3adf4627f5333c2236d38d49eaba3108fd7f1dd0265ee5245827f63707137.png)

3.3 获取 Polygon Mumbai 的 TESTNET\_RPC

进入 [Alchemy创建app的界面](https://dashboard.alchemyapi.io/apps) 创建一个 Polygon Mumbai 网络的app，创建app的时候注意网络需要选择 Polygon Mumbai

![](https://storage.googleapis.com/papyrus_images/f97a42c3efa345fdd669f8ef23fb802fe2eb2bd79027be90a0d2b131fa0c9c0e.png)

进入app详情界面，点击 VIEW KEY 按钮，图中的HTTPS就是我们需要的 TESTNET\_RPC

![](https://storage.googleapis.com/papyrus_images/a8788b3b8e01e1126881133f761cd662ee91d7dc205ce2a246487825b0b44ebb.png)

3.4 获取 Polygon Mumbai 的 POLYGONSCAN\_API\_KEY

打开 [Polygon注册界面](https://polygonscan.com/register)，创建一个账号

![](https://storage.googleapis.com/papyrus_images/7bd64539ac6c330ab6a7618807d919d1b0a14b3977e69d6e0c2cd69e123c0e4e.png)

账号注册成功后，进入 [API KEY配置界面](https://polygonscan.com/myapikey) ，创建API KEY

![](https://storage.googleapis.com/papyrus_images/56406e6d1556f9fac9b007c66b5509569fda0aff743d8d424f8d0f88daabaec1.png)

下图Api Key Token就是我们需要的POLYGONSCAN\_API\_KEY

![](https://storage.googleapis.com/papyrus_images/5a36ec8583ff4e60a704c66bb8e8dcf94b2007ae0f66e91a44dc38e9cbaf8858.png)

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

![](https://storage.googleapis.com/papyrus_images/927b8a3385b9e2761224e2d984f1b0e1c75cae7f19369d3ca9381794a55def7e.png)

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
      }
    };
    

![](https://storage.googleapis.com/papyrus_images/82a60f866d6925c2288abc27f30efb66c330525a05a49a568ff3ebe07fea7cff.png)

### Step3.编辑代码并发布合约

### 1.创建合约文件

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

![](https://storage.googleapis.com/papyrus_images/a3e4166f071280e3c75c5171f1996e31431e8b37f08568764068e05c83784686.png)

下面是文件源码

    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 文件，复制下面的源码到文件

![](https://storage.googleapis.com/papyrus_images/242669945b1d3dd292e67111caa8d8e5643bd37d311c47df6d1a03ebd6702872.png)

    // 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
    

![](https://storage.googleapis.com/papyrus_images/6f683eebe08c4ca2b5169df80518358739e5ef7ff65ed673ddfeb799a0c1f5a3.png)

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

![](https://storage.googleapis.com/papyrus_images/b092cb7828fb10f6a76dca9c60f81cc3a191474494db766bdc6b05ec2f74ba0b.png)

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

部署成功后可以在终端看到部署的合约地址 打开[区块浏览器](https://mumbai.polygonscan.com/)并输入对应合约地址可以看到部署的合约

![](https://storage.googleapis.com/papyrus_images/907cb526275807ba0afbaaecc6e656f4ed13a9e315da9edc0c161662f5ecf074.png)

### 4.验证并发布合约源码

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

    npx hardhat verify --network mumbai 0x660842E5eD720264ccC9f70002e78200d58a26b8
    

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

![](https://storage.googleapis.com/papyrus_images/2211754f152438634ac2f241a43225bdd238f03451e2e617ca836601caca57d4.png)

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

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

![](https://storage.googleapis.com/papyrus_images/32ecb629de81f790b84e0e532ca4eb80bbaeb35bc71694836f0b296f4dd5d626.png)

复制该文件中如下部分

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

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

![](https://storage.googleapis.com/papyrus_images/ce9ec5e0282c76e2080c32ff85a3e1473b90206cb4d5d5a41044a48204b9f99a.png)

复制到下图的 “}” 结束

![](https://storage.googleapis.com/papyrus_images/69a5bc530f038fb08ca4385396d49ddcc9c58b626c26fca7fbb845a764885bd8.png)

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

![](https://storage.googleapis.com/papyrus_images/79f9af4e65b3c2e4a51140eed46b548e0ad9b0b522411d1a38324f0933b7f97b.png)

4.4 在[区块浏览器](https://mumbai.polygonscan.com/address/0x660842E5eD720264ccC9f70002e78200d58a26b8#code)进入到刚才创建的合约，合约地址注意使用你生成的合约地址，然后点击图中标注的 “Verify and Publish”

![](https://storage.googleapis.com/papyrus_images/68c1c3d8951b78d22033f5e58c0b2a73121f792d90e7015c35cd9ae7d45b8a22.png)

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

![](https://storage.googleapis.com/papyrus_images/6e4796b72ab31ddf81772b4e665c1ef583c400d55030db4a7e741e91e7820642.png)

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

![](https://storage.googleapis.com/papyrus_images/3f8974141523aeb24fbe2a97c0d5cdb459c99ea4108bd0d710f7dd05f7a1c4e1.png)

上传成功后出现如下提示

![](https://storage.googleapis.com/papyrus_images/31bded3bab67608f60aa9c3d60283c8f6478af12374ec48d339e3542bd1eac19.png)

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

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

![](https://storage.googleapis.com/papyrus_images/e8a819bcc3d151b376bda917eb9a60e8bea2eb98e62d284aaae9ab82ef748afe.png)

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

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

![](https://storage.googleapis.com/papyrus_images/fdb81afbb56b5e31d5560d427bfc4218fe941ff4a60fcfb0bedfcd548a2af291.png)

5.2 进入 [OpenSea的个人中心](https://testnets.opensea.io/zh-CN/account)，就可以看到你创建的NFT

![](https://storage.googleapis.com/papyrus_images/f626179a3dedef6416e8fae3af35f93d1b68369e38945980c7bee57522db8443.png)

至此，Week3的任务完成，接下来就打开[任务提交链接](https://alchemyapi.typeform.com/roadtoweekthree)，

等官方快照完成后在[这里](https://mintkudos.xyz/claim/674)Claim 第三周的NFT

后续几周的教程会更新到下面的我的[推特](https://twitter.com/zhoulailaiZ)，对第三周教程有什么疑问也可以在推特问我。

---

*Originally published on [LaiLai](https://paragraph.com/@lailai-2/alchemy-road-to-web3)*
