Smart Contract 智能合約玩轉 NFT 盲盒模式

  • FrankFrank
  • /
  • 7 分鐘閱讀
  • /
  • Oct 10, 2021
  • /
  • - views

最近有留意一些 NFT 的項目專案,發現絕大多數都是採用了「盲盒模式」來玩,即購買的時候並不知道裡面是什麼,等到了一定的時間後,盲盒會開啟,才會知道你買的 NFT 具體的屬性,是否稀有等。

MekaVerse 盲盒

這裏我們不討論 Marketing 的做法,純粹從技術角度探討一下「盲盒模式」要怎麼做到。

之前寫過一篇文章講怎樣輕鬆編寫 ERC 721 智能合約,裡面有說過 NFT 在 OpenSea 等市場中顯示的名稱,圖像,屬性等資料,完全是由 NFT 的 metadata 決定,而 metadata 則是靠調用 NFT 智能合約中的 tokenURI function 而得到。因此,這個 metadata 並不是像大家想像的一樣不可更改,只要能夠讓智能合約返回不同的 tokenURI, 則在市場中顯示的圖像以及屬性就會隨之更改。

聰明的你可能想到,如果 tokenURI 是一個 http 連接,即 metadata 是存放在傳統伺服器中,則非常容易做到這個效果。只要修改伺服器程式,返回完全不同的 metadata 即可。沒錯,但如果 tokenURI 是一條 IPFS 連結,那要如何處理? IPFS 不是不可更改嗎? 哈哈,是的 IPFS 連結是不能更改,但是 tokenURI 可以呀。只要對智能合約的代碼做一點更改,便可以輕鬆做到效果。

我們的大概思路是,有一個開關參數控制盲盒開啟與否,如果 true,則 tokenURI 將返回一條盲盒專用的 metadata IPFS 連結,裡面的 name,image 等都是固定的名稱和圖像。另外我們還需要有兩個 function ,供授權的地址調用,一個用以改變上述開關參數的狀態,另一個用以設定盲盒開啟後新的 baseURI。等到開啟盲盒的神聖日子來臨,被授權的地址調用這兩個 function,即可揭開盲盒的神秘面紗。

(當然,聰明的你也應該想到,不使用開關參數,完全依靠更改baseURI 也是可以的。只是我覺得有個開關更容易操作一些)

以下是參考的代碼:

abstract contract BaseERC721BlindBox is BaseERC721 {
    using Strings for uint256;
    bool private _blindBoxOpened = false;
    string private _blindTokenURI =
        "ipfs://QmexqcLDvoP6HCTtSGumG3yzhZdH8guvV3z3kReCvf2QKn";

    function _isBlindBoxOpened() internal view returns (bool) {
        return _blindBoxOpened;
    }

    function setBlindBoxOpened(bool _status) public {
        require(
            hasRole(DEFAULT_ADMIN_ROLE, _msgSender()),
            "BaseERC721BlindBox: only admin can do this action"
        );
        _blindBoxOpened = _status;
    }

    // this function controls how the token URI is constructed
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (_blindBoxOpened) {
            string memory baseURI = _baseURI();
            return
                bytes(baseURI).length > 0
                    ? string(
                        abi.encodePacked(baseURI, tokenId.toString(), ".json")
                    )
                    : "";
        } else {
            return _blindTokenURI;
        }
    }
}

當盲盒沒有開啟時,進入 OpenSea 看到的會是這樣

當盲盒開啟後,進入 OpenSea 看到的會是這樣