# 教你製作完全 On-Chain 的 NFT

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

---

本文亦發佈在作者網站： 最後更新：2022-04-02

[https://www.frank.hk/blog/on-chain-nft/](https://www.frank.hk/blog/on-chain-nft/)

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

screen 20220402100140 2x

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

我在[製作 ERC721 智能合約那篇文章](https://www.frank.hk/blog/nft-smart-contract)中說過，NFT 之所以會顯示圖案，是因為根據 NFT 的標準，每一個 NFT 都有一個 json 格式的 metadata 與之對應， metadata 中有一個參數是指定 NFT 的圖案，所以只要訪問這個 NFT 的 metadata，然後再得到圖像地址，就可以顯示出 NFT 的圖案了。

meta

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

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

查看 [OpenSea metadata 的標準](https://docs.opensea.io/docs/metadata-standards)，除了 `image` 欄位，還有一個 `image_data` 可供使用，這個`image_data` 則為我們「即時產生」圖案提供了可能性。

screen 20220402102119 2x

既然 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。所以如果寫在同一張智能合約中會導致智能合約超出要求而無法發佈。**

screen 20220402104714 2x

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

screen 20220402110752 2x

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

OpenSea:[https://testnets.opensea.io/collection/onchainnft-demo](https://testnets.opensea.io/collection/onchainnft-demo?search%5BsortAscending%5D=true&search%5BsortBy%5D=CREATED_DATE)

EtherScan:[https://rinkeby.etherscan.io/address/0x5b97a3663d6a9be5ef6a21fb2108b93f93f8b2e5#writeContract](https://rinkeby.etherscan.io/address/0x5b97a3663d6a9be5ef6a21fb2108b93f93f8b2e5#writeContract)

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

---

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