📚

HardhatでPolygonテストネットにNFTをデプロイ・Mintする

2024/02/16に公開

はじめに

今回はテストネットはMumbaiを使います。
ただ、Mumbaiは非推奨となるようです。
https://news.yahoo.co.jp/articles/95700d014355c59eb3e7cf6fb8ef5effa04197f1
Amoyになる模様。

Hardhatのバージョンは
2.20.1
を使います。

コントラクト実装

OpenZepplinのERC721コントラクトを使います。バージョンは5.0.1
https://docs.openzeppelin.com/contracts/5.x/

yarn add @openzeppelin/contracts
contracts/HideExampleNft.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

// Uncomment this line to use console.log
// import "hardhat/console.sol";

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract HideExampleNft is ERC721, Ownable {
  constructor() ERC721("HideExampleNft", "HENFT") Ownable(_msgSender()) {}

  function _baseURI() internal pure override returns (string memory) {
    return "https://hid3h.github.io/assets/hide-example-nft/metadata/";
  }

  function safeMint(address to, uint256 tokenId) public onlyOwner {
    _safeMint(to, tokenId);
  }
}

今回は個人だけで使うので、metadataのJSONファイルは自身のGitHubのリポジトリにおいて公開しているものを使います。
また、念の為mintできるのも自身(コントラクトオーナー)のみとしています。

1年ぶりに触ったんですが、Owanableのconstructorが引数を取るように変わっていたのを知らなかった。
https://github.com/OpenZeppelin/openzeppelin-contracts/commit/13d5e0466a9855e9305119ed383e54fc913fdc60#diff-12d832c90ed93f8f3de2bdb5d70c7f18a00a7ee6ab685510c2f4c88510d32180

テスト実装

test/HideExampleNft.ts
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import { ethers } from "hardhat";

describe("HideExampleNft", function () {
  // We define a fixture to reuse the same setup in every test.
  // We use loadFixture to run this setup once, snapshot that state,
  // and reset Hardhat Network to that snapshot in every test.
  async function deployFixture() {
    // Contracts are deployed using the first signer/account by default
    const [owner, otherAccount] = await ethers.getSigners();

    const HideExampleNft = await ethers.getContractFactory("HideExampleNft");
    const hideExampleNft = await HideExampleNft.deploy();

    return { hideExampleNft, owner, otherAccount };
  }

  it("token nameとsymbolとオーナーが正しく設定されている", async () => {
    const { hideExampleNft, owner } = await loadFixture(deployFixture);

    expect(await hideExampleNft.name()).to.equal("HideExampleNft");
    expect(await hideExampleNft.symbol()).to.equal("HENFT");
    expect(await hideExampleNft.owner()).to.equal(owner.address);
  });

  it("ミントできる", async () => {
    const { hideExampleNft, owner, otherAccount } = await loadFixture(
      deployFixture
    );

    const tokenId = 0;
    await hideExampleNft.connect(owner).safeMint(otherAccount.address, tokenId);
    expect(await hideExampleNft.ownerOf(tokenId)).to.equal(
      otherAccount.address
    );
  });

  it("オーナー以外はミントできない", async () => {
    const { hideExampleNft, otherAccount } = await loadFixture(deployFixture);

    const tokenId = 0;
    await expect(
      hideExampleNft
        .connect(otherAccount)
        .safeMint(otherAccount.address, tokenId)
    ).to.be.revertedWithCustomError(
      hideExampleNft,
      "OwnableUnauthorizedAccount"
    );
  });

  it("token URIが正しい", async () => {
    const { hideExampleNft, owner, otherAccount } = await loadFixture(
      deployFixture
    );

    const tokenId = 0;
    await hideExampleNft.connect(owner).safeMint(otherAccount.address, tokenId);
    const tokenURI = await hideExampleNft.tokenURI(tokenId);
    expect(tokenURI).to.equal(
      "https://hid3h.github.io/assets/hide-example-nft/metadata/0"
    );
  });
});

コントラクトデプロイの設定

hardhat.config.tsの設定を行っていきます。

hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
require("dotenv").config();

const config: HardhatUserConfig = {
  solidity: "0.8.24",
  networks: {
    mumbai: {
      url: `https://polygon-mumbai.g.alchemy.com/v2/${process.env.MUMBAI_ALCHEMY_API_KEY}`,
      accounts: [process.env.PRIVATE_KEY || ""],
    },
  },
};

export default config;

上記のurlに関して。
デプロイにはAlchemyを使います。
https://www.alchemy.com/
Alchemyは、ブロックチェーンとやり取りするためのインフラストラクチャプロバイダーで、ノードへのアクセスを提供しています。
Alchemyを使うことで、自分でノードを立ち上げなくて済みます。

.env
MUMBAI_ALCHEMY_API_KEY=xxxxxxxxxxxxxx

API_KEYはAlchemyで適当にAppを作成して「API Key」から表示されるのでそれを使います。

accountsに関して。
PRIVATE_KEYにはコントラクトをデプロイするアカウントの秘密鍵をセットします。
秘密鍵の確認方法は、MetaMaskの場合は「アカウントの詳細」->「秘密鍵を表示」となります。

.env
MUMBAI_ALCHEMY_API_KEY=xxxxxxxxxxxxxx
PRIVATE_KEY=xxxxxxxxxxx

また、環境変数を.envファイルで扱いたいのでdotenvをインストールしておきます。

yarn add dotenv

コントラクトデプロイ

デプロイするスクリプトを用意してそれを実行します。

scripts/deploy.ts
import { ethers } from "hardhat";

async function main() {
  const hideExampleNft = await ethers.deployContract("HideExampleNft");

  await hideExampleNft.waitForDeployment();

  console.log(`deployed to ${hideExampleNft.target}`);
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

デプロイはネットワークを指定して実行します。
このときデプロイするアカウント(PRIVATE_KEYに秘密鍵をセットしたアカウント)にMATICが必要です。デプロイにガス代がかかるためです。

npx hardhat run --network mumbai scripts/deploy.ts
hide@hidenoMacBook-Pro deploy-and-mint % npx hardhat run --network mumbai scripts/deploy.ts
deployed to 0x038F81478a8A15300772e4D769f21a1Be3289533
hide@hidenoMacBook-Pro deploy-and-mint %

1分くらい時間がかかりました。

デプロイされたコントラクトを確認してみます。
コントラクトアドレスを出力するようにしていたので、そのコントラクトアドレスをpolygonscanで検索してみます。

デプロイされていました。

https://mumbai.polygonscan.com/address/0x038F81478a8A15300772e4D769f21a1Be3289533
ガス代は0.0029MATICかかっていました。

ミント

以下のアドレスのアカウント(今回はコントラクトをデプロイしたアカウントと同じ)にトークンID0でミントしてみます。
0xB55327dC3dA686b6c312De1E6c8F6361a107718a

scripts/mint.ts
import { ethers } from "hardhat";
require("dotenv").config();

const contract = require("../artifacts/contracts/HideExampleNft.sol/HideExampleNft.json");
const contractAddress = "0x038F81478a8A15300772e4D769f21a1Be3289533";
// ミント対象のアドレス
const to = "0xB55327dC3dA686b6c312De1E6c8F6361a107718a";
// トークンID
const tokenId = 0;

async function main() {
  const provider = ethers.provider;
  // 署名(mint)するアカウントを取得
  // getSigner()の引数無しの場合、デフォルトで0番目のアカウントを取得
  // ここで言うアカウントはhardhat.config.tsのaccountsにセットしているもの
  const signer = await provider.getSigner();
  const abi = contract.abi;
  // コントラクトのインスタンスを作成
  const myNftContract = new ethers.Contract(contractAddress, abi, signer);
  // コントラクトで実装したsafeMint()を呼び出してミントします
  const nftTxn = await myNftContract.safeMint(to, tokenId);

  await nftTxn.wait();

  console.log(
    `NFT Minted! Check it out at: https://etherscan.io/tx/${nftTxn.hash}`
  );
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

また、NFTのメタデータのJSONもデプロイしておきます。
今回は、https://github.com/hid3h/hid3h.github.io/tree/main/assets/hide-example-nft
でGitHub Pagesで公開したものを使います。

ミント用のスクリプトを実装して実行します。

npx hardhat run --network mumbai scripts/mint.ts
hide@hidenoMacBook-Pro deploy-and-mint % npx hardhat run --network mumbai scripts/mint.ts
NFT Minted! Check it out at: https://etherscan.io/tx/0x8db7ef2ceba3bf7b8a35f23144c62dd8052ee864913eebad87094a2dae20e856
hide@hidenoMacBook-Pro deploy-and-mint %

今回は5秒くらいで完了しました。

ミントに成功していました。
https://mumbai.polygonscan.com/tx/0x8db7ef2ceba3bf7b8a35f23144c62dd8052ee864913eebad87094a2dae20e856

0.0001MATICかかっていました。

OpenSea(テストネットワーク)で確認

ミントすると特に設定すること無くOpenSea上でNFTを確認することができます。
コントラクトアドレスで検索するとページを見つけることができます。
https://testnets.opensea.io/ja/collection/hideexamplenft

ミントした0番目のNFTも確認することができました。
https://testnets.opensea.io/ja/assets/mumbai/0x038f81478a8a15300772e4d769f21a1be3289533/0
画像やDescriptionなども、https://github.com/hid3h/hid3h.github.io/blob/main/assets/hide-example-nft/metadata/0 で設定したものになっていました。

サンプルプロジェクト

https://github.com/hid3h/nft-examples/tree/main/deploy-and-mint

Discussion