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

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

🪄 NFT を作成しよう

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

下記のように、MyEpicNFT.solを更新しましょう。

// MyEpicNFT.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
// いくつかの OpenZeppelin のコントラクトをインポートします。
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "hardhat/console.sol";

// インポートした OpenZeppelin のコントラクトを継承しています。
// 継承したコントラクトのメソッドにアクセスできるようになります。
contract MyEpicNFT 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 {

// NFT が Mint されるときのカウンターをインクリメントします。
_tokenIds.increment();

// 現在のtokenIdを取得します。tokenIdは1から始まります。
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);
}
}

1行ずつコードを見ていきましょう。

// MyEpicNFT.sol
contract MyEpicNFT is ERC721URIStorage {
:

ここでは、コントラクトを宣言する際に、is ERC721URIStorageを使用してOpenZeppelinのコントラクトを「継承」しています。

継承とは、OpenZepplin等のイーサリアムに関する開発を便利にするフレームワークを提供するライブラリから、スマートコントラクトに必要なモジュールを呼び出すことを意味します。

これは関数をインポートするようなイメージで理解してください。

NFTのモジュールはERC721として知られています。

このモジュールには、NFT発行に必要な標準機能が含まれているため、開発者は独自のロジックを記述してカスタマイズするところに集中できます。

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

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

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

これにより、トラッキングの際に起こりうるオーバーフローを防ぎます。

💡 オーバーフローとは?

例えば、8 ビットの情報量しか持てないuint8があるとします。つまり、格納できる最大の数値は 2 進数の 11111111(10 進数では、2^8 - 1 = 255 )です。 次のコードを見てください。最後の number は何に等しいでしょうか?

uint8 number = 255;
number++;

この場合、オーバーフローを起こしたので、numberは増加したにもかかわらず、実は 0 になっています。( 2 進数の 11111111 に 1 を加えると、時計が 23 時 59 分 から 00 時 00 分 に戻るように、00000000 にリセットされます)。

アンダーフローも同様で、0 に等しいuint8から 1 を引くと、255 になります( uintは符号なしなので、負にすることはできないからです)。

ここではuint8を使用していませんし、uint256が毎回 1 ずつ増加するときにオーバーフローする可能性は低いと思われますが( 2^256は本当に大きな数です)、将来的に DApp が予期せぬ動作をすることがないように、コントラクトに保護規定を設けることはグッドプラクティスです!👍

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

// MyEpicNFT.sol
Counters.Counter private _tokenIds;

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

  • _tokenIdsの初期値は0です。

tokenIdはNFTの一意な識別子で、0, 1, 2, .. Nのように付与されます。

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

// MyEpicNFT.sol
constructor() ERC721 ("TanyaNFT", "TANYA") {
console.log("This is my NFT contract.");
}

ERC721モジュールを使用したconstructorを定義しています。

ここでは任意のNFTトークンの名前とシンボルを引数として渡しています。

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

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

// MyEpicNFT.sol
// ユーザーが NFT を取得するために実行する関数です。
function makeAnEpicNFT() public {
// NFT が Mint されるときのカウンターをインクリメントします。
_tokenIds.increment();
// 現在のtokenIdを取得します。tokenIdは1から始まります。
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);
}

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

// MyEpicNFT.sol
_tokenIds.increment();

_tokenIds.increment()(= OpenZeppelinが提供する関数)を使用して、tokenIdsをインクリメント(= +1)しています。

これにより、毎回NFTが発行されると、異なるtokenIds識別子がNFTと紐付けられます。

複数のユーザーが、同じtokenIdsを持つことはありません。

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

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

_tokenIdsについて理解を深めましょう。

ピカソの例を覚えているでしょうか?

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

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

_tokenIdsは単なる数字です。

したがって、 makeAnEpicNFT関数が初めて呼び出されたとき、 newItemIdは1になります。

  • Counters.Counter private _tokenIdsにより初期化し、makeAnEpicNFT関数の最初でインクリメントされているため、newItemIdは1です。

もう一度実行すると、newItemIdは2になり、以下同様に続きます。

_tokenIds状態変数です。つまり、変更すると、値はコントラクトに直接保存されます。

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

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

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

msg.senderはSolidityが提供している変数であり、あなたのスマートコントラクトを呼び出したユーザーのパブリックアドレスを取得するために使用されます。

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

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

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

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

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

  • NFTを価値あるものにするのは、文字通り「NFTの一意の識別子」と「実際のデータ」を紐付ける必要があります。

今は、Valuable data!という文字列が、「実際のデータ」として設定されていますが、これは後で変更します。

  • Valuable data!は、ERC721の基準に準拠していません。
  • Valuable data!と入れ替わるtokenURIについてこれから学んでいきます。

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

console.log("An NFT w/ ID %s has been minted to %s", newItemId, msg.sender);

ここでは、NFTがいつ誰に作成されたかを確認しています。

🎟 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": デジタルデータへのリンク

メタデータの構造が ERC721のメタデータ規格 と一致しない場合、デジタルデータはOpenSeaや今回使用するNFTビュアーのgemcaseでは正しく表示されません。

  • Openseaは、ERC721のメタデータ規格をサポートしています。
  • 音声ファイル、動画ファイル、3Dメディアなどに対応するメタデータ構造に関しては、OpenSea の要件 を参照してください。

上記のTanyaのJSONメタデータをコピーして、 こちら のWebサイトに貼り付けてください。

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

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

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

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

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

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

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

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

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

🐈 MyEpicNFT.solを更新する

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

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

先ほど取得したJSONファイルへのリンクこそ、tokenURI(= NFT データが保存されている場所)です。

そのリンクを下記に貼り付けましょう。

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

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

ここから、実際にmakeAnEpicNFT()関数を呼び出し、スマートコントラクトが問題なくデプロイされるかテストしていきます。

scripts/deploy.jsファイルを下記のように変更しましょう。

// deploy.js
async function main() {
const nftContractFactory = await hre.ethers.getContractFactory("MyEpicNFT");
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();
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

上記をdeploy.jsに反映させたら、ターミナル上でETH-NFT-Collectionディレクトリ直下にいることを確認し下記を実行しましょう。

yarn contract deploy

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

下記のような結果が、ターミナルに出力されれば、テストは成功です。

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

現在、ユーザーがこのスマートコントラクトにアクセスしてNFTを発行するたび、データは常に同じTanyaです!  🐱

次のセクションでは、NFTを作成するすべての人がランダムで一意のNFTを取得できるようにする方法を学習していきます。

それでは、テストネットにMyEpicNFT.solコントラクトをデプロイしましょう。

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

これから、テストネットにあなたのスマートコントラクトをデプロイしていきます。

これが成功すると、NFT をオンラインで表示できるようになり、 世界中のユーザーがあなたのNFTを見ることができます。今回は、gemcaseというNFTビュアーを使用してオンライン上で確認してみたいと思います。

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

イーサリアムネットワーク上でブロックチェーンに新しく情報を書き込むことを、トランザクションと呼びます。

新しくスマートコントラクトをイーサリアムネットワークにデプロイしたという情報をブロックチェーン上に書き込むこともトランザクションです。

トランザクションにはマイナーの承認が必要ですので、Alchemyを導入します。

Alchemyは、世界中のトランザクションを一元化し、マイナーの承認を促進するプラットフォームです。

こちら からAlchemyのアカウントを作成してください。

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

Alchemyのアカウントを作成したら、CREATE APPボタンを押してください。

次に、下記の項目を埋めていきます。下図を参考にしてください。

  • NAME : プロジェクトの名前(例: MyEpicNFT)
  • DESCRIPTION : プロジェクトの概要(任意)
  • CHAIN : Ethereumを選択
  • NETWORK : Sepoliaを選択

それから、作成したAppのVIEW DETAILSをクリックします。

プロジェクトを開いたら、VIEW KEYボタンをクリックします。

ポップアップが開くので、HTTPSのリンクをコピーしてください。

これがあなたが本番環境のネットワークに接続する際に使用する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ボタンを押下するとその場でもらえます。
  • Chainlink - 0.1 test ETH(その場でもらえる)
    • Connect walletをクリックしてMetaMaskと接続する必要があります。
    • Twitterアカウントを連携する必要があります。

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

hardhat.config.jsファイルを変更する必要があります。

これは、スマートコントラクトプロジェクトのルートディレクトリにあります。

  • 今回は、packages/contractディレクトリの直下にhardhat.config.jsが存在するはずです。

例)contractlsを実行した結果

README.md			hardhat.config.js
artifacts package.json
cache scripts
contracts test

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

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

module.exports = {
solidity: "0.8.18",
defaultNetwork: "hardhat",
networks: {
sepolia: {
url: "YOUR_ALCHEMY_API_URL" || "",
accounts: "YOUR_PRIVATE_ACCOUNT_KEY" ? ["YOUR_PRIVATE_ACCOUNT_KEY"] : [],
},
},
};

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

1. YOUR_ALCHEMY_API_URLの取得

hardhat.config.jsYOUR_ALCHEMY_API_URLの部分を先ほど取得した Alchemy の URL( HTTPSリンク) と入れ替えます。

2. YOUR_PRIVATE_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_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 にログインしてプロジェクトをデプロイするのと同じです。

⭐️ 実行する

構成のセットアップが完了すると、前に作成したデプロイスクリプトを使用してデプロイするように設定されます。

ETH-NFT-Collectionディレクトリ直下でこのコマンドを実行します。

yarn contract deploy:sepolia

deploy.jsを実行すると、実際にNFTを作成します。 このプロセスは、約20〜40秒かかります。

下記のような結果がターミナルに出力されば、成功です。

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

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

ターミナルに出力されたContract deployed toに続くアドレスを、Etherscan に貼り付けて、あなたのスマートコントラクトのトランザクション履歴を見てみましょう。

Etherscanは、イーサリアムネットワーク上のトランザクションに関する情報を確認するのに便利なプラットフォームです。

表示されるまでに約 1 分かかり場合があります。

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

それでは、作成したNFTをNFTビュアーで確認してみましょう。

gemcase にアクセスし、フォームに情報を入力します。

  • Blockchain : EVM
  • Chain : Sepolia Testnet
  • Address : ターミナルに出力されたContract deployed toに続くアドレス
  • Token ID : 1

TanyaNFTが表示されたら、Viewをクリックします。

ミントをしたNFTの情報が表示されます。

画面を下にスクロールをすると、現在ミントされているNFT一覧が確認できます。ここでは、2つのNFT画像が表示されています。デプロイ時に2回ミントを行ったため、Token ID 1と2のNFT画像が確認できます。

私が作成したTanyaコレクションのtokenID 1番のリンクはこちらになります。

リンクの内容は以下のようになります。

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

中身を見ていきましょう。

0x8F561C94CC0c6C2771052d10980937804CD53Cd6は、MyEpicNFTコントラクトのデプロイ先のアドレスです。

1は、tokenIDが1番であることを意味しています。

上記のリンクを共有すれば、誰でもあなたのNFTをオンライン上で見ることができます。

🙋‍♂️ 質問する

ここまでの作業で何かわからないことがある場合は、Discordの#ethereumで質問をしてください。

ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨

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

おめでとうございます! セクション1が終了しました!

gemcaseのリンクを#ethereumに貼り付けて、コミュニティにあなたのNFTをシェアしてください 😊

どんなNFTなのか気になります ✨

オンラインであなたのNFTを確認したら、次のレッスンに進みましょう 🎉