hardhat + ether.jsを使用した機能検証用一番シンプルなNFT用コントラクト

はじめに
-
本記事はLinux／Unixサーバーを対象にGUIを使用できない環境で機能検証するためにEthereumまたはEthereum互換の別チェーン（例：Polygon、Fantomなど）上に最もシンプルなNFTを発行するための手順を解説します。ブロックチェーンの機能検証にお時間を多くかけたくない方におすすめです。

本記事では、Japan Open Chainという日本発のブロックチェーンをサンプルとして使用し、SolidityでERC721標準に基づいたNFTを発行します。

※ Japan Open Chainについてはこちらの記事をご参考ください。
→ <a href="https://note.com/joc_official/n/n7c20014a5fa9">解説レポート〜「1分でわかる！Japan Open Chain」</a>

必要なソフトウェア：
・Linux／Unix環境（本記事ではUbuntuを使用）
・Node.js／npm
・Ether.js
・Hardhat（Ethereumのスマートコントラクト開発環境）
・秘密鍵（Metamaskなどからエクスポートしたものを使用）
・RPCサーバーURL (または別のETH互換チェーン) 

環境構築
-
手順1. Node.js と npm のインストール
--
まず、Node.jsとnpmがインストールされているか確認します。
```
which node
which npm
```
インストールされていない場合は、以下のコマンドでインストールします。
```
sudo apt update
sudo apt install nodejs npm
```

手順2. Hardhatのセットアップ
--
プロジェクトフォルダを作成して、Hardhatで初期化します。
```
mkdir simple-nft-sample && cd simple-nft-sample
npm init -y
npm install --save-dev hardhat
npx hardhat
```
Hardhatプロジェクトを初期化する際には「Create a basic sample project」を選択し、フロントはJavascriptを選択しました。

手順3. 必要な依存関係のインストール
--
ERC721（NFT）のスマートコントラクトには @openzeppelin/contracts を使用します。
以下のコマンドでインストールします。
```
npm install @openzeppelin/contracts
```

手順4. スマートコントラクトの作成
--
contractsフォルダにSimpleNFTSample.solという名前のファイルを作成し、以下のコードを記述します。

```SimpleNFTSample.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract SNFTS is ERC721 {
    constructor() ERC721("SNFTS", "SNFTS") {}

    function nftMint(address to, uint256 tokenId) public {
        _mint(to, tokenId);
    }

    function burn(uint256 tokenId) public {
        _burn(tokenId);
    }
}
```
このコードは、関数を呼び出すことでNFTを発行また焼却できる一番シンプルなERC721トークンを作成します。


手順5. 環境設定
--
手順4で作成したスマートコントラクトをデプロイするために、ターゲットブロックチェーンの設定を含める必要があります。

hardhat.config.jsにRPCサーバーURLとウォレットの秘密鍵を追加します。

対象ブロックチェーンのRPCサーバーURLが不明な場合は下記サイトをご活用ください。
→ <a href="https://chainlist.org/"> [Chainlist] Helping users connect to EVM powered networks</a>

```hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.20",
    settings: {
      evmVersion: "london",
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  },
  networks: {
    joct: {
      url: "https://rpc-3.testnet.japanopenchain.org", // RPCサーバーのURLを書き換え
      chainId: 10081,
      accounts: ["// 0x付きでウォレットの秘密鍵を挿入"],
      asPrice: 1000000000 // ガス代をここに設定
    },
    eth-sepolia: {
      url: "https://1rpc.io/sepolia", // RPCサーバーのURLを書き換え
      chainId: 11155111,
      aaccounts: ["// 0x付きでウォレットの秘密鍵を挿入"],
      gasPrice: 2000000000 // ガス代をここに設定
    },
    eth-holesky: {
      url: "https://1rpc.io/holesky", // RPCサーバーのURLを書き換え
      chainId: 17000,
      aaccounts: ["// 0x付きでウォレットの秘密鍵を挿入"],
      gasPrice: 300000000 // ガス代をここに設定
    }
  }
};
```

手順6. デプロイスクリプトの作成
--
scriptsフォルダにdeploy.jsというファイルを作成し、以下のデプロイ用スクリプトを記述します。
```deploy.js
async function main() {
    const [deployer] = await ethers.getSigners();

    console.log("Deploying contracts with the account:", deployer.address);

    const NFT = await ethers.getContractFactory("SNFTS");
    const nft = await NFT.deploy();

    console.log("NFT contract object:", nft);
    console.log("Deploy transaction:", nft.deployTransaction);

    if (nft.deployTransaction) {
        console.log("Waiting for the contract to be deployed...");
        const receipt = await nft.deployTransaction.wait();

        console.log("Contract deployed to:", nft.address);
        console.log("Transaction receipt:", receipt);
    } else {
        console.error("Deploy transaction is undefined.");
    }
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error);
        process.exit(1);
    });
```
上記コードを記述完了後、Hardhatでスマートコントラクトをコンパイルします。
```
npx hardhat compile
```
特に問題なさそうであれば、下記コマンドでスマートコントラクトをデプロイします。
```
npx hardhat deplot ./scripts/deploy.js --network <hardhat.config.jsに記載したネットワーク名>
```
「Deploy Successed」、そしてスマートコントラクトアドレスが表示されたら、対象ブロックチェーンのエクスプローラで確認できます。

Hardhatとether.jsのバージョン問題による実行失敗問題
--
時々Hardhatとether.jsのバージョン問題によりdeploy.jsを実行失敗する場合があります。
下記コードはHardhatを経由せずethers.jsで直接デプロイできるスクリプトです。
scriptsフォルダにdeploy-ethers.jsというファイルを作成し、以下のデプロイ用スクリプトを記述します。

```deploy-ether.js
const { JsonRpcProvider, Wallet, ContractFactory } = require("ethers");
const fs = require("fs");

// プロバイダーとウォレットの設定
const provider = new JsonRpcProvider("// RPCサーバーのURLを書き換え");
const privateKey = process.env.PRIVATE_KEY || "// 0x付きでウォレットの秘密鍵を挿入";
const wallet = new Wallet(privateKey, provider);

async function main() {
  try {
    // コントラクトのバイトコードとABIをロード
    const contractJson = JSON.parse(fs.readFileSync("/artifacts/contracts/SNFTS.sol/SNFTS.jsonの絶対パスに書換え").toString());
    const abi = contractJson.abi;
    const bytecode = contractJson.bytecode;

    // コントラクトファクトリを作成
    const factory = new ContractFactory(abi, bytecode, wallet);

    // コントラクトのデプロイ
    console.log("Deploying contract...");
    const contract = await factory.deploy();
    await contract.waitForDeployment();
    console.log(`Contract deployed at address: ${contract.target}`);
  } catch (error) {
    console.error("Error deploying contract:", error);
  }
}

main().catch(console.error);
```
上記コードを使用するため、依存関係のインストールが必要です。
以下のコマンドでインストールします。
```
npm install fs
```
インストール完了後、下記コードでスマートコントラクトをデプロイします。
```
node ./scripts/deploy-ethers.js
```
「Deploy Successed」、そしてスマートコントラクトアドレスが表示されたら、対象ブロックチェーンのエクスプローラで確認できます。

最後
-
本記事は対象ブロックチェーンのNFT機能を検証するため、一番シンプルなスマートコントラクトの実装、およびデプロイ方法をご紹介させていただきました。また、Hardhatやether.jsなどバージョンアップによりdeploy.jsが使用できない場合はございます。その際はHardhatを経由せずにデプロイする方法で対応するか、最新の公式ドキュメントを参考し、モジュールを修正するかをご検討ください。本記事はお役に立てれば幸いです。