NFT 雖然是架構在去中心化的區塊鏈上,但我們查看或交易的時候,依然習慣前往中心化的 OpenSea 或者 Rarible 等平台。如果想從 Etherscan 直接查看,卻只能看到 contract 的狀態,如 token 擁有者或是轉移歷史等。依然無法看到「眼睛想看」的 JPG。能夠透過 Web3.js 簡單搭建一個去中心化的網站來達成這件事嗎?這篇文章即是分享實驗的經過。
目錄
Web3.js 是什麼?
Web3.js 是以太坊官方推出與區塊鏈溝通的套件,將 RPC-API 封裝後提供給 Javascript 使用。
但他只是 API 封裝,所以需要區塊鏈節點(Server)才能運作。可以透過瀏覽器錢包 injected provider,或是使用第三方的 RPC-API,以及使用自己本機架設的節點 API 等方式來操作。
import Web3 from 'web3';
// 使用錢包 inject 的 provider,若無 givenProvider 則為 null
web3 = new Web3(Web3.givenProvider)
// 使用第三方 RPC-API
web3 = new Web3('https://mainnet.infura.io/v3/...')
// 使用本機節點 RPC-API
web3 = new Web3('ws://localhost:8545"')
如何透過 Web3.js 查看 NFT?
既然是透過 Web3.js 跟 Smart Contract 互動,我們只能使用 Contract 現有的方法來實現我們的功能。
這個 DApp 需要有以下功能:
- 能夠取得 NFT 基本資訊(name、symbol、total supply)
- 能夠取得個別的 tokenURI 與 metadata
- 將資料以卡片和圖片的方式顯示在頁面上
- 有 token 個人頁面,列出所有 metadata 上的資料
為了方便開發,其中做了幾個假設:
- token id 起始值為 1
- token id 是單調遞增
- 僅支援 ERC-721 實作的 NFT
看了看需求與 ERC-721 能用的方法如下:
- 透過
name()
、symbol()
、totalSupply()
等方法取得基本資訊 - 透過
迭代
所有 token id 的方式,使用ownerOf()
、tokenURI()
取得每張 NFT 的擁有者和 metadata 存放路徑 - 分別使用
tokenURI()
回傳的路徑去抓取 metadata
// 取得 name
contract.methods.name().call().then((ret) => ...)
// 取得 symbol
contract.methods.symbol().call().then((ret) => ...)
// 取得 totalSupply
contract.methods.totalSupply().call().then((ret) => ...)
// 取得 ownerOf
contract.methods.ownerOf(tokenId).call().then((ret) => ...)
// 取得 tokenURI
contract.methods.tokenURI(tokenId).call().then((ret) => ...)
詳細的實作可以參考 這邊 ERC721.ts 和 這邊 TokenURIService.ts。
這種方法非常沒有效率,一個 token 平均要三次 request 才能取得必要的資料(分別是取 owner、tokenURI、metadata),違反我們設計前後端溝通的模式。如果一百張就要 3000 次 request!但沒辦法,若要做到去中心化,只能直接與 Smart Contract 溝通,而當初 ERC-721 的介面比較像是提供給詢問 單一 token
使用。因此要做類似 NFT 交易平台的清單頁面,只能透過多次呼叫實現。
為了避免一次性大量呼叫,頁面必須做分頁,並且只發 request 抓回必要的資料,減少效能消耗。可參考 NftSeriesComponent 實作。
如何解決 CORS 問題?
若 metadata 是託管在中心化的伺服器,瀏覽器直接去打會發生 CORS error,因為 metadata 所在的 domain 一定與 NFT Viewer app 不一樣,對方也不會幫忙在 response header 增加 Access-Control-Allow-Origin
等等允許跨網域請求的 header,怎麼辦呢?
首先思考為何 NFT 平台他們抓資料時不會發生此問題?因為 CORS 只發生在瀏覽器!瀏覽器在每一個 response 送回後會做 CORS 檢查。
所以只要有一個中間人:
- 接收 request 並轉發到目標伺服器
- 得到 response 後自行加上 CORS 所需的 header
- 吐回瀏覽器即可
即所謂的 CORS proxy。
本來想自己用 Django 架,但查了下已經有高手用 nodejs 開發好了
簡單用他 demo 的 server.js 就可以跑起來,完全無痛!
託管在 IPFS 的檔案如何取得?
有些 metadata 或圖片會託管在 IPFS 上,通常以 ipfs://
開頭,後面接上一串 CID。此時將 CID 改接在 Public IPFS Gateway 即可,如
https://cloudflare-ipfs.com/ipfs/<CID>
https://ipfs.fleek.co/ipfs/<CID>
實做出來的 DApp 有完全去中心化嗎?
個人覺得沒有完全,因為
- Web3.js 使用 injected provider 時,通常是透過錢包,而錢包也是透過中心化的 RPC-API server 實現功能
- 若 metadata host 在 IPFS 上,還是透過 Public IPFS Gateway 去取得,依然是中心化 server
- 若 metadata host 在私人 Server 上,本身已經中心化,還需過一個自架的中心化 CORS Proxy 解決 CORS error 問題
主因還是使用者不會自行架設區塊鏈和 IPFS 節點,只能使用第三方服務來實現。但相對透過 OpenSea、Rarible 等平台來說,已經相對去中心化很多!若使用者能用自己的節點,就能非常接近去中心化了!
這個 DApp 只要 Smart Contract 還在鏈上,就看得到 NFT ,不會被下架,除非有人能夠同時 hack RPC-API server 和 IPFS,否則要被消失還是比較難。
瀏覽效能呢?
和區塊鏈的 RPC-API 溝通上速度非常快,但如果從 IPFS 取得 metadata 或圖片等資料就不一定,只有在 IPFS Gateway hit cache 的時候才能迅速顯示,否則還是會有 IPFS timeout 或速度慢等老問題。相對的從中心化伺服器取資料就非常快速。
如何將 NFT Viewer DApp 託管在 IPFS 上?
既然要做 DApp,當然要把前端 deploy 到 IPFS 上,詳細可以參考 如何用 IPFS 架設去中心化網站?
實際 DApp:
https://nft-viewer-ipfs.koding.work
如果卡了,也可以使用 hosting by Fleek 的版本:
https://nft-viewer.koding.work
原始碼參考:
https://github.com/Jim-Chang/nft-viewer
延伸閱讀:
實測!用 Unstoppable Domains 鑄造區塊鏈域名並架站
用前端框架開發 IPFS Web3 DApp 有哪些坑?
如何用 IPFS 架設去中心化網站?
自己寫 NFT 吧!- 實現 ERC-721 – 以 Python Brownie 開發