之前有寫過一篇文章教大家自己15分鐘寫 ERC721 智能合約,這篇文章則說一說 ERC721 的升級版,ERC1155 智能合約。建議大家先看看關於 ERC721 的那篇文章。
ERC1155 和 ERC721 有什麼區別
開始介紹 ERC1155 智能合約的寫法之前,我覺得先要說說 ERC1155 和 ERC721 到底有什麼區別。 簡單來說,ERC1155 是 ERC721 的升級版,ERC1155 在 ERC721 的基礎上,主要增加或改善了如下功能:
- 同時支持可替換代幣(同質化代幣)和不可替換代幣(非同質化代幣);
- 批量轉賬: 僅需要一次智能合約調用,就可以轉賬多種代幣資產;
- 批量查詢餘額:一次智能合約調用可以查詢多種代幣餘額資料;
- 批量授權:一次智能合約調用可以向指定地址授權多種代幣的使用權;
用再簡單一些的人話來講,就是一張 ERC1155 智能合約,裡面可以同時包含類似 USDT 這種代幣,和類似 MekaVerse ,無聊猿等這種獨一無二的 NFT 代幣。 另外,傳統的 ERC721 合約,如果要轉移 NFT,必須一個一個操作,沒有辦法做到批量轉賬。而 ERC1155,則宛如加入了類似「購物車」的概念,可以一堆代幣一次過轉移。不要小看這個「批量」功能,要知道每次和區塊鏈交互,都要消耗 gas,如果有某些應用場景需要大量,頻繁轉移資產,那 ERC1155 相比 ERC721 則能節約大量的 gas 成本。
我們可以試想一下,假如現在想要創作一款區塊鏈遊戲,裡面有類似 USDT 的可替換同質化代幣以進行金融操作,也需要有各種遊戲裝備,英雄角色等獨一無二不可替換代幣來進行遊戲,這兩種代幣也需要透過一定的機制進行交易互換,這個場景,使用 ERC1155 則可以完美實現。
我們不妨對比一下 ERC721 和 ERC1155 合約的規範接口:
ERC721
function balanceOf(address _owner) external view returns (uint256);
function ownerOf(uint256 _tokenId) external view returns (address);
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
function approve(address _approved, uint256 _tokenId) external payable;
function setApprovalForAll(address _operator, bool _approved) external;
function getApproved(uint256 _tokenId) external view returns (address);
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
ERC1155
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
function balanceOf(address _owner, uint256 _id) external view returns (uint256);
function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory);
function setApprovalForAll(address _operator, bool _approved) external;
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
可以明顯看到,ERC1155 在原有 ERC721 基礎上增加了很多 batch 的接口。再看看 OpenZeppelin 中的 ERC1155 實現,其中的 mint function 增加了 amount
參數, 不難想象,如果 amount = 1
, 則代表這個代幣總共只發行一個,那這個代幣就是類似 ERC721 的 NFT 了。
function _mint(
address account,
uint256 id,
uint256 amount,
bytes memory data
) internal virtual {
require(account != address(0), "ERC1155: mint to the zero address");
address operator = _msgSender();
_beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data);
_balances[id][account] += amount;
emit TransferSingle(operator, address(0), account, id, amount);
_doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data);
}
說完了基本概念,下面我們就來 get your hands dirty,親手嘗試一下寫一個 ERC1155 智能合約,你會發現,其實遠比你想象的簡單。
15分鐘快速建立 ERC1155 智能合約
OpenZeppelin
我們這個示例,依舊使用 OpenZeppelin 的合約為範本。OpenZeppelin 是一個開源的智能合約倉庫,上面已經有許許多多各種各樣的智能合約範本,包括 ERC721,ERC1155 等智能合約。因此我們只需要基於 OpenZeppelin 的現有合約,擴展我們自己特定的功能即可。
安裝 OpenZeppelin Contracts
確保已經安裝最新版的 NodeJS,運行下面的指令:
npm install @openzeppelin/contracts
之後在當前目錄下就會安裝好 OpenZeppelin Contracts 的檔案。
使用 Remix IDE
Remix 是 Ethereum 官方提供的 IDE 。包含完整的編譯器、執行合約、發布合約等等的功能。無須安裝,只要用瀏覽器開啟 https://remix.ethereum.org/ 即可。
Remix 默認情況下會將所有資料存儲在瀏覽器的 Local Storage 中,所以當你清除瀏覽器緩存快取,或者在另外一台電腦打開 Remix,你的資料便會丟失。 Remix 也提供了使用本地文件系統來進行存儲的功能,但需要安裝一個 remixd 本地程式,具體方法可以參考這裡。
當你安裝好 remixd 後,便可以透過運行下面的指令來建立 Remix 和本地檔案的連結:
remixd -s <absolute-path-to-the-shared-folder> --remix-ide <your-remix-ide-URL-instance>
撰寫智能合約
完成上面的步驟後,我們便可以開始建立自己的 ERC1155 合約了。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol";
contract Marvel1155 is ERC1155PresetMinterPauser {
uint256 public constant CAPTAIN_AMERICA = 0;
uint256 public constant THOR = 1;
uint256 public constant IRON_MAN = 2;
uint256 public constant SPIDER_MAN = 3;
constructor() ERC1155PresetMinterPauser( "https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json") {
_mint(msg.sender, CAPTAIN_AMERICA, 10**18, "");
_mint(msg.sender, THOR, 1, "");
_mint(msg.sender, IRON_MAN, 5, "");
_mint(msg.sender, SPIDER_MAN, 10, "");
}
}
以上的代碼便是利用 OpenZeppelin 的 ERC1155 合約建立,我們只需要非常簡單的幾行代碼,便可以完成一個 ERC1155 智能合約。
簡單解釋一下以上代碼的意思:
uint256 public constant CAPTAIN_AMERICA = 0;
uint256 public constant THOR = 1;
uint256 public constant IRON_MAN = 2;
uint256 public constant SPIDER_MAN = 3;
上面這段代碼,是定義了這個合約中有幾種「類型」的代幣。(當然,直接在 mint 的時候傳入自定義類型也是可以的😂)
constructor() ERC1155PresetMinterPauser( "https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json") {
_mint(msg.sender, CAPTAIN_AMERICA, 10**18, "");
_mint(msg.sender, THOR, 1, "");
_mint(msg.sender, IRON_MAN, 5, "");
_mint(msg.sender, SPIDER_MAN, 10, "");
}
上面這段代碼,則是「構造器」,在智能合約發佈的時候會被調用,挖掘了4款不同的代幣,每個代幣的發行總量分別是 10 18,1,5,10。因此可以理解為,THOR 這個代幣,就是一個 NFT,而其他的代幣,則是類似 ERC20 的同質化代幣。
「構造器」中傳入的 https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json
是代幣對應的 URI,這個和 ERC721 的概念是一樣的,OpenSea, Rarible 等 NFT 市場,會訪問這個 URI,去取得代幣的 metadata,下面我們會再講到。
編譯,發布智能合約
編輯完成後, 就可以切換到 Compile 面板進行編譯。留意要選擇正確的編譯器版本,編譯器版本要和你合約中聲明的版本相符合,否則無法編譯。
編譯完成後,便可以準備發布(deploy)了。切換到 Deploy 面板。此時我們要在左上角的 Environment 中選擇【Injected Web 3】,此時便會彈出 MetaMask 進行連結。如果還沒有安裝 MetaMask,可以先在 Chrome Web Store 中安裝。
我們開始嘗試時一般都會選擇在測試網路(testnet)中進行,測試網絡和 Ethereum 主網在技術上沒有區別,但在測試網路中不需要花費真實的 ETH ,而是使用測試網路中的 ETH。一般我們開發的 Blockchain 程式,都要先在測試網路上做全面測試,之後才在主網中上線。要知道 Ethereum 主網的 Gas Fee 可高的驚人。
Ethereum 的測試網路有很多,例如 Ropsten, Rinkeby,Goerli 等等。為了能在 OpenSea 上測試,我們選擇 Rinkeby 測試網路(OpenSea 的testnet 預設為 Rinkeby 測試網路)。
使用測試網路,記得要在 MetaMask 中選擇 Rinkeby Test Network。
在 Ethereum 網路上任何導致更改數據的動作都需要花費 ETH,因此發布智能合約自然需要 ETH。如果沒有 testnet 的 ETH 可以在網上找到很多 testnet faucet 可以免費獲得 testnet 的 ETH ,例如這裡。一切就緒後,就可以按下 【Deploy】,開始發布你的智能合約,中間會彈出 MetaMask 進行付款確認。
發布完成後,Remix 會顯示對應的 Transaction Hash,例如我自己上面的例子,對應的 Transaction 可以在 https://rinkeby.etherscan.io 上看到,裡面亦可以看到我們建立的 ERC1155 智能合約地址。
因為我們的示例合約中在構造器( constructor ) 中已經 mint 了4 種代幣,因此當合約發佈後,在 OpenSea 中已經可以即時看到。
Mint NFT
回到 Remix,可以看到 Deployed Contracts 中已經有我們剛才發布的智能合約,點開他之後可以看到裡面有一個 mint 的 Function,現在我們就可以調用這個 Function 來 Mint NFT 了。這個 mint function 需要傳入四個參數,分別是擁有者地址
,token id
,發行數量
和附加數據
。附加數據在 ERC1155 規範中並沒有指定用途,因此這個參數可以根據自己的需要來傳遞任何數據,如果沒有的話傳遞空值即可。
當 mint 完成後,稍等一段時間便可以在 OpenSea 中看到對應的 NFT 已經誕生了!
到這裡其實我們已經完成了一個最基本的 NFT 的建立。
如果你是照抄我上面的例子😏,你 mint 出的 NFT 應該已經有圖案,名稱了。但你可能會問假如我要改 token 的名稱,圖像,屬性要怎麼做呢?
原來 NFT 對應的圖像以及其他屬性是由 NFT 的 Metadata 決定,要想 NFT 在 OpenSea 等平台 中顯示圖像和其他屬性,還需要指定該 NFT 的 Metadata。
我上面的例子之所以會有圖像,因為 https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json
這個地址會對應傳入的 token id
返回該 token 的 metadata, 而裡面則包括了 token 的圖像,名稱,描述等資料。
ERC721 Metadata
根據 OpenSea 上的 Metadata 標準,OpenSea 會調用我們智能合約中的 uri
function,並傳入 tokenID,而這個 function 需要返回一個 HTTP 或 IPFS URL,這個 URL 則必須返回一個 JSON 格式的數據,例如我例子中的這個,而這個 JSON 則定義了我們 NFT 的各種屬性。
用我們上面寫的智能合約為例,留意我們在 constructor 中有如下寫法:
constructor() ERC1155PresetMinterPauser( "https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json") {
...
}
其中 https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json
便是指定了我們 token 的 URI,當調用 uri
function 時,我們的合約就會 return https://api.frank.hk/api/nft/demo/1155/marvel/{id}.json
。{id}
是一個「變數」, NFT 市場會將這個變數替換為對應的 token id 。
當這個 Metadata 設置好後,再 mint 出的 NFT 就會帶有圖像等屬性了。
上面說過, ERC1155 和 ERC721 最大的不同,就是 ERC1155 可同時支援可替換代幣(同質化代幣)和不可替換代幣(非同質化代幣),這點我們可以在 OpenSea 中清楚的看到。
上面這個例子,因為 Iron Man 這個 token , mint 時發行量輸入了 5 , 而 Thor 這個 token 發行量僅為 1,所以我們看到 OpenSea 上的顯示會有不同。Thor 的顯示,則和 ERC721 的 NFT 類似。
在出售的時候,也會多出了數量選擇。
以上就是一個超級簡單的自己寫代碼製作 ERC1155 NFT 的教學了。如果有任何討論,或是錯誤指正,歡迎聯絡我不吝賜教。
感謝閱讀。