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ファイルへのリンクが表示されます。
枠で囲んだ部分をコピーして、ブラウザに貼り付け、メタデータがリンクとして保存されていることを確認しましょう。