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がいつ誰に作成されたかを確認しています。