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 需要有以下功能:

  1. 能夠取得 NFT 基本資訊(name、symbol、total supply)
  2. 能夠取得個別的 tokenURI 與 metadata
  3. 將資料以卡片和圖片的方式顯示在頁面上
  4. 有 token 個人頁面,列出所有 metadata 上的資料

為了方便開發,其中做了幾個假設:

  1. token id 起始值為 1
  2. token id 是單調遞增
  3. 僅支援 ERC-721 實作的 NFT

看了看需求與 ERC-721 能用的方法如下:

  1. 透過 name()symbol()totalSupply() 等方法取得基本資訊
  2. 透過 迭代 所有 token id 的方式,使用 ownerOf()tokenURI() 取得每張 NFT 的擁有者和 metadata 存放路徑
  3. 分別使用 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 檢查。

所以只要有一個中間人:

  1. 接收 request 並轉發到目標伺服器
  2. 得到 response 後自行加上 CORS 所需的 header
  3. 吐回瀏覽器即可

即所謂的 CORS proxy。

本來想自己用 Django 架,但查了下已經有高手用 nodejs 開發好了

cors-anywhere

簡單用他 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 有完全去中心化嗎?

個人覺得沒有完全,因為

  1. Web3.js 使用 injected provider 時,通常是透過錢包,而錢包也是透過中心化的 RPC-API server 實現功能
  2. 若 metadata host 在 IPFS 上,還是透過 Public IPFS Gateway 去取得,依然是中心化 server
  3. 若 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

能夠用 Web3.js 做去中心化的 NFT Dapp 嗎?
輸入合約地址後即可查看 NFT
能夠用 Web3.js 做去中心化的 NFT Dapp 嗎?
也可以看周董的 NFT

延伸閱讀:
實測!用 Unstoppable Domains 鑄造區塊鏈域名並架站
用前端框架開發 IPFS Web3 DApp 有哪些坑?
如何用 IPFS 架設去中心化網站?
自己寫 NFT 吧!- 實現 ERC-721 – 以 Python Brownie 開發

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