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

By [0x0016區塊猿](https://paragraph.com/@0x0016) · 2022-04-03

---

_本文亦發佈在作者網站： 最後更新：2021-10-10_

[https://www.frank.hk/blog/smart-contract-mystery-box/](https://www.frank.hk/blog/smart-contract-mystery-box/)

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

MekaVerse 盲盒

MekaVerse 盲盒

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

我[之前寫過一篇文章](https://www.frank.hk/blog/nft-smart-contract)講怎樣輕鬆編寫 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;
            }
        }
    }
    

screen 20211010183332 2x

screen 20211010184258 2x

技術交流，其他諮詢等，請[按此聯絡](https://www.frank.hk/contact)。

---

*Originally published on [0x0016區塊猿](https://paragraph.com/@0x0016/smart-contract-nft)*
