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を使用しているため、いくらでもトランザクションのテストを行えます。 今回は、以下のイベントをテストしていきます。
- トランザクションの発生を世界中のマイナーたちに知らせる
- あるマイナーがトランザクションを発見する
- そのマイナーがトランザクションを承認する
- そのマイナーがトランザクションを承認したことをほかのマイナーたちに知らせ、トランザクションのコピーを更新する
🚰 偽の ETH を取得する
今回は、Sepolia
というイーサリアム財団によって運営されているテストネットを使用します。
Sepolia
にコントラクトをデプロイし、コードのテストを行うために、偽のETHを取得しましょう。
ユーザーが偽のETHを取得するために用意されたインフラは、「フォーセット(=蛇口)」と呼ばれています。
フォーセットを使用する前に、あなたのMetaMaskウォレットをSepolia Test Network
に設定してください。
✍️: MetaMask で
Sepolia Test Network
を設定する方法1 . MetaMask ウォレットのネットワークトグルを開く。
2 .
Show/hide test networks
をクリック。3 .
Show test networks
をON
にする。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
が存在するはずです。 例)contract
でls
を実行した結果
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_URL
とYOUR_PRIVATE_SEPOLIA_ACCOUNT_KEY
を取得して、hardhat.config.js
に貼り付けましょう。
1. YOUR_ALCHEMY_API_URL
の取得
hardhat.config.js
のYOUR_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.js
のYOUR_PRIVATE_SEPOLIA_ACCOUNT_KEY
の部分をここで取得した秘密鍵とを入れ替えます。 ⚠️: 注意
hardhat.config.js
ファイルを Github にコミットしないでください。
hardhat.config.js
ファイルは、あなたのウォレットの秘密鍵を保持しており、他の人が見れる場所に保存するのは、大変危険です。 下記を実行して、VS Code で.gitignore
ファイルを編集しましょう。
code .gitignore
.gitignore
にhardhat.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