💣

【Solidity】強制的に流動性を持たせる(かもしれない)NFTの作り方

2022/02/08に公開

前置き

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

概要

前回、【Solidity】譲渡や二次流通ができないNFTの作り方という記事を書きました。
では逆に、無理やり流動性を持たせるNFTを作る方法はないかな?と考えた結果、1つ面白いやり方が思いついたので記事化しました。

流動性を持たせる

「誰かに転送しないと一定期間後に売買も譲渡もburnもできないような仕組み」を作ることで流動性を無理やり持たせてみます。

今回のサンプル

  • フルオンチェーンNFT。
  • mintして誰かにNFTを転送する。
  • 受取人は7日以内に誰かに渡さないと爆発し、以後売買も譲渡もできなくなる。

※NFTとして出すにはいろいろ欠陥があります

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

環境

  • node v12.22.9
  • hardhat 2.8.3
  • macOS 12.0

開発

環境構築します

npm install -D hardhat
npx hardhat

npm install base64-sol @openzeppelin/contracts
// 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 TimeBomb is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    mapping(uint256 => uint256) lastTransferredAt;
    uint256 constant timeToExplosion = 7 days;
    // uint256 constant timeToExplosion = 60 * 60 * 24 * 7; これと同じ

    constructor() ERC721("TimeBomb", "BOMB") {}

    function mintAndTransfer(address _to) public {
        _tokenCounter.increment();
        uint256 newItemId = _tokenCounter.current();
        // 送付先アドレスにmintするのではなく、あえてmintを自身にする
        // 誰がmintして送ってきたかがOpenSea上でわかりやすい。
        _safeMint(msg.sender, newItemId);
        _safeTransfer(msg.sender, _to, newItemId, "");
    }

    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": "',
            "Bomb! #",
            Strings.toString(tokenId),
            '", "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(uint256 _tokenId) private view returns (string memory) {
        string memory bombText;
        if (isExploded(lastTransferredAt[_tokenId], block.timestamp)) {
            bombText = unicode"💥";
        } else {
            bombText = unicode"💣";
        }
        return
            string(
                abi.encodePacked(
                    '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350">',
                    '<rect width="100%" height="100%" fill="#eeeeee" />',
                    '<text text-anchor="middle" x="50%" y="60%" font-size="100px">',
                    bombText,
                    "</text>",
                    "</svg>"
                )
            );
    }

    // 爆発したかどうかチェック
    // 最後に転送した時刻と現在時刻を元に計算し、爆発猶予時間を超えたか超えてないか?
    function isExploded(uint256 _lastTransferredAt, uint256 _now)
        public
        pure
        returns (bool)
    {
        return (_now - _lastTransferredAt) >= timeToExplosion;
    }

    // https://zenn.dev/ryo_takahashi/articles/d4bdc137b564db 参照
    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 tokenId
    ) internal override {
        if (from != address(0)) {
            // 爆発していたらtransferもburnも不可能
            require(!isExploded(lastTransferredAt[tokenId], block.timestamp));
        }
        lastTransferredAt[tokenId] = block.timestamp;
        super._beforeTokenTransfer(from, to, tokenId);
    }
}

ポイントは 時刻計算 の部分と _beforeTokenTransfer内の譲渡制限 にあります。
7日後に爆発するといっても、受取時から7日後に爆発処理が行われるわけではなく、受取時に時刻を保存しておき、tokenURIやNFTの転送、売買を行うときにチェックするような仕組みです。

https://zenn.dev/ryo_takahashi/articles/d4bdc137b564db

動作確認

テストネットワークにデプロイし、動作確認してみます。
mintやtransferはetherscanから叩いています。詳しくは以下。

https://zenn.dev/ryo_takahashi/articles/77f4eeb3f9f52b

サンプル↓

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

https://testnets.opensea.io/assets/0xc7cff662cb0bfe0af0cbe96cf8c6dc419e691197/3

mintと同時にtransferできています。
これが7日後には以下のようになり、以降は転送も売買も不可能になります。
(メタデータを更新しなければ見た目が反映されないのが難しいところ)

昇華

このままだといろいろ破綻していますが、以下のようなアイデアを盛り込むと、もしかしたら価値が出てくるかも?

  • 何回転送されたか加算して保持
  • 爆発に近くなるほど状態が変化する
    • だんだん爆弾が大きくなる
    • 爆発まであと3日→背景オレンジ。あと1日→背景赤

なんか面白いアイデアがある人は教えてください 🙏

まとめ

  • N日後に爆破する爆弾NFTを作りました
  • なんか面白いNFTに化けるかもしれない(適当)

これ適当にNFTとして出したら、何十年後かに「数十年前から続いてる爆弾ゲームがあるらしいぞ!急げ〜〜💨💨」とかならないですかね?ならないか。

etc

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

https://solidity-jp.dev/

また、TwitterにてSolidityに関する技術情報を発信しています。良ければフォローお願いします!

https://twitter.com/k0uhashi

Discussion