DX企業で働くエンジニアのブログ

DXエンジニアとして働いています。ブロックチェーンも時々やります

【2021年版】NFTを発行してほしいという問い合わせが多いので発行方法とサンプルコードを公開します

タイトル通りですが、NFTを発行して欲しいと問い合わせがたくさん来ており、直近忙しくしているので方法だけまとめておきます。

OpenZeppelin + Solidity0.8 + Hardhat + ethers.jsが良さそう

Solidityが0.4.21でワイワイしていたのはいつのことか。2021年はもうv0.8台になりました。OpenZeppelinもv0.8台で書かれています。

過去にはtruffle + Ganacheで開発がメジャーでしたが、Solidityの0.8系では時々不安定なことがあり、Hardhatに乗り換えました。Hardhatはtruffleのようなフレームワークに加え、内部でethers.jsのEVM機能を用いてGanacheのような仮想ノード環境を作っています。

hardhat.org

web3.jsよりHardhatで使われているethers.jsの方が取り回しがしやすいと思います。

Hardhatでプロジェクトを作る

Hardhatはチュートリアルがしっかりしているのでこれ通りにすればできるのですが、ざっくり概要を書いておきます。

hardhat.org

  1. hardhatのインストール
npm install --save-dev hardhat
  1. 空またはgit initしたディレクトリ内で、npx hardhatでプロジェクト作成.基本yesで答えていく

途中でwaffleのインストールなどもされる

f:id:naomasabit:20211018085611p:plain

  1. 一通りできていることを確認する
$ ls
README.md       node_modules        scripts
contracts       package-lock.json   test
hardhat.config.js   package.json
  1. hardhat.config.jsを修正して利用したいSolidityのバージョンを設定する
require("@nomiclabs/hardhat-waffle");

// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {
  const accounts = await hre.ethers.getSigners();

  for (const account of accounts) {
    console.log(account.address);
  }
});

// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more

/**
 * @type import('hardhat/config').HardhatUserConfig
 */
module.exports = {
  solidity: "0.8.4",
};

NFTコントラクトを、OpenZeppelinで作る

OpenZeppelinは2021年現在も健在です。installしておきましょう。

npm install @openzeppelin/contracts

コントラクト

先ほど作ったhardhatのプロジェクトの「contracts」ディレクトリ内に「Greeter.sol」があるので、「nft.sol」にrenameして以下のように書き換えます。

OpenZeppelinはよくあるコントラクトテンプレートをPresetとしても用意してくれていて、これを継承すると良い感じのNFTが作れます。

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract MyNFT is ERC721PresetMinterPauserAutoId {
    constructor()
        public
        ERC721PresetMinterPauserAutoId(
            "MyNFT", // トークン名
            "NFT",  // シンボル
            "https://example.com/token/" // TokenURI
        )
    {}
}

デプロイスクリプト

「scripts/sample-script.js」を「scripts/deploy.js」にrenameして、以下のように書き換えます。

// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.
const hre = require('hardhat');

async function main() {
  // Hardhat always runs the compile task when running scripts with its command
  // line interface.
  //
  // If this script is run directly using `node` you may want to call compile
  // manually to make sure everything is compiled
  // await hre.run('compile');
  const [deployer] = await ethers.getSigners();
  console.log('Deploying contracts with the account:', deployer.address);

  // We get the contract to deploy
  const MyNFT = await ethers.getContractFactory('MyNFT');
  const nft = await MyNFT.deploy();

  await nft.deployed();

  console.log('MyNFT deployed to:', nft.address);
}

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

ローカルで仮想ノードを動かす

npx hardhat node

で仮想ノードを動かします。

f:id:naomasabit:20211017231647p:plain

※node.jsのバージョンがサポート外とおこられています通り、本当はきちんとサポート対象のnode.jsを使った方がいいです

仮想ノードにデプロイ

npx hardhat run scripts/deploy.jsスクリプト実行します。

$ npx hardhat run scripts/deploy.js
You are using a version of Node.js that is not supported by Hardhat, and it may work incorrectly, or not work at all.

Please, upgrade your Node.js version.

To learn more about which versions of Node.js are supported go to https://hardhat.org/nodejs-versions
Deploying contracts with the account: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
MyNFT deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

仮想環境にデプロイされました。

hadhat flattenでコントラクトをフラットな形で見る

コントラクトは継承していますが、etherscanに登録するような形で、展開した状態でみたい場合もあります。そうすると以下コマンドを打ちます。

npx hardhat flatten > flat.sol  

flat.solに展開されたSolidityコードが入っています。

testを書く

testディレクトリにスクリプトが入っているので、テストを書いて

npx hardhat test

で実行します。

OpenZeppelinの関数を変えたい場合、オーバーライドする

Solidity 0.6.0以降から、virtual / overrideの識別子が増えました。

virtualがついている関数は継承先でoverrideと書くことでオーバーライドできます。OpenZeppelinのコードはほとんどvirtualがついていて、オーバーライドすることができます。

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol#L79

プロジェクト仕様的に変えたい場合、継承先でオーバーライドして関数を書き換えると良いでしょう。

ただし、引数の個数が違うなどオーバーライドの要件を満たさない場合、新たな関数を作るか、OpenZeppelinをフォークかローカルにダウンロードして変更を加えてそれをimportすることになると思います。

サンプルコードを公開しました

ここまで書いた内容を、githubのpublic リポジトリで公開しました。testコードは特にサンプルからいじっていないので修正する必要があります。

github.com

役に立ったよ、という方は以下アドレスにETH,MATIC,JPYC、それから当然NFTでの寄付を歓迎しています。

0xBeB0fA6e09Be83b0Cc072933824458A5585A89F3

[宣伝] v0.4系で少しバージョンは古いですが、2年前に翻訳した書籍です。Solidityの基本文法などの理解には役立つかと思います。