教你製作完全 On-Chain 的 NFT

  • FrankFrank
  • /
  • 7 分鐘閱讀
  • /
  • Apr 2, 2022
  • /
  • - views

首先看看我做的這個 demo: https://testnets.opensea.io/collection/onchainnft-demo

這些 NFT 的特別之處在於,每個 NFT 的圖案都不一樣( ID 不一樣),所有 NFT 的資料都是保存在區塊鏈上,沒有用到任何的伺服器或者 IPFS。

我在製作 ERC721 智能合約那篇文章中說過,NFT 之所以會顯示圖案,是因為根據 NFT 的標準,每一個 NFT 都有一個 json 格式的 metadata 與之對應, metadata 中有一個參數是指定 NFT 的圖案,所以只要訪問這個 NFT 的 metadata,然後再得到圖像地址,就可以顯示出 NFT 的圖案了。

一般的 NFT,metadata 和 NFT 的圖案都是保存在非區塊鏈上,例如伺服器,或者是去中心化的 IPFS。這樣做其中有一個問題就是有可能會導致資料遺失。中心化伺服器自然不用多說,而即便是 IPFS 也是不能保證資料永遠在線(如果對 IPFS 有興趣,可以參考我之前寫的 IPFS 問與答)。如果所有資料都保存在區塊鏈上,則能夠完美解決這個問題。但是,在區塊鏈上保存大量數據非常昂貴,所以想要這樣做,必須盡量減少儲存的數據量,那我們今天就來看看可以怎麼做。

首先我們不可能將所有 NFT 的圖案都「原封不動」的保存在區塊鏈,這樣成本太高。如果有辦法能「即時產生」圖案,則可以大大降低成本。這時我們想到了 SVG 圖案檔。SVG 的本質其實就是一段文字,因此我們可以根據一定的要求隨時「拼湊」出來。

查看 OpenSea metadata 的標準,除了 image 欄位,還有一個 image_data 可供使用,這個image_data 則為我們「即時產生」圖案提供了可能性。

既然 NFT 的圖案可以做到「即時產生」,那純文字的 NFT metadata 更不在話下,我們也可以按照這個思路,讓 tokenURI() 直接返回 NFT 的 metadata JSON, 而不是一個 http 或 ipfs 連接。

好了廢話不多說我們看看程式怎麼寫吧。

tokenURI() 我們做如下處理,拼湊出符合要求的 metadata JSON ,用 Base64 編碼之後直接返回。 注意對 string 進行拼接需要用到 abi.encodePacked , Base64 編碼使用到一個公用 library,文後會給出源碼。

function tokenURI(uint256 tokenId)
        public
        view
        override(ERC721)
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );
        
        string memory json = Base64.encode(
            bytes(
                string(
                    abi.encodePacked(
                        '{"name": "On Chain NFT #',
                        uint2str(tokenId),
                        '",',
                        '"image_data": "',
                        onChainSVG.getSvgImage(tokenId),
                        '"',
                        "}"
                    )
                )
            )
        );
        return string(abi.encodePacked("data:application/json;base64,", json));
    }

留意到上面拼接 json 時有一句 onChainSVG.getSvgImage(tokenId) 這個 function 的作用是根據傳入的 tokenId ,即時「產生」一個 SVG 數據。產生 SVG 的邏輯我們調用了另外一個智能合約進行處理,而不是寫在同一個智能合約中。 原因是根據 2016 年實施的 EIP-170 標準,每張智能合約的大小不能夠超過 24.576 kb。所以如果寫在同一張智能合約中會導致智能合約超出要求而無法發佈。

合約部署後,調用 tokenURI() 我們會看到合約直接給出了 metadata 的完整 JSON (Base64 編碼)

如果想測試一下的話,可以到 etherscan 上,連接錢包(Rinkeby 測試網路),調用 mint function mint 一個 NFT 給你自己哦。

兩張智能合約的原始碼:
https://gitlab.com/0x0016/smart-contract-demo/-/tree/main/contracts/nft

OpenSea:
https://testnets.opensea.io/collection/onchainnft-demo

EtherScan:
https://rinkeby.etherscan.io/address/0x5b97a3663d6a9be5ef6a21fb2108b93f93f8b2e5#writeContract