Chou Yu Lin
Full Stack Developer
建立 ERC721 標準智能合約,並打造自己的 NFT
![](https://cdn-images-1.medium.com/max/1024/1*fE_Uyu3r96VT6QSVThxqzA.png)
前提
本文是參考以下這篇教學文,再加上自己遇到的不懂的點,覺得需要做筆記的點,所以寫一篇自己覺得更詳細的文章,並非原創,此文僅筆記,非分享或教學。如有錯誤歡迎指正
使用 Remix,OpenZeppelin 15 分鐘建立 NFT 智能合約
請先看過原文。
NFT?
我參考了幾篇文章,覺得這篇說得滿清楚
看過之後結論大概就是
『可以證明某個東西是屬於某個人的,而且大家都看得到。』
例如藝術品或數位資產,很難證明擁有者,大家手上都有一份,全世界的人電腦裡都有 ABP-548,但版權商要怎麼把這個 ABP-548 賣給一個特定的人?就是發行一個代表 ABP-548 的 NFT,然後在拍賣掉。要是以後有人在沒經過你同意散播 ABP-548,你就可以去法院展示你的 NFT 證明這是你的。
所以說, 就算大家都有這個,但真正的擁有這個的人是我!
例如房產,既然他是非同質化的,這樣以後就不需要房產證明或是地契之類的東西。只要發布一個 NFT 代表某棟房產。之後拍賣掉這個 NFT ,購得者就能證明自己是擁有這個房產的人。沒人能搶走你的房子(前提是你不是活在共產黨的國家)。
OpenZeppelin
這個網站提供一些已經實作完成的智能合約樣本,像是官方 ERC20 Token Standard,但我們這次 NFT 是基於官方 ERC-721 Non-Fungible Token Standard,因為 ERC20 Token 本身是同質化代幣,每個 Token 都是一模一樣的。
這些文件要求我們必須參考這些標準去製作智能合約,但自己寫總有漏洞啊?為了提高安全性已經所以我們可以使用已經製作好的智能合約樣本
只要我們使用這些樣本,就不用再自己去實作那麼多標準,只要針對我們想要調整的地方進行處理就可以了。
安裝方法請參考
![](https://cdn-images-1.medium.com/max/1024/1*EWyWIR3zM1BLIu03B8kNGA.png)
關於智能合約的開發這邊不多做贅述,因為我們這次使用的不需要會 Solidity,如果想學習可以參考 OpenZeppelin 教學。
OpenZeppelin Presets
我們這次要使用的是 OpenZeppelin 的 Presets
這個系列就是 OpenZeppelin 將不同的 ERC 標準集大成已經都實作好,不用寫任何 Solidity 程式碼就可以部署合約。
雖然是 3.x 版本的文件,但應該只是文件尚未更新至 4.x,因為我的 npm 安裝是 4.1 版本也可以使用 Presets。
因為我們是 ERC721 ,所以這次要用到的是其中的
ERC721PresetMinterPauserAutoId
Remix IDE
這是一款以太坊官方提供的線上 IDE,可以編譯、執行、發布合約等等功能
Remix IDE 連結:https://remix.ethereum.org/
Remix IDE 文檔:https://remix-ide.readthedocs.io/en/latest/index.html
但因為他是線上版本的 IDE,因此檔案只是暫存在 LocalStorage,因此需要參考此篇教學讓他與本地進行連結。
簡單說明一下安裝方法
npm 安裝
npm install -g @remix-project/remixd
確認是否安裝成功
remixd -v
Link 資料夾
remixd -s <absolute-path-to-the-shared-folder> --remix-ide <your-remix-ide-URL-instance>
成功會如下圖
![](https://cdn-images-1.medium.com/max/1024/1*HK1ymtrGYpKWK0VJ2viarw.png)
然後我們開一個資料夾並新增一個合約
![](https://cdn-images-1.medium.com/max/1024/1*oToQOQyf2oZmHjyETUnFJw.png)
Workspaces 記得要切到 localhost,資料夾裡面應該會有我們剛剛裝的 OpenZeppelin 的 node_modules
附上我的 package.json
{ "name": "openzeppelin", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "@openzeppelin/contracts": "^4.1.0" } }
『因為剛買一個新鋼琴所以要炫耀一下』
接著只要把合約裡面的
NFT名稱:UU Piano
代幣代碼:UUP
MetaData URI:https://ipfs.io/ipfs/QmSGiYVgj24pkj58TWAMkvUgXj7MvLQXU7LmcBBagEzwrG/
其中 MetaData URI 屆時需要返回一個 JSON,所以不可隨意填寫
這邊是使用 IPFS (星際檔案系統) 服務
將檔案放在這個上面才能確保不可竄改。
下載桌面版本之後建立一個資料夾即可
![](https://cdn-images-1.medium.com/max/1024/1*ffZs7Z9R50u9S5haATFocA.png)
然後進入剛剛創建的資料夾後
創建一個檔案名稱是 0
![](https://cdn-images-1.medium.com/max/1024/1*IG288WZaiTll9M0wbLDkEg.png)
內容如下的檔案
![](https://cdn-images-1.medium.com/max/1024/1*7OXSxA-6x-XZhrkWNRwq-w.png)
當然我們 Metadata JSON 裡面的圖片網址就是 piano0 這張圖片
這樣的意思就是我們只打算鑄造一個 NFT 啦!
合約內容請複製下面的去修改即可,記得合約名稱也要改
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4;
import "../node_modules/@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol";
contract UUP is ERC721PresetMinterPauserAutoId { constructor() ERC721PresetMinterPauserAutoId("UU Piano", "UUP", "https://ipfs.io/ipfs/QmSGiYVgj24pkj58TWAMkvUgXj7MvLQXU7LmcBBagEzwrG/") {} // This allows the minter to update the tokenURI after it's been minted. // To disable this, delete this function. function setTokenURI(uint256 tokenId, string memory tokenURI) public { require(hasRole(MINTER_ROLE, _msgSender()), "web3 CLI: must have minter role to update tokenURI"); setTokenURI(tokenId, tokenURI); } }
合約部分我跟原文稍有不同的原因
- 沒有版權聲明會噴 Warning
- Visibility ( public / external ) is not needed for constructors anymore: To prevent a contract from being created, it can be marked abstract . This makes the visibility concept for constructors obsolete. 主要就是可見性這個用法已經過時了。
- Solidity 版本我改成當下最新的
- 原文的 Metadata 是使用作者自己的 Domain,這邊我是用 IPFS替代。
之後就可以進到編譯階段了
![](https://cdn-images-1.medium.com/max/1024/1*rIMq0wGvv7TqWzdmwALtow.png)
按下 Compile 即可,如果左下沒噴錯誤且有出現自己的合約名稱和三個按鈕表示成功了。
接下來就可以準備 Deploy 了
這次因為我們環境是選擇 Injected Web3 因為要搭配 MetaMask
所以瀏覽器請先安裝並且註冊 MetaMask。
並且確保 Rinkeby Test Network 裡面有錢,如果沒錢的話請到
Rinkeby Authenticated Faucet:https://faucet.rinkeby.io/
在自己的 Twitter 上新增一篇貼文並且貼上自己的測試鏈 ETH 地址,之後用公開的方式發表,之後將推文網址貼上,就可以乞討到 Rinkeby Test Network 上的 ETH 了。
![](https://cdn-images-1.medium.com/max/706/1*rIi1ZyMIAE09woiROXoofA.png)
確保安裝完成 MetaMask 且裡面有 ETH 後
準備 Deploy 合約
記得 ENVIRONMENT 要換成 Injected Web3
地址的部分請讓 MetaMask 帶入
其中 ENVIRONMENT 三個的差異請參考這裡
確認環境跟地址還有合約名稱都正確之後,就可以按下 Deploy
按下去之後如下圖
![](https://cdn-images-1.medium.com/max/1024/1*pEaIHEfwK3t7qJluK6Y2yg.png)
請確認 MetaMask 是在 Rinkeby Test Network (右上)
之後就可以在 MetaMask 上按『確認』
![](https://cdn-images-1.medium.com/max/1024/1*kzhhPTv_CYd9QkWknef1yQ.png)
下方就會出現在 rinkeby etherscan 上的 Transaction 網址,可以確認進度
![](https://cdn-images-1.medium.com/max/1024/1*8Gvjd6sRg6TAoDp_30eqFg.png)
可以看到我們是在建立合約,且是在測試鏈上,已經成功了。
接下來我們就可以鑄造 NFT 囉!
回到 Remix 點開 Deployed Contracts 如下圖找到 mint
![](https://cdn-images-1.medium.com/max/1024/1*h8n0ElVzd0WUsG7_1VCMAA.png)
這個 mint function 的文件說明在這裡
我們只要填入我們的 ETH 地址,我們就可以成為這個新鑄造的 NFT 的永有者。輸入地址後點擊 mint 按鈕會出現如下
![](https://cdn-images-1.medium.com/max/1024/1*dkjbFW3kwgSvUxHFwheBWQ.png)
一樣確認沒錯後,按下『確認』按鈕,之後回到 Remix IDE
![](https://cdn-images-1.medium.com/max/1024/1*e9auHyvTFURH1dEPJe9a7w.png)
一樣出現在 rinkeby etherscan 上的 Transaction 網址
![](https://cdn-images-1.medium.com/max/1024/1*USKjZeSRyZfPRFhIOrYjrw.png)
這樣就表示成功了,已經將這個 Token 發送到填入的地址。
既然有了 NFT 那要在哪裡才能看得到呢?
OpenSea
簡單來說就是一個 NFT 市場
這是正式鏈網址
但這次我們是發行在測試鏈上的 NFT
所以是以下這個網址
OpenSea: Buy NFTs, Crypto Collectibles, CryptoKitties, Decentraland, and more on Ethereum
進去之後用自己的地址註冊一個帳號就可以看到自己剛剛鑄造的 NFT 了
註冊帳號會自動跳出 MetaMask,這邊就不多說了。
![](https://cdn-images-1.medium.com/max/1024/1*Ruts7lH2ybUs5yKL2cSlKQ.png)
但是這邊怎麼都沒圖片呢?
還記得上面的 Metadata URI 嗎?
ERC721 Metadata
在以太坊官方的 ERC-721 Non-Fungible Token Standard 中我們可以看到標準的 JSON 格式
![](https://cdn-images-1.medium.com/max/1024/1*OADZE7QQWCGAPHBE0pqfew.png)
在 OpenSea 的 Metadata 標準規格中除了有更多選項之外也有說明 Metadata 對應的欄位。
![](https://cdn-images-1.medium.com/max/1024/1*fpBlhGAa6qxlTGwRkOp23Q.png)
這邊簡單說明一下
OpenSea 會呼叫的網址是你的 Token URI + Token ID
Token ID 是流水號從 0 開始
可以從我們當初鑄造的時候的交易流水看到
![](https://cdn-images-1.medium.com/max/1024/1*sArslfm8dtguhNIQJsRUrg.png)
所以我們可以預計這個 NFT 的 Metadata URI 就是
https://ipfs.io/ipfs/QmSGiYVgj24pkj58TWAMkvUgXj7MvLQXU7LmcBBagEzwrG/0
因為我們前面剛剛已經設置好了
所以點進我們剛剛創建的 NFT
按下右上角的 Refresh Metadata 按鈕
接著過幾秒鐘就會出現囉
![](https://cdn-images-1.medium.com/max/1024/1*5jj7h0iwEFUSCiacgzSTLw.png)
![](https://cdn-images-1.medium.com/max/1024/1*KCeufVbL-VPQVlsFn9jUIw.png)
當然我們也可以將這個 NFT 在 OpenSea 上面上架販售
或是直接轉送給別人囉!
後記
滿多人都有一個疑問
不是說是 NFT 嗎?不是說獨一無二嗎?
為什麼 Metadata 的 JSON 還可以自架伺服器呢?
沒有強迫使用 IPFS 嗎?
我也苦思這個問題很久得出一些結論
確實 !Metadata 不是固定的!也沒有強迫要固定!甚至 OpenSea 官方教學還有教你怎麼架設 Server 去部署你的 Metadata JSON,文章最下面就有了。
但這邊就出現一個盲區,今天 Metadata JSON 如果沒有放在 IPFS上導致他是可變的,今天要是我交易或是傳送給A這顆 NFT 後,我把我的 JSON Server 關閉或是亂改了,這樣如果A也要賣給別人怎麼辦?這個 NFT 的 Metadata 就會被篡改或是消失,A的 NFT 介紹不就出問題了嗎?
對,沒錯,會出問題!
問題可能會衍生兩種:
1. JSON Server 關閉了,那麼目前 OpenSea的做法是會繼續沿用原本的,所以不會出問題,但今天要是有新的 NFT 市場出來的話,他沒有原本你的 Metadata JSON,那可能就會沒有 NFT 介紹了
2. JSON 內容被改了,那只要按下 Refresh Metadata 按鈕刷新之後介紹就會變了。沒救了。
看起來是不是很慘!
但是出問題的只是你的 Metadata ,只是你的商品介紹而已。
在區塊鏈上,你的 NFT 依然沒變,依然是那個獨一無二且帶有編號的 NFT
話雖如此
這就是為什麼我極力推薦使用 IPFS 的原因
因為他才是不可篡改的
用 IPFS 又分成兩種情況
- 假設今天你預期這個合約會鑄造 100 個 Metadata 不一樣的 NFT,你要先在你的 IPFS 開好資料夾,然後要先把這 100 個 Metadata JSON 檔案開好,然後再去建立合約。因為只要資料夾內容一有變動,因為 IPFS 的特性導致資料夾的 CID 就會跑掉,意思就是你動不了啦。
當然原本那 100 個也不會受影響,你也改不了。這個缺點就是你就是只能鑄造 100 個 NFT,當你鑄造第 101 個的時候,你當初沒預設他的 Metadata 了,也就是說,你這個東西雖然存在,但沒有 Metadata,但他還是存在!但通常這種會直接在合約裡寫上只能鑄造 100 個 NFT 的規則會比較合理啦。 - 這個合約預期鑄造出來的 NFT 是 Metadata一模一樣的,你就可以直接固定一個 IPFS URL,讓之後用這個合約鑄造出來的 NFT 的 Metadata 長一樣。但他本身還是獨一無二的喔!一樣的只是 Metadata!
再說一次我得出的結論
假設達文西畫了 100 幅蒙娜麗莎的微笑,即使他的 Metadata 一樣(name=蒙娜麗莎的微笑),但他們每一幅畫都不一樣!都是獨一無二的!也許在畫的時候微笑的角度不小心多了一點點點,但這幅畫還是蒙娜麗莎的微笑。這就是 ERC721
但兩個一塊錢硬幣,就是一樣的東西,他是同質的,就是 ERC20
頭真痛… NFT到底是不是假議題…