本系列文將分享如何使用 Python 的 Brownie 框架開發 ERC-721,也就是 NFT。並且 deploy contract 到 Ethereum 的測試網路 Rinkeby,圖檔和 Metadata 則上傳到 IPFS,再透過測試版本的 Opensea 來看看成果!

在上一篇 自己寫 NFT 吧!- Deploy 到 Ganache – 以 Python Brownie 開發 我們將 NFT 的 ERC-721 contract deploy 到 local 的 Ganache,接下來要準備 NFT 的 Metadata,到 testnet mint 後才能在 OpenSea 看到精美的圖片和文字。

為何需要 Metadata?

自己寫 NFT 吧!- 實現 ERC-721 – 以 Python Brownie 開發 我們提過,ERC-721 contract 中其實只儲存了「tokenID -> owner address」和「tokenID -> tokenURI」的 mapping,裡面並沒有放如 NFT 所代表的圖片、標題等等資訊,此時就需要透過外部的 Metadata 來實現,並透過 tokenURI 指向 Metadata 所在的位置。

Metadata 其實就是一份 JSON 檔,由 EIP-721 所定義,記錄這張 NFT 的資訊。我們可以從 OpenSea 的官方文件參考整理好的欄位定義:

https://docs.opensea.io/docs/metadata-standards

在 NFT 專屬頁面,OpenSea 會透過 JSON 上定義的資料去顯示:

https://docs.opensea.io/docs/metadata-standards

這邊我們以圖片 NFT 為例,並將圖片檔案和 Metadata 放置到 IPFS。

上傳 NFT 圖檔到 IPFS

IPFS 可以自行下載節點來「Pin」檔案,或者透過第三方服務,上傳到他們的機器由他們幫你「Pin」,這邊以 Pinata 為例。

Pinata 有提供免費 1GB 的額度,不用綁信用卡,申請即可使用。介面也很像一般的雲端硬碟,簡單點擊就能把檔案上傳。

我們把可愛恐龍的素材圖片上傳到 Pinata,可以看到檔名旁邊有一排 CID,透過這 CID 就能由 IPFS 協定取得對應檔案。

如第一張圖片的 CID 是QmcV38FD4qZBs9K8SafexvKZ3kbd1bxHVCLew7E4W61cCT,我們可以透過 IPFS 官方的 gateway,讓他去跟 IPFS 節點取得資料後轉成 http 的方式傳回瀏覽器。

https://gateway.ipfs.io/ipfs/QmcV38FD4qZBs9K8SafexvKZ3kbd1bxHVCLew7E4W61cCT

你可能會好奇,CID 怎麼來的?根據 IPFS 官方文件,CID 是透過 sha-256 去計算檔案內容而得的。因此只要檔案內容不一樣,CID 就不一樣。IPFS 也有點區塊鏈不可變性質的味道。

實作 Metadata

有了對應圖片的 IPFS 位置,我們可以開始填寫 Metadata JSON 了。以 KongLongNFT #1 為例:

{
	"description": "Kong Long say: Happy new year!", 
	"external_url": "https://koding.work/kong-long-nft/", 
	"image": "ipfs://QmcV38FD4qZBs9K8SafexvKZ3kbd1bxHVCLew7E4W61cCT", 
	"name": "KongLong #1",
	"attributes": [
		{
			"trait_type": "Festival", 
      		"value": "CNY"
		},
		{
			"trait_type": "Doing",
			"value": "Be cute"
		},
		{
			"trait_type": "Eyes", 
      		"value": "Big"
		},
		{
			"trait_type": "Body", 
	  		"value": "None"
		},
		{
			"trait_type": "Head", 
	  		"value": "Big"
		}
	]
  }

依序給定了這張 NFT 的 name、description、image 這三個必定要有的參數。external_url 給定了 NFT collection 的專頁網址。attributes 可以看成是這張 NFT 的屬性,OpenSea 頁面會幫你統計這張 NFT 的屬性稀有度,炒作時可以依照稀有性將該張 NFT 的價格堆到月球。

我們可以先偷看一下這份 Metadata 在 NFT deploy 後會長怎樣,讓大家有個視覺上的概念:

可以看到 Metadata 中的 image 欄位決定圖片,name 決定這張 NFT 的名稱「KongLong #1」,description 決定圖片下方的描述,attributes 決定 Properties 欄位的性質。

完成後,也將此 Metadata 上傳到 Pinata,取得 CID

NFT 到底是不是不可變?

我們差不多把 NFT 的原理和如何實現走了一遍,只剩下 deploy 還沒示範。但在這討論一個問題,NFT 到底是不是像一般文章講的,唯一且不可變?

技術上來說,他是有唯一性,但可能還是可變。為何?原因就出在 Metadata 存放的方式。

ERC-721 允許 Metadata 存放在外部,主要是區塊鏈上的儲存空間很貴,若只在 contract 上存一個 tokenURI 指向 Metadata 位址將會大幅減少成本。但卻沒有規定 tokenURI 指向的檔案是放在中心化的機器,如 AWS S3,或者是去中心化且不可變的 IPFS。這邊就有人為可以操作的空間,讓同一張 NFT 在 OpenSea 看到的是 A 圖,但是到其他 NFT 交易平台看到的是 B 圖,怎麼做到?

修改 Metadata 即可!先上傳第一版 Metadata json 讓他 image 指向 A 圖,並第一次前往 OpenSea 打開該 NFT 頁面,此時 OpenSea 抓取第一版 Metadata 知道要顯示 A 圖,之後就會 cache 起來。

接下來上傳第二版 Metadata json 到同樣位置蓋掉舊資料,讓他指向 B 圖,此時到另一個 NFT 交易平台 Rarible 開啟頁面,他抓的 Metadata 就會讓他顯示 B 圖,成功讓同一張 NFT 在兩個平台顯示不同的圖。

就連 OpenSea 和 Rarible 也提供 refresh metadata 的選項給使用者,暗示著 Metadata 的可變性!

可以由網址的 contract address 和 token id 確認上圖與下圖在鏈上是同一張 NFT,但變更 metadata 後點選 Refresh Metadata,馬上變回 OpenSea 看到的那一張!

這到底是不是 Bug?從 EIP-721 的頁面看起來其實是預期中的行為,原文中說到:

The URI MAY be mutable (i.e. it changes from time to time). We considered an NFT representing ownership of a house, in this case metadata about the house (image, occupants, etc.) can naturally change.

我們可以把 NFT 看成是房產權狀,Metadata 就像是房子,現實生活中擁有的房子是會自然變化的(如老舊斑駁),因此 Metadata 也允許隨時間變化。

但這邊 tricky 的是,真實世界中的房子不會被偷天換日,比如本來是透天被換成大樓,但是 NFT 卻可以辦到,甚至被 contract owner 的人控制,NFT 實質擁有者卻無能為力。因此 NFT 允許可變性這件事蠻值得思考的。

NFT 市場另一個火熱的玩法是盲盒,其實也是利用一樣的原理,透過改變 Metadata 來實現。在尚未打開前,回傳未打開圖像的 Metadata 檔案位置,打開後則回傳真正的 Metadata 位置。

另外一點是,我們透過中心化的伺服器,如 OpenSea 或 Rarible,才能看到去中心化的 NFT 「長什麼樣」,如果中間真的要作惡,其實也很難防範。這也是為什麼 NFT 詐騙很多。

所以到底 NFT 有沒有所謂的不可變、稀有價值,這就是看擁有的人和大眾怎麼去解讀他。當大家覺得有價值時,他就有價值,大概是這種感覺。

不過以我們這邊的做法,把 Metadata 和圖片都上傳到 IPFS,一但上傳就不能變更,想要修改只能刪掉舊的並上傳新的。但上傳新檔案的 CID 肯定不一樣,可以阻止前面抽換 Metadata 的手法。同時只要 contract 的寫法能保證 mint 後的 tokenURI 不能改變,這樣透過 IPFS host 的方式就能實現真正不可變的 NFT。

到這 Metadata 已經準備的差不多,下一篇我們會把 ERC-721 contract deploy 到 testnet 上,實際去 OpenSea 看看成果!

延伸閱讀:
如何以 Python Brownie 開發 DeFi 應用?- 環境準備
自己寫 NFT 吧!- 實現 ERC-721 – 以 Python Brownie 開發
自己寫 NFT 吧!- Deploy 到 Ganache – 以 Python Brownie 開發
自己寫 NFT 吧!- Deploy 到 Rinkeby testnet – 以 Python Brownie 開發

J
Written by J
雖然大學唸的是生物,但持著興趣與熱情自學,畢業後轉戰硬體工程師,與宅宅工程師們一起過著沒日沒夜的生活,做著台灣最薄的 intel 筆電,要與 macbook air 比拼。離開後,憑著一股傻勁與朋友創業,再度轉戰軟體工程師,一手扛起前後端、雙平台 app 開發,過程中雖跌跌撞撞,卻也累計不少經驗。可惜不是那 1% 的成功人士,於是加入其他成功人士的新創公司,專職開發後端。沒想到卻在採前人坑的過程中,拓寬了眼界,得到了深層的領悟。