レベルアップするNFTを作る！！！〜メタデータを更新できるようにしよう〜

## 前書き
プログラミング、スマートコントラクトともに初学者のものです。最近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に対して複数回呼び出すことによってレベルアップ（メタデータの更新）を実現します。

## コード
```solidity:level-up-nft.sol
//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を取得し、ブラウザのフォームに入力。以下のようなページが出力されます。
![スクリーンショット 2023-03-24 0.14.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2440833/403412f9-f0ca-66f8-591f-6c33094fa43e.png)
4.data:image/~ をコピーしブラウザのフォームの入力します。以下のように出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2440833/92127016-e8ea-ce07-34c1-dd59dc77b6f3.png)
5.levelUp関数を実行します。
6.3, 4を再び実行すると以下のように出力されます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2440833/964bcc78-ee87-ac20-0043-22b00eae7379.png)

無事レベルアップ（メタデータの更新）が確認されました！！！