lesson-5_ローカル環境でNFTをmintしよう
✨ NFT を Mint する
さて、キャラクターのデータが整ったので、次は実際にNFTをMintしていきましょう。
下記のように、MyEpicGame.sol
を更新してください。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// NFT発行のコントラクト ERC721.sol をインポートします。
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
//OpenZeppelinが提供するヘルパー機能をインポートします。
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "hardhat/console.sol";
// MyEpicGameコントラクトは、NFTの標準規格であるERC721を継承します。
contract MyEpicGame is ERC721 {
struct CharacterAttributes {
uint characterIndex;
string name;
string imageURI;
uint hp;
uint maxHp;
uint attackDamage;
}
//OpenZeppelin が提供する tokenIds を簡単に追跡するライブラリを呼び出しています。
using Counters for Counters.Counter;
// tokenIdはNFTの一意な識別子で、0, 1, 2, .. N のように付与されます。
Counters.Counter private _tokenIds;
// キャラクターのデフォルトデータを保持するための配列 defaultCharacters を作成します。それぞれの配列は、CharacterAttributes 型です。
CharacterAttributes[] defaultCharacters;
// NFTの tokenId と CharacterAttributes を紐づける mapping を作成します。
mapping(uint256 => CharacterAttributes) public nftHolderAttributes;
// ユーザーのアドレスと NFT の tokenId を紐づける mapping を作成しています。
mapping(address => uint256) public nftHolders;
constructor(
// プレイヤーが新しく NFT キャラクターを Mint する際に、キャラクターを初期化するために渡されるデータを設定しています。これらの値は フロントエンド(js ファイル)から渡されます。
string[] memory characterNames,
string[] memory characterImageURIs,
uint[] memory characterHp,
uint[] memory characterAttackDmg
)
// 作成するNFTの名前とそのシンボルをERC721規格に渡しています。
ERC721("OnePiece", "ONEPIECE")
{
// ゲームで扱う全てのキャラクターをループ処理で呼び出し、それぞれのキャラクターに付与されるデフォルト値をコントラクトに保存します。
// 後でNFTを作成する際に使用します。
for(uint i = 0; i < characterNames.length; i += 1) {
defaultCharacters.push(CharacterAttributes({
characterIndex: i,
name: characterNames[i],
imageURI: characterImageURIs[i],
hp: characterHp[i],
maxHp: characterHp[i],
attackDamage: characterAttackDmg[i]
}));
CharacterAttributes memory character = defaultCharacters[i];
// ハードハットのconsole.log()では、任意の順番で最大4つのパラメータを指定できます。
// 使用できるパラメータの種類: uint, string, bool, address
console.log("Done initializing %s w/ HP %s, img %s", character.name, character.hp, character.imageURI);
}
// 次の NFT が Mint されるときのカウンターをインクリメントします。
_tokenIds.increment();
}
// ユーザーは mintCharacterNFT 関数を呼び出して、NFT を Mint ことができます。
// _characterIndex は フロントエンドから送信されます。
function mintCharacterNFT(uint _characterIndex) external {
// 現在の tokenId を取得します(constructor内でインクリメントしているため、1から始まります)。
uint256 newItemId = _tokenIds.current();
// msg.sender でフロントエンドからユーザーのアドレスを取得して、NFT をユーザーに Mint します。
_safeMint(msg.sender, newItemId);
// mapping で定義した tokenId を CharacterAttributesに紐付けます。
nftHolderAttributes[newItemId] = CharacterAttributes({
characterIndex: _characterIndex,
name: defaultCharacters[_characterIndex].name,
imageURI: defaultCharacters[_characterIndex].imageURI,
hp: defaultCharacters[_characterIndex].hp,
maxHp: defaultCharacters[_characterIndex].maxHp,
attackDamage: defaultCharacters[_characterIndex].attackDamage
});
console.log("Minted NFT w/ tokenId %s and characterIndex %s", newItemId, _characterIndex);
// NFTの所有者を簡単に確認できるようにします。
nftHolders[msg.sender] = newItemId;
// 次に使用する人のためにtokenIdをインクリメントします。
_tokenIds.increment();
}
}
一行ずつ、更新されたコードを見ていきましょう。
// NFT発行のコントラクト ERC721.sol をインポートします。
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
//OpenZeppelinが提供するヘルパー機能をインポートします。
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
OpenZepplin は、イーサリアムネットワーク上の開発を便利にするフレームワークです。
OpenZeppelinは、NFTの標準規格を実装し、そのうえに独自のロジックを書いてカスタマイズできるライブラリを提供しています。ここでは、それらをMyEpicGame.sol
にインポートしています。
次に、下記のコードを見ていきましょう。
contract MyEpicGame is ERC721 {
ここでは、コントラクトを宣言する際に、is ERC721
を使用してOpenZeppelinのコントラクトを「継承」しています。
「継承」とは、OpenZeppelinのようなライブラリやほかのスマートコントラクトから、必要なモジュールを呼び出すことを意味します。
-
これは関数をインポートするよ うなイメージで理解してください。
-
NFTのモジュールは
ERC721
として知られています。 -
このモジュールには、NFTの発行に必要な標準機能が含まれているため、開発者は自分のコントラクトをカスタマイズすることに集中できます。
次に、下記のコードを見ていきましょう。
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 が予期せぬ動作をすることがないように、コントラクトに保護規定を設けることはグッとプラクティスです!👍
次に、下記のコードを見ていきましょう。
Counters.Counter private _tokenIds;
ここでは、private _tokenIds
を宣言して 、_tokenIds
を初期化しています。
_tokenIds
の初期値は0です。
tokenIdはNFTの一意な識別子で、0, 1, 2, .. Nのように付与されます。
次に、下記のコードを見ていきましょう。
mapping(uint256 => CharacterAttributes) public nftHolderAttributes;
nftHolderAttributes
はプレイヤーのNFTの状態を保存する変数になります。
ここでは、NFTのIDをCharacterAttributes
構造体にmapping
しています。
✍️:
mapping
について ここでは、mapping
と呼ばれる特別なデータ構造を使用しています。Solidity の
mapping
は、他の言語におけるハッシュテーブルや辞書のような役割を果たします。これらは、下記のように
_Key
と_Value
のペアの形式でデータを格納するために使用されます。例:
mapping(_Key=> _Value)public mappingName
今回は、NFTキャラクターのtokenId
(= _Key
= uint256
)をそのユーザ ーがMintするNFTのCharacterAttributes
(= _Value
)に関連付けるためにmapping
を使用しました。
-
nftHolderAttributes
という状態変数には、tokenId
とCharacterAttributes
構造体に格納されたデータが対になって保存されます。 -
コードの後半に、
nftHolderAttributes[newItemId] = CharacterAttributes({...})
という処理が記載されています。ここでは、現在のtokenId
であるnewItemId
をCharacterAttributes
構造体に紐付ける処理が行われています。- 後で詳しく解説します。
同じようにmapping
を使用している下記のコードを見ていきましょう。
mapping(address => uint256) public nftHolders;
ここでは、ユーザーのaddress
とtokenId
を紐づけるため、mapping
を使用しています。
-
nftHolders
という状態変数には、ユーザーのaddress
とtokenId
に格納されたデータが対になって保存されます。 -
コードの後半に、
nftHolders[msg.sender] = newItemId
という処理が記載されています。ここでは、msg.sender
(=フロントエンドから送信されるユーザーのaddress
)にnewItemId
に紐付ける処理が行われています。- 後で詳しく解説します。
次に下記のコードを見ていきましょう。
ERC721("OnePiece", "ONEPIECE");
ここでは、作成するNFTの名前("OnePiece"
)とそのシンボル("ONEPIECE"
)をERC721の規格に渡しています。
NFTはNon-Fungible "Token" の略であり、Tokenには、必ず名前とシンボルを付与する必要があります。
例:
- トークンの名前:Ethereum
- トークンのシンボル:ETH
次に、下記のコードを見ていきましょう。
_tokenIds.increment();
Solidityにおいて、すべての数は0
から始まるため、_tokenIds
の初期値は0
です。
ここでは_tokenIDs
に1
を加算しています。
constructor
の中で _tokenIDs
を1
にインクリメントするのは、1番目のtokenId
を1
とした方が、あとあと処理が楽になるためです。
increment()
関数に関しては、こちら を参照してください。
次に、mintCharacterNFT
関数の中身を見ていきましょう。
function mintCharacterNFT(uint _characterIndex) external {
この関数を呼び出すことにより、NFT の Mint が行われます。
_characterIndex
はフロントエンドから送信される変数です。
_characterIndex
をmintCharacterNFT
関数に渡すことで、プレイヤーがどのキャラクター(例:ナミ)を欲しいか、コントラクトに伝えます。
たとえば、mintCharacterNFT(1)
とすると、defaultCharacters[1]
のデータを持つキャラクターがMintされます。
次に下記のコードを見ていきましょう。
uint256 newItemId = _tokenIds.current();
ここでは、ユーザーが新しくNFTをMintする際に発行されるtokenID
を格納するために、newItemId
変数を定義してます。
これはNFT自体のIDです。
各NFTは「一意」であり、そのために各トークンに一意のIDを付与しています。
通常_tokenIds.current()
は0
から始まりますが、constructor
で_tokenIds.increment()
を行ったので、newItemId
は1
になります。
NFTの一意な識別子を追跡するために_tokenIds
を使用していますが、これは単なる数字です。
-
最初に
mintCharacterNFT
を呼び出すとnewItemId
は1
になり、もう一度呼び出すとnewItemId
は2
になり、これが繰り返されます。 -
newItemId
の値を変更すると、その値はグローバル変数のように直接コントラクトに格納され、メモリ上に永久に残ります。
次に、下記のコードを見ていきましょう。
_safeMint(msg.sender, newItemId);
上記が実行されると、newItemId
というIDのNFTキャラクターがmsg.sender
(=フロントエンドからユーザーのアドレス)に、Mintされます。
✍️:
msg.sender
についてmsg.sender
は Solidity が提供する 変数で、フロントエンドからコントラクトを呼び出したユーザーの 公開アドレス を保持した変数です。原則として、ユーザーは、コントラクトを匿名で呼び出すことはできません。
ユーザーは、フロントエンドからウォレット認証を行って、NFT を Mint する必要があります。
- これは、コントラクトへの「サインイン」機能のようなものです。
🎨 NFT のデータを更新する
引き続き、MyEpicGame.sol
の内容を見ていきます。
今回のWebアプリケーションゲームでは、ボスに攻撃されると、プレイヤーの保持するNFTキャラクターのHPが減少します。
例を見ていきましょう。
私が新しくNFTをMintした際、私のNFTキャラクターには以下のようなデフォルト値が与えられます。
{
characterIndex: 1,
name: "USOPP",
imageURI: "https://i.imgur.com/WVAaMPA.png",
hp: 200,
maxHp: 200,
attackDamage: 50
}
たとえば、私のキャラクターが攻撃を受けてHPが50減ったとします。
HPは200 → 150になります。
その値を下記のように、NFT上で変更する必要があります。
{
characterIndex: 1,
name: "USOPP",
imageURI: "https://i.imgur.com/WVAaMPA.png",
hp: 150, // 更新された値
maxHp: 200,
attackDamage: 50
}
⚠️: 注意
すべてのプレイヤーは、それぞれ自分のキャラクター NFT を持っており、それぞれの NFT がキャラクターの状態に関する固有のデータを保持しています。
このようなゲームの仕様を実装するために、コントラクトの中に、NFTキャラクターのHPが減ったことをデータとして保存するロジックを追加しました。
それでは、下記のコードを見ていきましょう。
nftHolderAttributes[newItemId] = CharacterAttributes({
characterIndex: _characterIndex,
name: defaultCharacters[_characterIndex].name,
imageURI: defaultCharacters[_characterIndex].imageURI,
hp: defaultCharacters[_characterIndex].hp,
maxHp: defaultCharacters[_characterIndex].maxHp,
attackDamage: defaultCharacters[_characterIndex].attackDamage,
});
ここでは、newItemId
というIDを持つNFTキャラクターの状態を、更新しています。
データを更新するために、NFTのtokenId
(= newItemId
)をCharacterAttributes
構造体にマップするnftHolderAttributes
変数を使用します。
これにより、プレイヤーのNFTに関連する値を簡単に更新できます。
-
プレイヤーが攻撃されて、NFTキャラクターの
hp
値が減ると、nftHolderAttributes
上でそのキャラクターのhp
値が更新されます。 -
この処理によって、プレイヤー固有のNFTデータをコントラクトに保存できます。
✍️:
mapping
を覚えていますか?// MyEpicGame.sol
mapping(uint256 => CharacterAttributes) public nftHolderAttributesここで、現在の
tokenId
(=newItemId
)をCharacterAttributes
構造体に紐づけるnftHolderAttributes
変数を定義しました。
nftHolderAttributes
はプレイヤーの NFT の状態を保存する変数になります。
NFTのメタデータは変更できないと思われがちですが、そんなことはありません。実はクリエイター次第なんです 😊
次に、下記 の処理を見ていきましょう。
nftHolders[msg.sender] = newItemId;
ここでは、ユーザーのパブリックウォレットアドレスをNFTのtokenI
(= newItemId
)にマップしています。
この処理によって、誰がどのNFTを所有しているかを簡単に追跡できます。
🦖: プレイヤーと NFT キャラクター
簡単のために、今回のプロジェクトでは、各プレイヤーはウォレットアドレスにつき 1 つの NFT キャラクターしか保有できないようになっています。
もし興味があれば、プレイヤーが複数のキャラクターを保持できるようにコントラクトを調整してみてください 😊
最後に、下記のコードを見ていきましょう。
_tokenIds.increment();
NFTをMintした後、OpenZeppelinが提供する関数_tokenIds.increment()
を使って_tokenIds
をインクリメントしています。
この処理によって、次回NFTをミントするユーザーには、新しいtokenId
が付与されます。すでにMintされたtokenId
は誰も持つことができません。
😳 ローカル環境でテストを実行する
次は、run.js
に、mintCharacterNFT
関数を呼び出す処理を追加していきます。
以下のコードをrun.js
のconsole.log
の直下に追加しましょう。
// 再代入可能な変数 txn を宣言
let txn;
// 3体のNFTキャラクターの中から、3番目のキャラクターを Mint しています。
txn = await gameContract.mintCharacterNFT(2);
// Minting が仮想マイナーにより、承認されるのを待ちます。
await txn.wait();
// NFTのURIの値を取得します。tokenURI は ERC721 から継承した関数です。
let returnedTokenUri = await gameContract.tokenURI(1);
console.log("Token URI:", returnedTokenUri);
コードを1行ずつ見ていきましょう。
// 再代入可能な変数 txn を宣言
let txn;
// 3体のNFTキャラクターの中から、3番目のキャラクターを Mint しています。
txn = await gameContract.mintCharacterNFT(2);
https://qiita.com/y-temp4/items/289686fbdde896d22b5e
ここでは、MyEpicGame.sol
からmintCharacterNFT
関数を呼び出して、3体のNFTキャラクターの中から、3番目のキャラクターをMintしています。
run.js
からmintCharacterNFT
関数を実行する際、Hardhatはあなたのローカル環境に設定された デフォルトウォレット をコントラクトに展開します。
- したがって、
MyEpicGame.sol
上では、Hardhatが提供したデフォルトウォレットのパブリックアドレスがmsg.sender
に格納されます。
次に、下記のコードを見ていきましょう。
// Minting が仮想マイナーにより、承認されるのを待ちます。
await txn.wait();
// NFTのURIの値を取得します。tokenURI は ERC721 から継承した関数です。
let returnedTokenUri = await gameContract.tokenURI(1);
console.log("Token URI:", returnedTokenUri);
tokenURI()
は、NFTにアタッチされている 実際のデータ を返す関数です。
gameContract.tokenURI(1)
が呼び出されると、returnedTokenUri
には、tokenId
= 1
のNFTキャラクターのデータ(キャラクターの名前、HPなど)が格納されます。
それでは、ターミナル上で下記を実行してみましょう。
yarn contract run:script
下記のような結果がターミナルに出力されていれば、テストは成功です。
Compiling 11 files with 0.8.17
Solidity compilation finished successfully
Done initializing ZORO w/ HP 100, img https://i.imgur.com/TZEhCTX.png
Done initializing USOPP w/ HP 200, img https://i.imgur.com/WVAaMPA.png
Done initializing USOPP w/ HP 300, img https://i.imgur.com/pCMZeiM.png
Contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Minted NFT w/ tokenId 1 and characterIndex 2
Token URI:
現在、NFTに実際のデータは添付されていないので、ターミナルには、Token URIが出力されていません。
これから、MyEpicGame.sol
のnftHolderAttributes
を更新して、tokenURI
を添付していきます。
⭐️ tokenURI
をセットアップする
tokenURI
には、NFTデータを JSON 形式で渡す必要があります。
まず、contract/contracts
ディレクトリの下にlibraries
というディレクトリを作成しましょう。
下記のディレクトリ構図を参考にしてください。
contract
|_ contracts
|_ libraries
libraries
ディレクトリにBase64.sol
という名前のファイルを作成し、下記のコードを貼り付けてください。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <brecht@loopring.org>
library Base64 {
bytes internal constant TABLE =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/// @notice Encodes some bytes to the base64 representation
function encode(bytes memory data) internal pure returns (string memory) {
uint256 len = data.length;
if (len == 0) return "";
// multiply by 4/3 rounded up
uint256 encodedLen = 4 * ((len + 2) / 3);
// Add some extra buffer at the end
bytes memory result = new bytes(encodedLen + 32);
bytes memory table = TABLE;
assembly {
let tablePtr := add(table, 1)
let resultPtr := add(result, 32)
for {
let i := 0
} lt(i, len) {
} {
i := add(i, 3)
let input := and(mload(add(data, i)), 0xffffff)
let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
out := shl(8, out)
out := add(
out,
and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF)
)
out := shl(8, out)
out := add(
out,
and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF)
)
out := shl(8, out)
out := add(
out,
and(mload(add(tablePtr, and(input, 0x3F))), 0xFF)
)
out := shl(224, out)
mstore(resultPtr, out)
resultPtr := add(resultPtr, 4)
}
switch mod(len, 3)
case 1 {
mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
}
case 2 {
mstore(sub(resultPtr, 1), shl(248, 0x3d))
}
mstore(result, encodedLen)
}
return string(result);
}
}
このファイルには、SVGとJSONをBase64に変換するための関数が含まれています。
次に、MyEpicGame.sol
にBase64.sol
をインポートするコードを追加します。
下記のコードを、MyEpicGame.sol
の先頭付近(ライブラリをインポートしているコードブロックの近く)に、追加してください。
// Base64.sol からヘルパー関数をインポートする。
import "./libraries/Base64.sol";
次に、MyEpicGame
コントラクトの中に、tokenURI
という関数を記述します。
mintCharacterNFT
関数の下に、下記の関数を追加してください。
// nftHolderAttributes を更新して、tokenURI を添付する関数を作成
function tokenURI(uint256 _tokenId) public view override returns (string memory) {
CharacterAttributes memory charAttributes = nftHolderAttributes[_tokenId];
// charAttributes のデータ編集して、JSON の構造に合わせた変数に格納しています。
string memory strHp = Strings.toString(charAttributes.hp);
string memory strMaxHp = Strings.toString(charAttributes.maxHp);
string memory strAttackDamage = Strings.toString(charAttributes.attackDamage);
string memory json = Base64.encode(
// abi.encodePacked で文字列を結合します。
// OpenSeaが採用するJSONデータをフォーマットしています。
abi.encodePacked(
'{"name": "',
charAttributes.name,
' -- NFT #: ',
Strings.toString(_tokenId),
'", "description": "This is an NFT that lets people play in the game Metaverse Slayer!", "image": "',
charAttributes.imageURI,
'", "attributes": [ { "trait_type": "Health Points", "value": ',strHp,', "max_value":',strMaxHp,'}, { "trait_type": "Attack Damage", "value": ',
strAttackDamage,'} ]}'
)
);
// 文字列 data:application/json;base64, と json の中身を結合して、tokenURI を作成しています。
string memory output = string(
abi.encodePacked("data:application/json;base64,", json)
);
return output;
}
コードを見ていきましょう。
// nftHolderAttributes を更新して、tokenURI を添付する関数を作成
function tokenURI(uint256 _tokenId) public view override returns (string memory) {
// _tokenId を使って特定の NFT データを照会し、データを取得しています。
CharacterAttributes memory charAttributes = nftHolderAttributes[_tokenId];
:
ここでは、nftHolderAttributes
関数に渡された_tokenId
を使って、特定のNFTデータを照会し、そのデータをcharAttributes
に格納しています。
- もし私が
tokenURI(151)
を実行したら、151番目のNFTに関連するJSONデータを返します。
次に、下記のコードを見ていきましょう。
string memory strHp = Strings.toString(charAttributes.hp);
string memory strMaxHp = Strings.toString(charAttributes.maxHp);
string memory strAttackDamage = Strings.toString(charAttributes.attackDamage);
ここでは、charAttributes
データ編集して、JSONの構造に合わせた変数に格納しています。
次に、下記のコードを見ていきましょう。
string memory json = Base64.encode(
// abi.encodePacked で文字列を結合します。
abi.encodePacked(
'{"name": "',
charAttributes.name,
' -- NFT #: ',
Strings.toString(_tokenId),
'", "description": "This is an NFT that lets people play in the game Metaverse Slayer!", "image": "',
charAttributes.imageURI,
'", "attributes": [ { "trait_type": "Health Points", "value": ',strHp,', "max_value":',strMaxHp,'}, { "trait_type": "Attack Damage", "value": ',
strAttackDamage,'} ]}'
)
);
ここでは、OpenSeaなどのNFTマーケットプレイスが採用しているNFTのメタデータのデータ構造に沿って、JSONをフォーマットしています。
- OpenSeaなどのNFTマーケットプレイスで扱えるJSONのデータ構造に関しては、こちら をご覧ください。
NFTのメタデータとして使用できるJSONは、下記のようなデータ構造になります。
{
"name": "USOPP",
"description": "This is an NFT that lets people play in the game Metaverse Slayer!",
"image": "https://i.imgur.com/WVAaMPA.png",
"attributes": [
{ "trait_type": "Health Points", "value": 200, "max_value": 200 },
{ "trait_type": "Attack Damage", "value": 50 }
]
}
最後に、下記のコードを見ていきましょう。
abi.encodePacked("data:application/json;base64,", json);
ここでは、文字列data:application/json;base64,
とjson
の中身を結合して、tokenURI
を作成しています。
それでは、ターミナル上で下記を実行してみましょう。
yarn contract run:script
下記のような結果がターミナルに出力されていれば、テストは成功です。
Compiling 2 files with 0.8.17
Solidity compilation finished successfully
Done initializing ZORO w/ HP 100, img https://i.imgur.com/TZEhCTX.png
Done initializing NAMI w/ HP 200, img https://i.imgur.com/WVAaMPA.png
Done initializing USOPP w/ HP 300, img https://i.imgur.com/pCMZeiM.png
Contract deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3
Minted NFT w/ tokenId 1 and characterIndex 2
Token URI: data:application/json;base64,eyJuYW1lIjogIlpFTklHQU1FIC0tIE5GVCAjOiAxIiwgImRlc2NyaXB0aW9uIjogIkJyYXZlIGFzIGEgYmxhemluZyBmaXJlLiIsICJpbWFnZSI6ICJodHRwczovL2kuaW1ndXIuY29tL2NmdG9kajkucG5nIiwgImF0dHJpYnV0ZXMiOiBbIHsgInRyYWl0X3R5cGUiOiAiSGVhbHRoIFBvaW50cyIsICJ2YWx1ZSI6IDMwMCwgIm1heF92YWx1ZSI6MzAwfSwgeyAidHJhaXRfdHlwZSI6ICJBdHRhY2sgRGFtYWdlIiwgInZhbHVlIjogMjV9IF19
Token URIがターミナルに出力されました!
Token URI:
の後に続く文字列全体をコピーしてください。
例)
data:application/json;base64 eyJuYW1lIjogIlpFTklHQU1FIC0tIE5GVC...
その文字列をブラウザのURLバーに貼り付けて、中身を確認してみましょ。
ブラウザに表示されるているのは、MyEpicGame.sol
に追加したtokenURI
関数の中で、NFTキャラクターの情報がJSONファイル形式にフォーマットされ、さらにBase64形式でエンコードされた結果です。
- JSONファイルの前に
data:application/json;base64,
を付けると、上記のようなエンコード文字列になり、ブラウザで読み込めます。
🙋♂️ 質問する
ここまでの作業で何かわからないことがある場合は、Discordの#ethereum
で質問をしてください。
ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨
1. 質問が関連しているセクション番号とレッスン番号
2. 何をしよう としていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット
次のレッスンに進んで、テストネットにコントラクトをデプロイしていきましょう 🎉