lesson-1_スマートコントラクトを作ろう
🖋 コントラクトを作成する
これから、ETHとガス代を支払うことで、誰でもNFTをMintできるスマートコントラクトをSolidityで作成していきます。
- ここで作成するスマートコントラクトは、後でユースケースに合わせて自由に変更できます。
packages/contract/contracts
ディレクトリの下にNFTCollectible.sol
という名前のファイルを作成します。
Hardhatを使用する場合、ファイル構造は非常に重要ですので、注意する必要があります。ファイル構造が下記のようになっていれば大丈夫です 😊
packages
└── contract
└── contracts
└── NFTCollectible.sol
次に、コードエディタでプロジェクトのコードを開きます。
ここでは、VS Codeの使用をお勧めします。ダウンロードはこちらから。
VS Codeをターミナルから起動する方法はこちらをご覧ください。今後VS Codeを起動するのが一段と楽になるので、ぜひ導入してみてください。
コーディングのサポートツールとして、VS Code上でSolidityの拡張機能をダウンロードすることをお勧めします。ダウンロードは こちら から。
それでは、これからNFTCollectible.sol
の中身の作成していきます。NFTCollectible.sol
をVS Codeで開き、下記を入力します。
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "hardhat/console.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract NFTCollectible is ERC721Enumerable, Ownable {
using SafeMath for uint256;
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
}
コードを詳しくみていきましょう。
// SPDX-License-Identifier: MIT
これは「SPDXライセンス識別子」と呼ばれ、ソフトウェア・ライセンスの種類が一目でわかるようにするための識別子です。
pragma solidity ^0.8.17;
これは、コントラクトで使用するSolidityコンパイラのバージョンです。上記の場合「このコントラクトを実行するときは、Solidityコンパイラのバージョン0.8.17のみを使用し、それ以下のものは使用しません」という意味です。コンパイラのバージョンがhardhat.config.js
で同じであることを確認してください。
もし、hardhat.config.js
の中に記載されているSolidityのバージョンが0.8.17
でなかった場合は、NFTCollectible.sol
の中身をhardhat.config.js
に記載されているバージョンに変更しましょう。
import "hardhat/console.sol";
コントラクトを実行する際、コンソールログをターミナルに出力するためにHardhatのconsole.sol
のファイルをインポートしています。これは、今後スマートコントラクトのデバッグが発生した場合に、とても役立つツールです。
import "hardhat/console.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
contract NFTCollectible is ERC721Enumerable, Ownable {
using SafeMath for uint256;
using Counters for Counters.Counter;
Counters.Counter private _tokenIds;
ここでは、OpenZeppelinのERC721 EnumerableコントラクトとOwnableコントラクトを継承しています。
🗃 定数( constants
)と変数( variables
)を保存する
これからコントラクトに、特定の変数や定数を保存していきます。
NFTCollectible.sol
の中のCounters.Counter private _tokenIds;
の直下に以下のコードを追加しましょう。
uint public constant MAX_SUPPLY = 30;
uint public constant PRICE = 0.01 ether;
uint public constant MAX_PER_MINT = 3;
string public baseTokenURI;
まず、ここでは以下の3つを定数(constants
)として定義します。
1. NFT の供給量( MAX_SUPPLY
): コレクションでMint可能なNFTの最大数。
2. NFT の価格( PRICE
): NFTを購入するのにユーザーが支払うETHの額。
3. 1 取引あたりの最大 Mint 数( MAX_PER_MINT
): ユーザーが一度にMintできるNFTの上限限。
コントラクトがデプロイされたら、定数の中身を変更することはできません。
- これらコンスタントが取る値(
30
、0.01 ether
など)は、自由に変更できます。
それから、下記を変数として定義します。
Base Token URI( baseTokenURI
): JSONファイル(メタデータ)が格納されているフォルダのIPFS URL。
コントラクトの所有者(またはデプロイ先)が必要に応じてBase Token URIを変更できるように、これからbaseTokenURI
のセッタ関数を記述していきます。
✍️:
public
は Solidity のアクセス修飾子です。 Solidity のアクセス修飾子に関しては、こちら をご覧ください。
public
を含む、他のアクセス修飾子について詳しく説明しています。
🤖 コンストラクタ( constructor
)を記述する
コンストラクタ(constructor
)の呼び出して、baseTokenURI
を設定していきます。
🔩:
constructor
とはconstructor
はオプションの関数で、contract
の状態変数を初期化するために使用されます。これから詳しく説明していくので、constructor
に関しては、まず以下の特徴を理解してください。
contract
は 1 つのconstructor
しか持つことができません。
constructor
は、スマートコントラクトの作成時に一度だけ実行され、contract
の状態を初期化するために使用されます。
constructor
が実行された後、コードがブロックチェーンにデプロイされます。
NFTCollectible.sol
の中のstring public baseTokenURI;
の直下に以下のコードを追加しましょう。
constructor(string memory baseURI) ERC721("NFT Collectible", "NFTC") {
setBaseURI(baseURI);
}
setBaseURI(baseURI);
は、メタデータが存在する場所のBase Token URIを設定します。
この処理により、個々のNFTに対して手動でBase Token URIを設定する作業が軽減されます。
setBaseURI
関数については、後で詳しく説明します。
また、ERC721("NFT Collectible", "NFTC")
では、親コンストラクタ(ERC721
)を呼び出して、NFTコレクションの名前とシンボルを設定します。
-
NFTコレクションの名前:
"NFT Collectible"
-
NFTコレクションのシンボル:
"NFTC"
- NFTコレクシ ョンの名前とシンボルは任意で変更して大丈夫です 😊
🎟 いくつかの NFT を無料で配布する
スマートコントラクトは、一度ブロックチェーン上にデプロイしてしまうと中身を変更できません。
したがって、すべてのNFTを有料にすると、自分自身や友達、イベントの景品として無料でNFTを配布できなくなってしまいます。
なので今から、ある一定数(この場合は10個)のNFTをキープしておいて、ユーザーが無料でMintできる関数(reserveNFTs
)をコントラクトに実装していきます。
下記を、constructor
のコードブロック直下に追加しましょう。
function reserveNFTs() public onlyOwner {
uint totalMinted = _tokenIds.current();
require(totalMinted.add(10) < MAX_SUPPLY, "Not enough NFTs");
for (uint i = 0; i < 10; i++) {
_mintSingleNFT();
}
}
reserveNFTs
数を呼び出すユーザーは、ガス代だけ払えばよいので、onlyOwner
とマークして、コントラクトの所有者だけが呼び出せるようにします。
tokenIds.current()
を呼び出して、これまでにMintされたNFTの総数を確認します。
tokenId
は0
から始まり、NFTがMintされるごとに+1
されます。
次に、require
を使って、キープできるNFT(= 10個)がコレクションに残っているかどうかを確認します。
totalMinted.add(10) < MAX_SUPPLY
は、現在MintされようとしているtokenId
に+10
した数が、MAX_SUPPLY
(この場合は30
)を超えていないかチェックしています。
キープできるNFTがコレクションに残っていた場合、_mintSingleNFT
を10回呼び出し て10個のNFTをMintします。
_mintSingleNFT
関数については、後で詳しく説明します。
✍️:
Ownable
/onlyOwner
についてOwnable
は、OpenZeppelin が提供するコントラクトへのアクセス制御を提供するモジュールです。このモジュールは、コントラクトの継承によって使用されます。
onlyOwner
という修飾子を関数に適用することで、関数の使用をコントラクトの所有者に限定することができます。
🔗 Base Token URI を設定する
これから、Base Token URIを効率よくコントラクトに取得して、tokenIdと紐付ける関数を実装していきます。
前回のレッスンでIPFSに保存したJSONファイルを覚えていますか?
#0
番目のNFTコレクションのメタデータは、下記のエンドポイントを持つサーバーでホストされています。
https://gateway.pinata.cloud/ipfs/QmSvw119ALMN9SkP89Xj37jvqJik8jZrSjU5c1vgBhkhz8/0