Alchemy第三周任务-使用Polygon链上元数据制作 NFT

我们都知道创建 NFT 时,最好将元数据存储在集中式对象存储IPFS等分散式解决方案中,以避免直接在链上存储大量数据(如图像和 JSON 对象)产生的巨额 Gas 费用。

但这有一个问题:

不将元数据存储在区块链上将使您无法通过智能合约与之交互,因为区块无法与“外部世界”通信。**如果我们想直接从我们的智能合约更新我们的元数据,我们需要将其存储在链上,但是汽油费呢?幸运的是,像 Polygon 这样的 L2 链可以提供帮助,大大降低了 Gas 成本,并引入了许多优势,使开发人员能够扩展其应用程序的功能。

在本教程中,学习如何创建区块链游戏的基础知识,开发一个完全动态的 NFT,其链上元数据会根据您与它的交互而变化,并将其部署在Polygon Mumbai上以降低汽油费。

更准确地说,您将学习:

  • 如何在链上存储 NFT 元数据

  • 什么是 Polygon 以及为什么降低 Gas 费用很重要。

  • 如何在 Polygon Mumbai 上部署

  • 如何处理和存储链上 SVG 图像和 JSON 对象

  • 如何根据您与 NFT 的交互来修改元数据

1.Polygon PoS - 更低的 Gas 费用和更快的交易

Polygon 是一个去中心化的 EVM 兼容扩展平台,使开发人员能够在不牺牲安全性的情况下以低交易费用构建可扩展的用户友好型 DApp。

它属于被描述为**第 2 层链 (L2)**的一组链,这意味着它建立在以太坊之上,以解决一些表征它的问题 - 同时依赖它来运行。

众所周知,以太坊既不快也不便宜,在其上部署智能合约可能会迅速变得非常昂贵,这就是PolygonOptimism等 L2 解决方案发挥作用的地方。

例如,多边形有两个主要优点:

  • 更快的交易(65,000 tx/秒 vs ~14)

  • 每笔交易的gas 成本比以太坊低约 10,000 倍

第二个正是我们在 Polygon 上部署带有链上元数据的 NFT 智能合约的原因。一方面,如果在以太坊上存储我们的元数据时,我们可以预期每笔交易花费数百美元,那么在 Polygon 上它的成本不会超过几美分。

1.1将 Polygon Mumbai 添加到您的 Metamask 钱包

首先,让我们将 Polygon Mumbai 添加到我们的 Metamask 钱包中。

导航测试polygon网络并向下滚动到页面底部。您将看到**“添加多边形网络”按钮**,单击它并确认您要将其添加到 Metamask:

post image

1.2获取免费的 Matic 以部署NFT 智能合约

我们去到mumbaifaucet.com获取测试币,输入我们的地址获取测试币即可

post image

10-20 秒后,会看到 MATIC 出现在 Metamask 钱包中。

2.1项目设置

# 新建一个文件夹用于此次项目的搭建
mkdir ChainBattled
cd ChainBattled
# 安装yarn并查看版本
npm install -g yarn
yarn --version
yarn add hardhat
# 初始化项目
npx hardhat init
post image

现在我们需要安装OpenZeppelin包来访问ERC721 智能合约我们将使用该标准作为模板来构建我们的 NFT 智能合约。

yarn add @openzeppelin/contracts

2.2修改 hardhat.config.js 文件

将我们的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
  }
};

2.3开发智能合约

在 contracts 文件夹中,创建一个新文件并将其命名为“ChainBattles.sol”。

与往常一样,我们需要指定SPDX-Licence-Identifierpragma ,并从OpenZeppelin导入几个库,我们将用作智能合约的基础,合约内容如下:

// SPDX-License-Identifier: MIT
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 tokenIdToLevels;

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

    function generateCharacter(uint256 tokenId) public 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 = tokenIdToLevels[tokenId];
    return levels.toString();
}
 function getTokenURI(uint256 tokenId) public 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();
    _safeMint(msg.sender, newItemId);
    tokenIdToLevels[newItemId] = 0;
    _setTokenURI(newItemId, getTokenURI(newItemId));
}

function train(uint256 tokenId) public{
  require(_exists(tokenId),"The tokenId does not exist");
  require(_isApprovedOrOwner(msg.sender, tokenId),"You are not the owner of the NFT"); 
  tokenIdToLevels[tokenId] += 1;
  _setTokenURI(tokenId, getTokenURI(tokenId));
}
}

我们在上面将合约全部完善了,也新增了 4 个不同的功能:

  • **generateCharacter:**生成和更新我们 NFT 的 SVG 图像

  • **getLevels:**获取 NFT 的当前级别

  • **getTokenURI :**获取 NFT 的 TokenURI

  • **mint:**到 mint - 当然

  • **train:**训练 NFT 并提高其等级

首先,让我们在项目的根文件夹中新建一个 .env 文件,并添加以下变量:

TESTNET_RPC=""
PRIVATE_KEY=""
POLYGONSCAN_API_KEY=""

然后,导航到alchemy.com并创建一个新的 Polygon Mumbai 应用程序:

单击新创建的应用程序,复制 API HTTP URL,并将 API 作为“ TESTNET_RPC ”值粘贴到我们在上面创建的 .env 文件中。

打开您的Metamask钱包,点击三个点菜单 > 帐户详细信息 > 并将您的私钥复制粘贴为..env

最后,继续Polygon,并创建一个新帐户,登录后,进入个人资料菜单并单击 API Keys,如果没有则需要新建一个:

post image

现在将 Api-Key 令牌复制粘贴为 .env 中的“ POLYGONSCAN API_KEY ”值。最终结果如下:

post image

在部署我们的智能合约之前的最后一步,我们需要创建部署脚本。

3.1创建部署脚本

首先安装依赖

npm install dotenv
npm install @nomiclabs/hardhat-waffle

我们将deploy.js的脚本替换如下:

const main = async () => {
  try {
    const nftContractFactory = await hre.ethers.getContractFactory(
      "ChainBattles"
    );
    const nftContract = await nftContractFactory.deploy();
    await nftContract.deployed();

    console.log("Contract deployed to:", nftContract.address);
    process.exit(0);
  } catch (error) {
    console.log(error);
    process.exit(1);
  }
};
  
main();

3.2编译和部署智能合约

当我们把脚本写好后,编译智能合约,只需在项目内的终端中运行以下命令:

npx hardhat compile

如果一切按预期进行,你将在 artifacts 文件夹中看到已编译的智能合约。

现在,让我们在运行的 Polygon Mumbai 链上部署智能合约

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

如果一切正常你将在页面看到合约的地址

post image

3.3在 Polygon Scan 上检查智能合约

复制刚刚部署的智能合约的地址,去测试polygon网络,然后**在搜索栏中粘贴智能合约的地址。**进入智能合约页面后,单击“合约”选项卡。你会注意到合约代码不可读:

post image

这是因为我们还没有验证我们的代码。

为了验证我们的智能合约,我们需要回到我们的项,并在终端中运行以下代码:

# npx hardhat verify --network mumbai 合约地址
npx hardhat verify --network mumbai 

0xE49617678DeA173D147e484a10f7dbcc4aE568e1

报错信息:

Error in plugin @nomiclabs/hardhat-etherscan: Failed to send contract verification request.
Endpoint URL: https://api-testnet.polygonscan.com/api
Reason: read ECONNRESET

通过网站添加方法,点击立即验证即可:

post image

3.4通过多边形扫描与您的智能合约交互

现在智能合约已经通过验证,mumbai.polygonscan.com 将在其附近显示一个绿色小勾:

post image
post image

然后寻找“min​​t”函数并点击Write:

post image

这将打开一个 Metamask 弹出窗口,要求支付 gas 费用,单击签名按钮。恭喜!您刚刚铸造了您的第一个动态 NFT - 让我们转移到 OpenSea 测试网来现场查看它。

4.在 OpenSea 上查看您的动态 NFT

复制智能合约地址,前往testnet.opensea.com,并将其粘贴到搜索栏中:

post image

如果一切正常,现在应该会看到您的 NFT 显示在 OpenSea 上,其中包含动态图像、标题和描述。

4.1更新动态 NFT 图像训练 NFT

导航回测试.polygons网络,点击合约标签 > 写合约并寻找“train”功能。

插入您的 NFT 的 ID - 在这种情况下为“1”,因为我们只铸造了一个,然后单击写入:

post image

然后回到testnets.opensea.com并刷新页面:

post image