メインコンテンツまでスキップ

lesson-4_NFTを発行するスマートコントラクトを作ろう

環境づくりが終わり、このlessonからNFTに関する話が始まります!わくわくしていきましょう!!

🪄 NFT を作成しよう

これから、いくつかのNFTを作成します。

下記のように、Web3Mint.solを更新しましょう。 まずは、NFTの仕組みをわかりやすくみるためにERC721URIStorageとそれのfunctionである_setTokenURIを使ってNFTを作成しますが、これはあとで変更します。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

// いくつかの OpenZeppelin のコントラクトをインポートします。
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "hardhat/console.sol";

// インポートした OpenZeppelin のコントラクトを継承しています。
// 継承したコントラクトのメソッドにアクセスできるようになります。
contract Web3Mint is ERC721URIStorage {
// OpenZeppelin が tokenIds を簡単に追跡するために提供するライブラリを呼び出しています
using Counters for Counters.Counter;
// _tokenIdsを初期化(_tokenIds = 0)
Counters.Counter private _tokenIds;

// NFT トークンの名前とそのシンボルを渡します。
constructor() ERC721("TanyaNFT", "TANYA") {
console.log("This is my NFT contract.");
}

// ユーザーが NFT を取得するために実行する関数です。
function makeAnEpicNFT() public {
// 現在のtokenIdを取得します。tokenIdは0から始まります。
uint256 newItemId = _tokenIds.current();
// msg.sender を使って NFT を送信者に Mint します。
_safeMint(msg.sender, newItemId);
// NFT データを設定します。
_setTokenURI(newItemId, "Valuable data!");
// NFTがいつ誰に作成されたかを確認します。
console.log(
"An NFT w/ ID %s has been minted to %s",
newItemId,
msg.sender
);
// 次の NFT が Mint されるときのカウンターをインクリメントする。
_tokenIds.increment();
}
}

これはETH-NFT-Collectionと同様の構成になっています。復習も兼ねて是非一度戻ってみることをおすすめします。

// Web3Mint.sol
contract Web3Mint is ERC721URIStorage {
:
}

ここでは、ERC721URIStorageを継承しています。

なぜ、ERC721ではなくERC721URIStorageを継承しているのかというと、tokenURI関数よりも、setTokenURIの方がシンプルでわかりやすいからという理由です。

今はわからなくても大丈夫なので、そうなんだと受け流してください。

次に、下記のコードを見ていきましょう。

// Web3Mint.sol
using Counters for Counters.Counter;

using Counters for Counters.CounterはOpenZeppelinが_tokenIdsを追跡するために提供するライブラリを呼び出しています。

using A for Bは、Bという型で定義したものがAというメンバー関数を使うことができることになるものです。詳しくは公式を読んでみてください。

ここではCounters.Counterで定義した_tokenIdsが、_tokenIds.current()、_tokenIds.increment()のようにCountersのfunctionを使えるようになっています。

これにより、トラッキングの際に起こりうるオーバーフローを防ぎます。 uint256のNFTをオーバーフローさせるのに必要なETHはとんでもない額になるので、おそらくオーバーフローはしないのですが、対策をしていくのは大事なことだと思います。

次に、下記のコードを見ていきましょう。

// Web3Mint.sol
Counters.Counter private _tokenIds;

ここでは、private _tokenIdsを宣言して、_tokenIdsを初期化しています。

  • _tokenIdsの初期値は0です。
  • tokenIdはNFTの一意な識別子で、0, 1, 2, .. Nのように付与されます。 これが初めから強調してきた、NFTの本体と言ってもいい識別子になるので、これに注意してコードを書いていきましょう! 次に、下記のコードを見ていきましょう。
// Web3Mint.sol
constructor() ERC721 ("TanyaNFT", "TANYA") {
console.log("This is my NFT contract.");
}

ERC721モジュールを使用したconstructorを定義しています。 ここでは任意のNFTトークンの名前とシンボルを引数として渡しています。

node_modulesの中にあるERC721.solを見ればわかるのですが、継承したcontractのconstructorが引数を持っていたらこのように引数を渡してあげます。

  • TanyaNFT: NFTトークンの名前
  • TANYA: NFTトークンのそのシンボル

次に、下記のmakeAnEpicNFT関数を段階的に見ていきましょう。

// Web3Mint.sol
// ユーザーが NFT を取得するために実行する関数です。
function makeAnEpicNFT() public {
// 現在の tokenId を取得します。tokenId は 0 から始まります。
uint256 newItemId = _tokenIds.current();
// msg.sender を使って NFT を送信者に Mint します。
_safeMint(msg.sender, newItemId);
// NFT データを設定します。
_setTokenURI(newItemId, "Valuable data!");
// 次の NFT が Mint されるときのカウンターをインクリメントする。
_tokenIds.increment();
}

まず、下記のコードを見ていきます。

// Web3Mint.sol
uint256 newItemId = _tokenIds.current();

_tokenIdsについて理解を深めましょう。 Project2でも同じような解説が乗っていたと思いますが、これは重要なのでもう一度説明します。ピカソの例を思い出してください。

彼は、自分のNFTコレクションに、Sketch #1からSketch #100までのユニークな識別子を付与していました。

ここでは、ピカソがしたように、_tokenIdsを使用してNFTに対して一意の識別子を付与し、トラッキングできるようにしています。

_tokenIdsは単なる数字です。 したがって、 makeAnEpicNFT関数が初めて呼び出されたとき、 newItemIdは0になります。

  • Counters.Counter private _tokenIdsにより初期化されているため、newItemIdは0です。

もう一度実行すると、newItemIdは1になり、以下同様に続きます。 _tokenIds状態変数です。つまり、変更すると、値はコントラクトに直接保存されます。

次に、下記のコードを見ていきましょう。

// Web3Mint.sol
_safeMint(msg.sender, newItemId);

ここでは、コントラクトを呼び出したユーザー(= msg.sender)のアドレスに、ID(= newItemId)の付与されたNFTをMintしています。

ざっくりと解説した、NFTの識別子をmint先のユーザーと結びつける行為ですね。 msg.senderはSolidityが提供している変数であり、あなたのスマートコントラクトを呼び出したユーザーのパブリックアドレスを取得するために使用されます。

これは、ユーザーのパブリックアドレスを取得するための安全な方法です。

  • ユーザーはパブリックアドレスを使用して、コントラクトを呼び出す必要があります。
  • これは、「サインイン」のような認証機能の役割を果たします。

次に、下記のコードを見ていきましょう。

// Web3Mint.sol
_setTokenURI(newItemId, "Valuable data!");

これにより、NFTの一意の識別子と、その一意の識別子に関連付けられたデータが紐付けられます。

  • NFTを価値あるものにするのは、文字通り「NFTの一意の識別子」と「実際のデータ」を紐付ける必要があります。 今は、Valuable data!という文字列が、「実際のデータ」として設定されていますが、これは後で変更します。
  • Valuable data!は、ERC721の基準に準拠していません。
  • Valuable data!と入れ替わるtokenURIについてこれから学んでいきます。

最後に、下記のコードを見ていきましょう。

// Web3Mint.sol
_tokenIds.increment();

NFTが発行された後、_tokenIds.increment()(= OpenZeppelinが提供する関数)を使用して、tokenIdsをインクリメント(= +1)しています。 これにより、毎回NFTが発行されると、異なるtokenIds識別子がNFTと紐付けられます。 複数のユーザーが、同じtokenIdsを持つことはありません。

🎟 tokenURIを取得して、コントラクトをローカル環境で実行しよう

tokenURIは、NFT データが保存されている場所を意味します。 tokenURIは、下記のような「メタデータ」と呼ばれるJSONファイルにリンクされています。

{
"name": "Tanya",
"description": "A mindful creature. Just woke up like this.",
"image": "https://i.imgur.com/t1fye4S.jpg"
}

メタデータの構造に注意してください。

  • "name": デジタルデータの名前
  • "description": デジタルデータの説明
  • "image": デジタルデータへのリンク メタデータの構造が OpenSea の要件 と一致しない場合、デジタルデータはOpenSea上で正しく表示されません。
  • Openseaは、ERC721のメタデータ規格をサポートしています。
  • 音声ファイル、動画ファイル、3Dメディアなどに対応するメタデータ構造に関しては、OpenSea の要件 を参照してください。
  • 上記のTanyaのJSONメタデータをコピーして、 ここのWebサイトに貼り付けてください。

このWebサイトは、JSONデータをホストするのに便利です。

このレッスンでは、NFTデータを保持するために使用します。

下図のように、Webサイトにメタデータを貼り付けて、Saveボタンをクリックすると、JSONファイルへのリンクが表示されます。

枠で囲んだ部分をコピーして、ブラウザに貼り付け、メタデータがリンクとして保存されていることを確認しましょう。

こちらは、私のリンクです: https://jsonkeeper.com/b/OLSM

🐱 オリジナルの画像を使用する方法

Imgur というWebサイトを使うと、無料で画像をオンライン上でホストできます。

メタデータの"image"にリンクを貼り付ける場合は、Direct Linkを使用するようにしてください。

下記にImgurで画像をアップロードした際に選択するDirect Linkの取得方法を示します。

ぜひ自分のお気に入りの画像を使って、自分だけのメタデータを作成してみましょう。

🐈 Web3Mint.solを更新する

それでは、スマートコントラクトに向かい、下記の行を変更しましょう。

_setTokenURI(newItemId, "Valuable data!");

先ほど取得したJSONファイルへのリンクこそ、tokenURI(= NFT データが保存されている場所)です。 そのリンクを下記に貼り付けましょう。

_setTokenURI(
newItemId,
"こちらに、JSON ファイルへのリンクを貼り付けてください"
);

🎉 NFT をローカルネットワークにデプロイしよう

ここから、実際にmakeAnEpicNFT()関数を呼び出し、スマートコントラクトが問題なくデプロイされるかテストしていきます。 テスト用のプログラムrun.jsファイルを下記のように変更しましょう。

const main = async () => {
const nftContractFactory = await hre.ethers.getContractFactory("Web3Mint");
const nftContract = await nftContractFactory.deploy();
await nftContract.deployed();
console.log("Contract deployed to:", nftContract.address);
// makeAnEpicNFT 関数を呼び出す。NFT が Mint される。
let txn = await nftContract.makeAnEpicNFT();
// Minting が仮想マイナーにより、承認されるのを待つ。
await txn.wait();
// makeAnEpicNFT 関数をもう一度呼び出す。NFT がまた Mint される。
txn = await nftContract.makeAnEpicNFT();
// Minting が仮想マイナーにより、承認されるのを待つ。
await txn.wait();
};

const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};

runMain();

上記をrun.jsに反映させえたら、下記をターミナル上で実行しましょう。

yarn contract run:script

エラーが発生した場合は、pwdを実行して、 ETH-NFT-Makerディレクトリにいることを確認して、もう一度上記のコードを実行してみてください。

2回ミントが実行されることを確認できるはずです! 以下、出力結果のサンプルです。

Compiled 13 Solidity files successfully
This is my NFT contract.
Contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
An NFT w/ ID 0 has been minted to 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
An NFT w/ ID 1 has been minted to 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266

現在、ユーザーがこのスマートコントラクトにアクセスしてNFTを発行するたび、データは常に同じTanyaです!  🐱。 それでは、テストネットにWeb3Mint.solコントラクトをデプロイしましょう。

🛫 テストネットへコントラクトをデプロイする

これから、テストネットにあなたのスマートコントラクトをデプロイしていきます。 これが成功すると、NFT をオンラインで表示できるようになり、 世界中のユーザーがあなたのNFTをから見ることができます。

✅ テストネットにデプロイするにあたって必要なこと

こちらをよむと、スマートコントラクトをデプロイするにあたって必要なものが4つあることがわかります。 まずは、コントラクトのバイトコードです、これはhardhatが生成してくれます。 2つ目はデプロイするために、ETHがかかるため、そのETHを払うためのウォレットです。これはmetamaskを使って、解決します。 3つ目は、デプロイするためのスクリプトです。これも今から書きます。 そして最後は、イーサリアムのノードにアクセスすることです。これは自分でノードを立ててもいいのですが、それは少し面倒なため、APIを使ってノードを叩けるようにしてくれているAlchemyのAPIを使います。

💳 トランザクションについて

イーサリアムネットワーク上でブロックチェーンに新しく情報を書き込むことを、トランザクションと呼びます。 新しくスマートコントラクトをイーサリアムネットワークにデプロイしたという情報をブロックチェーン上に書き込むこともトランザクションです。 トランザクションにはマイナーの承認が必要ですので、Alchemyを導入します。 Alchemyは、世界中のトランザクションを一元化し、マイナーの承認を促進するプラットフォームです。 こちら からAlchemyのアカウントを作成してください。

💎 Alchemy でネットワークを作成する

Alchemyのアカウントを作成したら、CREATE APPボタンを押してください。 次に、下記の項目を埋めていきます。下図を参考にしてください。

  • NAME : プロジェクトの名前(例: Web3NFT)
  • DESCRIPTION : プロジェクトの概要(任意)
  • CHAIN : Ethereumを選択。
  • NETWORK : Sepoliaを選択。 それから、作成したAppのVIEW DETAILSをクリックします。 プロジェクトを開いたら、VIEW KEYボタンをクリックします。 ポップアップが開くので、HTTPのリンクをコピーしてください。 これがあなたが本番環境のネットワークに接続する際に使用するAPI Keyになります。
  • API Keyは、後で必要になるので、あなたの PC 上のわかりやすいところに、メモとして残しておいてください。

🐣 テストネットとは?

今回のプロジェクトでは、コスト(= 本物のETH)が発生するイーサリアムメインネットではなく、テストネットにコントラクトをデプロイします。 テストネットはイーサリアムメインネットを模しています。

  • イーサリアムメインネットにコントラクトをデプロイした際に発生するイベントのテストを行うのに最適です。
  • テストネットは偽のETHを使用しているため、いくらでもトランザクションのテストを行えます。 今回は、以下のイベントをテストしていきます。
  1. トランザクションの発生を世界中のマイナーたちに知らせる
  2. あるマイナーがトランザクションを発見する
  3. そのマイナーがトランザクションを承認する
  4. そのマイナーがトランザクションを承認したことをほかのマイナーたちに知らせ、トランザクションのコピーを更新する

🚰 偽の ETH を取得する

今回は、Sepoliaというイーサリアム財団によって運営されているテストネットを使用します。 Sepoliaにコントラクトをデプロイし、コードのテストを行うために、偽のETHを取得しましょう。 ユーザーが偽のETHを取得するために用意されたインフラは、「フォーセット(=蛇口)」と呼ばれています。 フォーセットを使用する前に、あなたのMetaMaskウォレットをSepolia Test Networkに設定してください。

✍️: MetaMask でSepolia Test Networkを設定する方法

1 . MetaMask ウォレットのネットワークトグルを開く。

2 . Show/hide test networksをクリック。

3 . Show test networksONにする。

4 . Sepolia Test Networkを選択する。

MetaMask ウォレットにSepolia Test Networkが設定されたら、下記のリンクの中から条件に合うものを選んで、少量の偽 ETH を取得しましょう。

  • Alchemy - 1 Sepolia ETH(24時間に1度もらうことができる)
    • ウォレットアドレスを入力してSend Me ETHボタンを押下するとその場でもらえます。

🚀 deploy.jsファイルを作成する

run.jsは、あくまでローカル環境でコードのテストを行うためのスクリプトでした。

テストネットにコントラクトをデプロイするために、scriptsディレクトリの中にあるdeploy.jsを以下のとおり更新します。

const main = async () => {
// コントラクトがコンパイルします
// コントラクトを扱うために必要なファイルが `artifacts` ディレクトリの直下に生成されます。
const nftContractFactory = await hre.ethers.getContractFactory("Web3Mint");
// Hardhat がローカルの Ethereum ネットワークを作成します。
const nftContract = await nftContractFactory.deploy();
// コントラクトが Mint され、ローカルのブロックチェーンにデプロイされるまで待ちます。
await nftContract.deployed();
console.log("Contract deployed to:", nftContract.address);
// makeAnEpicNFT 関数を呼び出す。NFT が Mint される。
let txn = await nftContract.makeAnEpicNFT();
// Minting が仮想マイナーにより、承認されるのを待ちます。
await txn.wait();
console.log("Minted NFT #1");
// makeAnEpicNFT 関数をもう一度呼び出します。NFT がまた Mint されます。
txn = await nftContract.makeAnEpicNFT();
// Minting が仮想マイナーにより、承認されるのを待ちます。
await txn.wait();
console.log("Minted NFT #2");
};

// エラー処理を行っています。
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};

runMain();

📈 Sepolia Test Network に コントラクトをデプロイしましょう

hardhat.config.jsファイルを変更する必要があります。 これは、スマートコントラクトプロジェクトのルートディレクトリにあります。

  • 今回は、contractディレクトリの直下にhardhat.config.jsが存在するはずです。 例)contractlsを実行した結果
README.md			package-lock.json
artifacts package.json
cache scripts
contracts test
hardhat.config.js

下記のように、hardhat.config.jsの中身を更新します。

require("@nomicfoundation/hardhat-toolbox");
module.exports = {
solidity: "0.8.17",
networks: {
sepolia: {
url: "YOUR_ALCHEMY_API_URL",
accounts: ["YOUR_PRIVATE_SEPOLIA_ACCOUNT_KEY"],
},
},
};

次に、YOUR_ALCHEMY_API_URLYOUR_PRIVATE_SEPOLIA_ACCOUNT_KEYを取得して、hardhat.config.jsに貼り付けましょう。 1. YOUR_ALCHEMY_API_URLの取得

hardhat.config.jsYOUR_ALCHEMY_API_URLの部分を先ほど取得した Alchemy の URL( HTTPリンク) と入れ替えます。 2. YOUR_PRIVATE_SEPOLIA_ACCOUNT_KEYの取得 1. お使いのブラウザから、MetaMask プラグインをクリックして、ネットワークをSepolia Test Networkに変更します。

2. それから、Account detailsを選択してください。

3. Account detailsからExport Private Keyをクリックしてください。

4. MetaMask のパスワードを求められるので、入力したらConfirmを推します。

5. あなたの秘密鍵(= Private Key )が表示されるので、クリックしてコピーします。

hardhat.config.jsYOUR_PRIVATE_SEPOLIA_ACCOUNT_KEYの部分をここで取得した秘密鍵とを入れ替えます。 ⚠️: 注意

hardhat.config.jsファイルを Github にコミットしないでください。

hardhat.config.jsファイルは、あなたのウォレットの秘密鍵を保持しており、他の人が見れる場所に保存するのは、大変危険です。 下記を実行して、VS Code で.gitignoreファイルを編集しましょう。

code .gitignore

.gitignorehardhat.config.jsの行を追加します。 .gitignoreの中身が下記のようになっていれば、問題ありません。

node_modules
.env
coverage
coverage.json
typechain
typechain-types

#Hardhat files
cache
artifacts
hardhat.config.js

.gitignoreに記載されているファイルやディレクトリは、GitHubにディレクトリをプッシュされずに、ローカル環境にのみ保存されます。

✍️: スマートコントラクトをデプロイするのに秘密鍵が必要な理由 > 新らしくスマートコントラクトをイーサリアムネットワーク上にデプロイすることも、トランザクションの一つです。

トランザクションを行うためには、ブロックチェーンに「ログイン」する必要があります。

「ログイン」には下記の情報が必要となります。

  • ユーザー名: 公開アドレス

  • パスワード: 秘密鍵

ユーザー名とパスワードを使用して、AWS にログインしてプロジェクトをデプロイするのと同じです。

⭐️ 実行する

構成のセットアップが完了すると、前に作成したデプロイスクリプトを使用してデプロイするように設定されます。 ルートディレクトリからこのコマンドを実行します 。

yarn contract deploy

deploy.jsを実行すると、実際にNFTを作成します。 このプロセスは、約20〜40秒かかります。 下記のような結果がターミナルに出力されば、成功です。

Contract deployed to: 0x8F315ec3999874A676bbC9323cE31F3d242a4bC7
Minted NFT #1
Minted NFT #2

👀 Etherscan でトランザクションを確認する

ターミナルに出力されたContract deployed toに続くアドレスを、Etherscan に貼り付けて、あなたのスマートコントラクトのトランザクション履歴を見てみましょう。 Etherscanは、イーサリアムネットワーク上のトランザクションに関する情報を確認するのに便利なプラットフォームです。 表示されるまでに約 1 分かかる場合があります。

🖼 NFT をオンラインで確認しよう

作成したNFTは、gemcase(NFT で閲覧できるサービス)で確認できます。

ターミナルに出力されたContract deployed toに続くアドレスを検索してみましょう。 Enterをクリックしないように注意してください。検索でコレクションが表示されたら、コレクション自体をクリックしてください 。 ⚠️: gemcaseでNFTを確認するのに時間が掛かる場合があります。 続いて、ターミナルに出力されたContract deployed toに続くアドレスを検索してみましょう。 TanyaNFTをクリックしてみましょう。 コレクションがgemcaseに表示されているのを確認してください。 私が作成したTanyaコレクションのtokenID 1番のリンクは[こちら]https://gemcase.vercel.app/view/evm/sepolia/0x677fcCF5F8be725ad8A9C23622ba6B738A2DED27/1になります(リンク先は、学習コンテンツ制作時に使用したRinkebyになっていますが、Rinkebyの箇所がSEPOLIAでも同様に表示されます)。 リンクの内容は以下のようになります。

https://gemcase.vercel.app/view/evm/sepolia/0x677fcCF5F8be725ad8A9C23622ba6B738A2DED27/1

中身を見ていきましょう。 0x67cd3f53c20e3a6211458dd5b7465e1f9464531cは、Web3Mintコントラクトのデプロイ先のアドレスです。 0は、tokenIDが0番であることを意味しています。 上記のリンクを共有すれば、誰でもあなたのNFTをオンライン上で見ることができます。

🙋‍♂️ 質問する

ここまでの作業で何かわからないことがある場合は、Discordの#ethereumで質問をしてください。 ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨

1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット

おめでとうございます! セクション1が終了しました! OpenSeaのリンクを#ethereumを貼り付けて、コミュニティにあなたのNFTをシェアしてください 😊 どんなNFTなのか気になります ✨ オンラインであなたのNFTを確認したら、次のレッスンに進みましょう 🎉