🤖

奇跡体験!アンビリバボーbotのアレをフルオンチェーンNFTとして再現する

2022/01/19に公開

前置き

この記事は特定のNFTや仮想通貨の購買を促進する記事ではありません。

奇跡体験!アンビリバボーbotのアレとは?

https://twitter.com/kiseki_unb

こういうアレですね。似たようなのでファルコン・パンチとか。

https://twitter.com/Fal_conpunch

この1つ1つの乱数生成結果をフルオンチェーンNFTとして作ったらなんか面白いんじゃないか?と思ったので今回実装しました。

実装

環境

  • hardhat
  • solidity 0.8.0系

フルオンチェーンとは

フルオンチェーンNFTの概要と作る簡単な方法についてはこちらがわかりやすくまとまっています。
今回はこの生成を自動化する部分も作ります。

環境構築

hardhatのget-startedに書かれている手順に沿ってプロジェクトを作ります。

npm install --save-dev hardhat
npx hardhat

openzeppelinのライブラリもインストールします
(ERC721と便利クラス用)

npm i @openzeppelin/contracts

Base64クラスのライブラリをインストール
(フルオンチェーン化するために必要)

npm i base64-sol@1.0.1
// Kisekitaiken.sol

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

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "base64-sol/base64.sol";

contract Kisekitaiken is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    struct KisekitaikenToken {
        string prefixText;
        string suffixText;
    }

    KisekitaikenToken[] public tokens;

    // 日本語の文字列を埋め込むには unicode"" としなければコンパイルエラーになる
    string[] private _prefixWord = [
        unicode"奇",
        unicode"跡",
        unicode"体",
        unicode"験"
    ];
    string[] private _suffixWord = [
        unicode"ア",
        unicode"ン",
        unicode"ビ",
        unicode"リ",
        unicode"バ",
        unicode"ボ"
    ];

    // 疑似乱数用の使い捨て変数
    uint256 randNonce;

    constructor() ERC721("Kisekitaiken", "KSK") {}

    function mint() public {
        _tokenCounter.increment();
        uint256 newItemId = _tokenCounter.current();
        _safeMint(msg.sender, newItemId);

        // 奇跡体験 と アンビリバボ をランダム生成する ---->>>>
        string memory prefixText;
        uint256 prefixTextLength = 4;
        for (uint256 i = 0; i < prefixTextLength; i++) {
            uint256 random = randMod(prefixTextLength);
            prefixText = string(
                abi.encodePacked(prefixText, _prefixWord[random])
            );
        }
        string memory suffixText;
        uint256 suffixTextLength = 6;
        for (uint256 i = 0; i < suffixTextLength; i++) {
            uint256 random = randMod(suffixTextLength);
            suffixText = string(
                abi.encodePacked(suffixText, _suffixWord[random])
            );
        }
        // <<<<----
        tokens.push(KisekitaikenToken(prefixText, suffixText));
    }

    function mintBatch(uint256 _mintCount) public {
        for (uint256 i = 0; i < _mintCount; i++) {
            mint();
        }
    }

    // 乱数生成用
    function randMod(uint256 _modulus) internal returns (uint256) {
        randNonce++;
        return
            uint256(
                keccak256(
                    abi.encodePacked(block.timestamp, msg.sender, randNonce)
                )
            ) % _modulus;
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );
        KisekitaikenToken memory token = tokens[tokenId - 1];
        string memory svg = getSVG(token);
        bytes memory json = abi.encodePacked(
            '{"name": "',
            token.prefixText,
            unicode"!",
            token.suffixText,
            unicode"ー",
            '", "description": "kisekitaiken", "image": "data:image/svg+xml;base64,',
            Base64.encode(bytes(svg)),
            '"}'
        );
        string memory _tokenURI = string(
            abi.encodePacked(
                "data:application/json;base64,",
                Base64.encode(json)
            )
        );
        return _tokenURI;
    }

    // OpenSea等で表示される実体
    function getSVG(KisekitaikenToken memory token)
        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;font-family:serif;}</style>",
                    '<rect width="100%" height="100%" fill="#a9ceec" />',
                    '<text x="10%" y="45%" font-size="50px">',
                    token.prefixText,
                    unicode"!",
                    "</text>",
                    '<text x="10%" y="65%" font-size="40px">',
                    token.suffixText,
                    unicode"ー",
                    "</text>",
                    "</svg>"
                )
            );
    }
}

技術的に難しいことはしていなく、ランダム生成用のプールからランダムに文字列をピックし、保存。
読み出すときはsvgを生成してbase64エンコードして返すような流れです。

デプロイ用のスクリプト作成

  • deploy.jsを作成する
// scripts/deploy.js
const hre = require("hardhat");

async function main() {
  const KisekitaikenFactory = await hre.ethers.getContractFactory(
    "Kisekitaiken"
  );
  const kisekitaiken = await KisekitaikenFactory.deploy();

  await kisekitaiken.deployed();
  // gasLimitがデフォルトだとガスリミットエラーによりトランザクションが失敗してしまうため、上限をあげています。
  await kisekitaiken.mintBatch(50, { gasLimit: 10000000 });

  console.log("kisekitaiken deployed to:", kisekitaiken.address);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

デプロイを実行する

npx hardhat run scripts/deploy.js --network rinkeby

デプロイのやり方↓

https://hardhat.org/tutorial/deploying-to-a-live-network.html

確認

今回のサンプル↓

https://rinkeby.etherscan.io/address/0xbA8b2D0b5139C8066f204613DE79d368eCad0697

https://testnets.opensea.io/collection/kisekitaiken-sdxfg9ufyq

無事svgが表示されているのを確認できました

まとめ

  • 奇跡体験アンビリバボーbotのようなランダム文字列生成フルオンチェーンNFTを作った
  • hardhatを用いてrinkebyテストネットにデプロイした

最後に

今回はネタ寄りの記事でしたが、アイデア次第ではいままでにないNFTを作れるかも?
また、スマートコントラクトでの乱数生成は脆弱性を生じやすいです。
今回使った乱数生成を実際のプロダクトで流用するかどうかは慎重に判断してください。

参考リンク

https://nawoo.hateblo.jp/entry/2021/09/28/203446

今回のフルオンチェーンのソースコードは こちらのサイトを参考に改変しました。ありがとうございます🙏

etc

Solidityについてワイワイ学ぶコミュニティ「solidity-jp」を作りました!
いまから学んでみたい/学習中だけどの日本語の情報が少ない/古くて時間がかかっているという方、一緒に学びましょう〜!!

https://solidity-jp.dev/

また、TwitterにてSolidityについて技術的な部分を発信しています。良ければフォローお願いします!

https://twitter.com/k0uhashi

Discussion