Now and Nawoo

NFTの制作記録、技術メモ → C#, Solidity, Blockchain, Bitcoin, Ethereum, NFT

シンプルなフルオンチェーンNFTを作りました

Solidityの勉強として、シンプルなフルオンチェーンNFT (HelloNFT) を作ってみました。

f:id:nawoo5:20210928125616p:plain:w200

  • フルオンチェーンのNFT (サーバーを使っていません)
  • 見た目も中身もシンプル
  • 数字(#)が変わるだけです
  • ランダム要素はありません
  • #0から順番に発行されます
  • 発行数の制限はありません
  • mint関数でガス代だけで発行できます
  • Polygonのテストネット(Mumbai)にデプロイしました (MumbaiのMATICが必要です)

という、なんともシンプルなNFTです。

Polygonscanでコードを見る

OpenSeaで見る

コード解説

最低限の機能しかないため、コードもシンプルです。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./Base64.sol";
  • OpenZeppelinのERC721を継承して作っています。
  • Strings.solはuintからstringへの変換に使います。
  • Base64.solはSVGJSONBase64エンコードするときに使っています。(tokenURI関数で利用)
contract HelloNft is ERC721("HelloNFT", "HELLO") {
    uint256 public nextTokenId = 0;

    constructor() {}

↑このコンストラクタは消し忘れです (^^;

   function mint() external {
        uint256 tokenId = nextTokenId;
        nextTokenId++; // <- Effects
        _safeMint(_msgSender(), tokenId); // <- Interactions
    }

mint関数で、新しいNFTを発行します。 LootのようにtokenIdを指定するのではなく、0番から順に発行していきます。

リエントランシー攻撃対策のCheck-Effects-Interactionsパターンを意識して、 nextTokenId++を_safeMint()より先にしています。

   function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
        string memory svg = getSVG(tokenId);
        bytes memory json = abi.encodePacked(
            '{"name": "HelloNFT #',
            Strings.toString(tokenId),
            '", "description": "HelloNFT is a full on-chain text NFT.", "image": "data:image/svg+xml;base64,',
            Base64.encode(bytes(svg)),
            '"}'
        );
        return string(abi.encodePacked("data:application/json;base64,", Base64.encode(json)));
    }

一般的なNFTでは tokenURI関数でサーバーのURLを返すだけですが、 今回は tokenURI関数でSVGを含んだJSONデータを返すことで、 サーバーを必要としないNFT(フルオンチェーンNFT)を実現しています。

最初に requireで引数のtokenIdが発行済みかどうかチェックしています。 (Lootだと未発行のtokenURIも見えてしまうという問題がありました)

JSONデータには name, description, image の項目があります。 imageにはBase64エンコードしたSVGデータを設定しています。

tokenURI関数の最後で、JSON全体をBase64エンコードして返します。

   function getSVG(uint256 tokenId) private pure returns (string memory) {
        return
            string(
                abi.encodePacked(
                    '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350">\
<style>text{fill:black;30px;font-family:serif;}</style>\
<rect width="100%" height="100%" fill="#ccddcc" />\
<text x="10%" y="30%" font-size="50px">HelloNFT</text>\
<text x="10%" y="60%" font-size="30px">#',
                    Strings.toString(tokenId),
                    "</text></svg>"
                )
            );
    }
}

ここではSVGを作成します。rectタグが背景の塗りつぶし、textタグで名前(HelloNFT)とID(#数字)を表示します。

コードはこれで終わりです。わずか46行でNFTができてしまいました。

おわりに

まだSolidity初心者なので、間違い、バグ、改善点などありましたら、どうぞご指摘ください。

さて、今回はとても簡単なものでしたが、次はもう少し楽しめるランダム要素のあるNFTを作りたいと思います。