前書き
プログラミング、スマートコントラクトともに初学者のものです。最近NFTを発行できるスマートコントラクトを作成できるようになったのですが、メタデータを更新してレベルアップできるNFTを作れるようになったら面白いなと思ったので、今回はそのようなスマートコントラクトを作成していこうと思います。
※NFT発行のためのコードとremix ideの操作方法を一通り理解している前提で進めます。
環境
Remix ide
solidity 0.8.9
完成物のイメージ
SVGのオンチェーンNFTをミントする関数とメタデータを更新する関数を備えているスマートコントラクト。
SVGで現在のレベルを出力させます。こんな感じ
<svg width="350" height="50" fill="none" xmlns="http://www.w3.org/2000/svg">
<text x="0" y="30" font-family="Verdana" font-size="20" fill="black">
level:具体的な数字
</text>
</svg>
どのようにレベルアップ(メタデータの更新)するか
Open ZeppelinのERC721のライブラリに _setTokenURI() という関数がありますが、この関数を一つのtokenIdに対して複数回呼び出すことによってレベルアップ(メタデータの更新)を実現します。
コード
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
contract levelUpNFT is ERC721URIStorage {
// CountersとStringsの使用を宣言
using Counters for Counters.Counter;
using Strings for uint256;
// idごとにトークンのレベルを管理するマッピングを定義
mapping (uint256 => uint256) public idToLevel;
//tokenIdを状態変数として定義(NFTをミントするたびにインクリメントされます。)
Counters.Counter private _lastTokenId;
//コンストラクタを定義
constructor() ERC721("LevelUpNFT", "LEVELUP") {}
//ミント関数を定義
function nftMint() public {
//tokenIdをインクリメント
_lastTokenId.increment();
//NFTをミント
_safeMint(msg.sender, _lastTokenId.current());
//NFTのコンテンツとしてのsvgとメタデータを関数から取得
bytes memory svg = _createSVG(0);
bytes memory metadata = _createMetadata(svg);
//uriを作成
string memory uri = string(abi.encodePacked("data:application/json;base64,", Base64.encode(metadata)));
//発行したNFTにコンテンツを付与
_setTokenURI(_lastTokenId.current(), uri);
//マッピングに要素を追加
idToLevel[_lastTokenId.current()] = 0;
}
//簡単なメタデータを定義
function _createMetadata(bytes memory svg) pure private returns(bytes memory){
return(
abi.encodePacked(
'{"image": "data:image/svg+xml;base64,',
Base64.encode(svg),
'"}'
)
);
}
//SVGを返す関数を定義
function _createSVG(uint256 level) pure private returns(bytes memory) {
bytes memory contentSVG = abi.encodePacked(
'<svg width="350" height="50" fill="none" xmlns="http://www.w3.org/2000/svg">',
'<text x="0" y="30" font-family="Verdana" font-size="20" fill="black">level:',
Strings.toString(level),
'</text>',
'</svg>'
);
return contentSVG;
}
//URIを再設定する関数を定義(レベルアップ関数)
function levelUp(uint256 tokenId) public {
address owner = ownerOf(tokenId);
require(owner == msg.sender);
//レベルをインクリメント
uint256 newLevel = idToLevel[tokenId] + 1;
idToLevel[tokenId] = newLevel;
//NFTのコンテンツとしてのsvgとメタデータを関数から取得
bytes memory newSvg = _createSVG(newLevel);
bytes memory metadata = _createMetadata(newSvg);
//uriを作成
string memory newURI = string(abi.encodePacked("data:application/json;base64,", Base64.encode(metadata)));
_setTokenURI(tokenId, newURI);
}
}
肝となるコード
上記のコードの idToLevelマッピング と levelUp関数 が今回のプロジェクトの肝です。
tokenIdごとのレベルをmappingで管理
// idごとにトークンのレベルを管理するマッピングを定義
mapping (uint256 => uint256) public idToLevel;
tokenIdごとにレベルをmappingで管理します。
メタデータを更新する
//URIを再設定する関数を定義(レベルアップ関数)
function levelUp(uint256 tokenId) public {
address owner = ownerOf(tokenId);
require(owner == msg.sender);
//レベルをインクリメント
uint256 newLevel = idToLevel[tokenId] + 1;
idToLevel[tokenId] = newLevel;
//NFTのコンテンツとしてのsvgとメタデータを関数から取得
bytes memory newSvg = _createSVG(newLevel);
bytes memory metadata = _createMetadata(newSvg);
//uriを作成
string memory newURI = string(abi.encodePacked("data:application/json;base64,", Base64.encode(metadata)));
_setTokenURI(tokenId, newURI);
}
requireで引数のトークンが関数実行者のものか確認します。idToLevelマッピングから引数のtokenIdのレベルを取得しインクリメントします。それをmappingに格納しなおします。インクリメントしたレベル(newLevel)を引数としてSVGを作成し、新たなメタデータを作成します。
動作確認
1.コンパイルと仮装環境へのデプロイ
2.nftMint関数を実行(最初のtokenIdは1です)
3.引数を1としてtokenURI関数でURIを取得し、ブラウザのフォームに入力。以下のようなページが出力されます。
4.data:image/~ をコピーしブラウザのフォームの入力します。以下のように出力されます。
5.levelUp関数を実行します。
6.3, 4を再び実行すると以下のように出力されます。
無事レベルアップ(メタデータの更新)が確認されました!!!