Now and Nawoo

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

MonoPixelNFTのコード解説

MonoPixelNFTの紹介記事

ソースコードはこちら

今回作ったコントラクトをざっと解説していきます。

ライセンス表記

// SPDX-FileCopyrightText: 2021 @NowAndNawoo
//
// SPDX-License-Identifier: MIT

他の人のコントラクトを見ていると、こんなCopyright表記をしている人がいたのでマネしてみました。

ERC721EnumerableとOwnableを継承

contract MonoPixelNft is ERC721Enumerable, Ownable {

Ownableを継承しているのは、以前の記事で書きましたがOpenSea対策です。

ERC721Enumerableを継承しているのは、tokenOfOwnerByIndex関数を使うためです。tokenOfOwnerByIndex関数ではownerとindexを指定してtokenIDを取得できます。 これを使って、UIサイトでユーザーの持っているNFTを一覧表示しています。

f:id:nawoo5:20211018224120p:plain:w500

アスキーアートのロゴ

    /*
  __  __                     _____ _          _   _   _ ______ _______ 
 |  \/  |                   |  __ (_)        | | | \ | |  ____|__   __|
 | \  / | ___  _ __   ___   | |__) |__  _____| | |  \| | |__     | |   
 | |\/| |/ _ \| '_ \ / _ \  |  ___/ \ \/ / _ \ | | . ` |  __|    | |   
 | |  | | (_) | | | | (_) | | |   | |>  <  __/ | | |\  | |       | |   
 |_|  |_|\___/|_| |_|\___/  |_|   |_/_/\_\___|_| |_| \_|_|       |_|   

*/

ソースコードアスキーアートでロゴを書くのに憧れていたので、やってみました。

アスキーアートはこのサイトで作成しました。→ Text to ASCII Art Generator (TAAG)

version

    uint8 public constant version = 5;

テストネットやメインネットへデプロイした後に修正したくなって再デプロイ、というのを繰り返していると どれが最新のコントラクトだっけ?となってしまうので、自分で区別しやすいように version を付けています。

claim関数

    function claim(uint256 tokenId) external {
        _safeMint(_msgSender(), tokenId);
    }

NFTを発行するためのclaim関数です。ただ_safeMintを呼んでいるだけです。

exists関数

    function exists(uint256 tokenId) public view returns (bool) {
        return _exists(tokenId);
    }

tokenIDが発行済かどうかをチェックする関数です。 UIサイトで[Claim NFT]ボタンをクリックしたときに、まずexists関数を呼び出して、 tokenIDが未発行の場合のみclaimを実行します。 (無駄なトランザクションを発行しないため。)

tokenURI関数

    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": "MonoPixelNFT ',
            tokenId.toHexString(32),
            '", \
"description": "MonoPixelNFT is a full on-chain NFT. You can store black and white 16x16 pixel art on the blockchain. created by @NowAndNawoo", \
"image": "data:image/svg+xml;base64,',
            Base64.encode(bytes(svg)),
            '"}'
        );
        return
            string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(json)
                )
            );
    }

tokenURI関数はいつもどおりで、特に書くことはないですが、 name, description, imageの3つを含むJSONBase64エンコードして返します。 imageは次のgetSVG関数で作成します。

getSVG関数

    function getSVG(uint256 data) private pure returns (string memory) {
        bytes memory rects;
        for (uint256 y = 0; y < 16; y++) {
            for (uint256 x = 0; x < 16; x++) {
                uint256 index = 255 - y * 16 - x; // (0,0)=>255, (1,0)=>254, (15,15)=>0
                if (data & (1 << index) != 0) {
                    rects = abi.encodePacked(
                        rects,
                        '<rect x="',
                        (x * 10 + 1).toString(),
                        '" y="',
                        (y * 10 + 1).toString(),
                        '"/>'
                    );
                }
            }
        }
        return
            string(
                abi.encodePacked(
                    '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 162 162">\
<style>rect{shape-rendering:crispEdges;width:10px;height:10px;fill:white;}</style>\
<rect style="width:162px;height:162px;fill:black;"/>',
                    rects,
                    "</svg>"
                )
            );
    }
}

getSVG関数では、tokenIDをdataとして受け取って、ピクセルアートのSVGを作成します。

まず、forループで256bitを順にチェックします。 if (data & (1 << index) != 0) の部分で、index番目のbitが0か1かを判定します。 bitが1ならば白ピクセル用の<rect>を作成し、rects変数に追加していきます。

ビット演算についてはこちら → Solidityメモ: ビット演算 - Now and Nawoo

生成するSVGを短くするために、白ピクセル用<rect>のwidth、height、fillは<style>タグでまとめて指定しています。

最後にsvgタグ全体を作ります。 svgは162x162サイズにしています。 これは1ピクセルのサイズが10x10で、それが縦と横に16個並び、さらに全体の周囲に幅1の黒い枠線を付けるためです。 (OpenSeaで見ると黒い枠線があったほうがキレイだったので。)

背景用の162x162サイズの黒い<rect>の後に、先程作った白ピクセルのrects変数を追加します。

shape-rendering:crispEdges について

<style>タグでshape-rendering:crispEdges;を指定しておくと、四角形の周囲がくっきりと表示されるのでピクセルアートには必須です。 (これはCryptoPunksを参考にしました。)

f:id:nawoo5:20211018231340p:plain

おわりに

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