前提

本文是參考以下這篇教學文,再加上自己遇到的不懂的點,覺得需要做筆記的點,所以寫一篇自己覺得更詳細的文章,並非原創,此文僅筆記,非分享或教學。如有錯誤歡迎指正

使用 Remix,OpenZeppelin 15 分鐘建立 NFT 智能合約

請先看過原文。

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 都是一模一樣的。

這些文件要求我們必須參考這些標準去製作智能合約,但自己寫總有漏洞啊?為了提高安全性已經所以我們可以使用已經製作好的智能合約樣本

只要我們使用這些樣本,就不用再自己去實作那麼多標準,只要針對我們想要調整的地方進行處理就可以了。

安裝方法請參考

Contracts – OpenZeppelin Docs

安裝完之後就有 node_modules 可以用了

關於智能合約的開發這邊不多做贅述,因為我們這次使用的不需要會 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>

成功會如下圖

剛開始還真的不知道怎麼打參數

然後我們開一個資料夾並新增一個合約

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 (星際檔案系統) 服務
將檔案放在這個上面才能確保不可竄改。
下載桌面版本之後建立一個資料夾即可

然後進入剛剛創建的資料夾後
創建一個檔案名稱是 0

piano0.jpg 就是我準備要在JSON裡面用到的鋼琴圖

內容如下的檔案

這些JSON比較複雜,請自行去官方複製簡易版的即可

當然我們 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);
    }
}

合約部分我跟原文稍有不同的原因

  1. 沒有版權聲明會噴 Warning
  2. 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. 主要就是可見性這個用法已經過時了。
  3. Solidity 版本我改成當下最新的
  4. 原文的 Metadata 是使用作者自己的 Domain,這邊我是用 IPFS替代。

之後就可以進到編譯階段了

按下 Compile 即可,如果左下沒噴錯誤且有出現自己的合約名稱和三個按鈕表示成功了。

接下來就可以準備 Deploy 了

這次因為我們環境是選擇 Injected Web3 因為要搭配 MetaMask
所以瀏覽器請先安裝並且註冊 MetaMask

並且確保 Rinkeby Test Network 裡面有錢,如果沒錢的話請到

Rinkeby Authenticated Faucet:https://faucet.rinkeby.io/

在自己的 Twitter 上新增一篇貼文並且貼上自己的測試鏈 ETH 地址,之後用公開的方式發表,之後將推文網址貼上,就可以乞討到 Rinkeby Test Network 上的 ETH 了。

如果是正式鏈就好了

確保安裝完成 MetaMask 且裡面有 ETH 後

準備 Deploy 合約

記得 ENVIRONMENT 要換成 Injected Web3

地址的部分請讓 MetaMask 帶入

其中 ENVIRONMENT 三個的差異請參考這裡

確認環境跟地址還有合約名稱都正確之後,就可以按下 Deploy

按下去之後如下圖

請確認 MetaMask 是在 Rinkeby Test Network (右上)

之後就可以在 MetaMask 上按『確認』

下方就會出現在 rinkeby etherscan 上的 Transaction 網址,可以確認進度

可以看到我們是在建立合約,且是在測試鏈上,已經成功了。

接下來我們就可以鑄造 NFT 囉!

回到 Remix 點開 Deployed Contracts 如下圖找到 mint

這個 mint function 的文件說明在這裡

我們只要填入我們的 ETH 地址,我們就可以成為這個新鑄造的 NFT 的永有者。輸入地址後點擊 mint 按鈕會出現如下

一樣確認沒錯後,按下『確認』按鈕,之後回到 Remix IDE

一樣出現在 rinkeby etherscan 上的 Transaction 網址

這樣就表示成功了,已經將這個 Token 發送到填入的地址。

既然有了 NFT 那要在哪裡才能看得到呢?

OpenSea

簡單來說就是一個 NFT 市場

這是正式鏈網址

但這次我們是發行在測試鏈上的 NFT
所以是以下這個網址

OpenSea: Buy NFTs, Crypto Collectibles, CryptoKitties, Decentraland, and more on Ethereum

進去之後用自己的地址註冊一個帳號就可以看到自己剛剛鑄造的 NFT 了
註冊帳號會自動跳出 MetaMask,這邊就不多說了。

但是這邊怎麼都沒圖片呢?
還記得上面的 Metadata URI 嗎?

ERC721 Metadata

在以太坊官方的 ERC-721 Non-Fungible Token Standard 中我們可以看到標準的 JSON 格式

OpenSea 的 Metadata 標準規格中除了有更多選項之外也有說明 Metadata 對應的欄位。

這邊簡單說明一下
OpenSea 會呼叫的網址是你的 Token URI + Token ID
Token ID 是流水號從 0 開始
可以從我們當初鑄造的時候的交易流水看到

所以我們可以預計這個 NFT 的 Metadata URI 就是

https://ipfs.io/ipfs/QmSGiYVgj24pkj58TWAMkvUgXj7MvLQXU7LmcBBagEzwrG/0

因為我們前面剛剛已經設置好了
所以點進我們剛剛創建的 NFT
按下右上角的 Refresh Metadata 按鈕
接著過幾秒鐘就會出現囉

當然我們也可以將這個 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 又分成兩種情況

  1. 假設今天你預期這個合約會鑄造 100 個 Metadata 不一樣的 NFT,你要先在你的 IPFS 開好資料夾,然後要先把這 100 個 Metadata JSON 檔案開好,然後再去建立合約。因為只要資料夾內容一有變動,因為 IPFS 的特性導致資料夾的 CID 就會跑掉,意思就是你動不了啦。
    當然原本那 100 個也不會受影響,你也改不了。這個缺點就是你就是只能鑄造 100 個 NFT,當你鑄造第 101 個的時候,你當初沒預設他的 Metadata 了,也就是說,你這個東西雖然存在,但沒有 Metadata,但他還是存在!但通常這種會直接在合約裡寫上只能鑄造 100 個 NFT 的規則會比較合理啦。
  2. 這個合約預期鑄造出來的 NFT 是 Metadata一模一樣的,你就可以直接固定一個 IPFS URL,讓之後用這個合約鑄造出來的 NFT 的 Metadata 長一樣。但他本身還是獨一無二的喔!一樣的只是 Metadata!

再說一次我得出的結論

假設達文西畫了 100 幅蒙娜麗莎的微笑,即使他的 Metadata 一樣(name=蒙娜麗莎的微笑),但他們每一幅畫都不一樣!都是獨一無二的!也許在畫的時候微笑的角度不小心多了一點點點,但這幅畫還是蒙娜麗莎的微笑。這就是 ERC721

但兩個一塊錢硬幣,就是一樣的東西,他是同質的,就是 ERC20

頭真痛… NFT到底是不是假議題…