本文檔內容基於 ERC-3525 參考實現 1.1.0 版(2022 年 12 月發布)。
作者: Ethan Tsai, Alvis Du, Mike Meng
ERC-3525 標準是以太坊社區批准通過的半勻質化通證(Semifungible Token, 亦稱為半同質化通證,簡稱 SFT)標準,由 Solv Protocol 提出。
ERC-3525 標准定義了一類新型的數字資產,具有以下突出優勢:
- 與 ERC-721 標準兼容,具有唯一 ID 和可視化外觀,可複用現有的大量 NFT 基礎設施;
- 可拆分、可合併、可計算;
- 具有賬戶特徵,可以容納其他數字資產,如 ERC-20 通證、NFT 等,並支持在若干 SFT 之間的轉賬操作;
- 可以對外觀、功能、資產存儲、鎖定、轉賬等各方面進行編程,並且為元數據的結構化進行了特別的優化,以支持動態變化、複雜金融邏輯等高級功能的開發。
由於具有以上的優勢,ERC-3525 特別適合用來描述金融工具、數字票證、數字合同等高級數字資產,同時也正在被試用於 Web3 虛擬物品、動態 NFT 藝術品、虛擬裝備、真實世界資產(RWA)通證化等領域,得到了非常廣泛的關注。
Solv Protocol 已經將 ERC-3525 參考實現開源,為有興趣了解和快速嘗試這一全新通證技術的開發者提供有力的支持。這一參考實現同時以開源代碼庫和 NPM 模塊包的形式提供,開發者可以以此為起點,在這個參考實現代碼的基礎之上通過改寫和擴展,開發自己的 ERC-3525 應用。
本文檔引導讀者安裝、配置和部署 ERC-3525 官方參考實現,並引導讀者在此基礎上開發一個簡單的 ERC-3525 通證合約。這個合約沒有任何特別的功能,但開發、測試和部署這個合約的過程卻是通用的。開發者如果了解和熟練掌握了這個開發過程,就能夠在此基礎上開發複雜的、具有業務功能的 ERC-3525 通證合約了。
本文檔內容基於 ERC-3525 參考實現 1.1.0 版(2022 年 12 月發布)。
預備知識和技能
ERC-3525 參考實現是基於 Hardhat 框架、以 Solidity 語言為主開發的。我們推薦讀者在學習 ERC-3525 開發之前,首先掌握以下知識和技能:
- Solidity 語言和 EVM 智能合約開發的基本知識
- Hardhat 智能合約開發框架的基本實用技能
當然,要使用 Hardhat 框架,也必須對於 JavaScript 或者 TypeScript 語言有基本的了解。本文檔使用 TypeScript 進行介紹,但對於有經驗的開發者來說,基於本文檔介紹的內容,很容易可以用 JavaScript 完成相同的工作。
我們推薦對於 Hardhat 不熟悉的讀者首先通過 Hardhat 的官方文檔 ( https://hardhat.org/docs ) 來熟悉這一流行的智能合約開發框架。
快速入門
1. 開發環境
建議讀者在 macOS 或 Linux 的命令行環境下進行 ERC-3525 開發。如果讀者使用 Windows,我們強烈建議讀者首先安裝 Windows Subsystem for Linux(WSL),然後在 WSL 環境中進行如下操作。
讀者可以選擇自己喜歡的任何一種代碼編輯工具來編寫代碼,但我們推薦使用 Visual Studio Code,因為 Hardhat 的開發者 Nomic Foundation 為 Visual Studio Code 開發了一款 Solidity 插件,可以幫助提升 Solidity 和 Hardhat 開發效率。
此外,Hardhat 開發中大量使用 JavaScript 或者 TypeScript 編寫測試用例,Visual Studio Code 本身對於 JavaScript 和 TypeScript 就提供了良好的支持。
2. 創建 Hardhat TypeScript 項目
首先在命令行環境下通過如下命令準備項目目錄。本示例項目名稱為 erc3525-getting-started。
mkdir erc3525-getting-startedcd erc3525-getting-startednpm init -ynpm install --save-dev hardhat
在命令行輸入以下命令(以 MacOSX 為例)
npx hardhat
將看到以下界面
選擇 “Create a TypeScript project” 後,Hardhat 會提示若干問題,讀者直接通過回車選擇缺省選項即可。
全部選擇完畢後,系統自動執行一系列安裝和準備工作。結束後,使用 Visual Studio Code 打開目錄,你可以看到如下項目結構:
3. 引入和安裝 ERC-3525 參考實現模塊包
下面,通過 npm 命令在當前目錄安裝 ERC-3525 參考實現
npm install @solvprotocol/erc-3525@latest
由於我們需要用到 OpenZeppelin 的 String 庫,因此需要使用以下命令安裝 OpenZepplin:
npm install @openzeppelin/contracts@latest
安裝完畢之後,可打開 package.json 文件,應該能夠看到 @solvprotocol/erc-3525 相關信息,表明已經成功安裝。
4. 編寫智能合約
為了簡單起見,我們規避複雜的業務邏輯,以一個最簡單的應用案例來講解 ERC-3525 的代碼開發過程。這個案例中,我們創建一個最簡單的 ERC-3525 通證,它只具備 ERC-3525 的基本功能,沒有額外的功能。不過我們將為它創建一個 “外表”,使它可以用 SVG 動態圖像來顯示內部的狀態。
在 Hardhat 項目創建過程中,自動添加了一個實例代碼文件 Lock.sol。本範例中不需要這文件,因此首先請刪除 contracts/Lock.sol,並在 contracts 目錄中新建文件 ERC3525GettingStarted.sol,代碼如下:
// SPDX-License-Identifier: MITpragma solidity ^0.8.9;import"@openzeppelin/contracts/utils/Strings.sol";import"@solvprotocol/erc-3525/ERC3525.sol";contract ERC3525GettingStarted is ERC3525 { using Strings for uint256; address public owner;constructor(address owner_) ERC3525("ERC3525GettingStarted", "ERC3525GS", 18) { owner = owner_; }function mint(address to_, uint256 slot_, uint256 amount_) external {require(msg.sender == owner, "ERC3525GettingStarted: only owner can mint"); _mint(to_, slot_, amount_); }}
在以上代碼中,我們創建了一個新的合約 ERC3525GettingStarted。這個合約從 ERC3525 參考實現合約中派生,其構造函數直接調用 ERC3525 合約的構造函數,傳入合約的全名、符號和小數位數,並為 owner 賦值。我們並且添加了一個 mint() 函數,確保只有 owner 能夠鑄造這個 ERC-3525 通證。具體的鑄造過程,是通過調用 ERC3525 合約當中的_mint() 實現的,這樣我們就復用了 ERC3525 合約的參考實現,得到了一個最簡單的 ERC-3525 通證合約。
有了 ERC-3525 的參考實現,很多基本功能都可以直接調用相應的函數實現,開發者可以只聚焦於業務邏輯和創新功能,這樣就大大簡化了相關的開發。
代碼編寫完畢之後,在命令行執行以下命令進行編譯:
npx hardhat compile
編譯成功結果如下:
5. 編寫測試用例
使用 Hardhat 框架開發智能合約的主要好處之一是可以進行自動化測試。下面我們介紹如何使用 Hardhat 的測試框架對 ERC3525GettingStarted 合約進行自動化測試。
測試代碼集中在 test 目錄下。同樣,我們首先刪除 test/Lock.ts,然後在 test 目錄下新建 ERC3525GettingStarted.ts,代碼如下
import { loadFixture } from"@nomicfoundation/hardhat-network-helpers";import { expect } from"chai";import { ethers } from"hardhat";describe("ERC3525GettingStarted", function () {// We define a fixture to reuse the same setup in every test.// We use loadFixture to run this setup once, snapshot that state,// and reset Hardhat Network to that snapshot in every test.asyncfunction deployGettingStartedFixture() {// Contracts are deployed using the first signer/account by defaultconst [owner, otherAccount] = await ethers.getSigners();const GettingStarted = await ethers.getContractFactory("ERC3525GettingStarted");const gettingStarted = await GettingStarted.deploy(owner.address);return { gettingStarted, owner, otherAccount }; } describe("Deployment", function () { it("Should set the right owner", asyncfunction () {const { gettingStarted, owner } = await loadFixture( deployGettingStartedFixture); expect(await gettingStarted.owner()).to.equal(owner.address); }); }); describe("Mintable", function () { describe("Validations", function () { it("Should revert with not owner", asyncfunction () {const { gettingStarted, owner, otherAccount } = await loadFixture(deployGettingStartedFixture);const slot = 3525const value = ethers.utils.parseEther("9.5");await expect( gettingStarted.connect(otherAccount) .mint(owner.address, slot, value)) .to.be.revertedWith("ERC3525GettingStarted: only owner can mint" ); }); }); describe("Mint", function () { it("Should mint to other account", asyncfunction () {const { gettingStarted, owner, otherAccount } = await loadFixture(deployGettingStartedFixture);const slot = 3525const value = await ethers.utils.parseEther("9.5");await gettingStarted.mint(otherAccount.address, slot, value); expect(await gettingStarted["balanceOf(uint256)"](1)).to.eq(value); expect(await gettingStarted.slotOf(1)).to.eq(slot); expect(await gettingStarted.ownerOf(1)) .to.eq(otherAccount.address); }); }); });});
在上面的測試代碼中,我們編寫了一個測試夾具和三個測試用例,分別測試了 owner 的正確性、mint 的操作權限和 mint 操作的功能。這些用例遵循了 Hardhat 中編寫智能合約測試代碼的標準方式,讀者可通過 Hardhat 官方文檔學習,此處不再贅述。
6. 運行測試
下面實際運行測試。方法是在項目主目錄執行如下命令:
npx hardhat test
執行結果如下:
這表明我們的智能合約成功通過了所有三個測試用例。
7. 添加 SVG 圖像
ERC-3525 最初的設計目標是表達複雜的金融資產,特別是數字票據。既然是數字資產,就必須支持可拆分、可合併,能夠像 ERC-20 通證一樣進行各種數學計算。另一方面,ERC-3525 超越 ERC-20 的重要一點,就是具有可視化的外在形象,唯有如此才能夠向用戶傳達丰富的信息,使復雜數字資產的複雜性能夠被表達出來。這是 ERC-3525 選擇兼容 ERC-721 的主要動機。因此,ERC-3525 支持元數據,並且通過從 IERC721Metadata 接口繼承而來的 tokenURI 函數返回資源的 URL,或者直接返回圖片的內容數據。在 NFT 當中,普遍的做法是將圖片放在鏈外的存儲上,然後讓 tokenURI 函數返回其 URL。這種設計其實有一個非常大的風險,就是在 NFT 出售以後,控制存儲的人可以更換 URL 上的圖片,這樣買家手裡的 NFT 實際上就被篡改了。為了解決這個問題,大多數 NFT 採用了 IPFS 存儲,通過哈希值來確保圖片資源的唯一性。即使如此,也難以防範一些破壞,比如將 IPFS 上存儲的圖片資源刪除。
ERC-3525 的設計初衷是為了表達金融資產,金融資產的信息非常敏感和重要,決不能被更換和刪除。因此,Solv 建議直接將展現層用 SVG 表達,並直接放在鏈上。具體方法就是讓 tokenURI 函數直接返回 SVG 代碼片段,而不是指向圖像資源的鏈接。
在 ERC3525GettingStarted 合約中加入以下函數:
function tokenURI(uint256 tokenId_) public view virtual override returns (string memory) { return string( abi.encodePacked( '<svg width="600" height="600" xmlns="http://www.w3.org/2000/svg">', ' <g><title>Layer 1</title>', ' <rect id="svg_1" height="600" width="600" y="0" x="0" stroke="#000" fill="#000000"/>', ' <text xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_2" y="340" x="200" stroke-width="0" stroke="#000" fill="#ffffff">TokenId: ', tokenId_.toString(), '</text>', ' <text xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_3" y="410" x="200" stroke-width="0" stroke="#000" fill="#ffffff">Balance: ', balanceOf(tokenId_).toString(), '</text>', ' <text xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_3" y="270" x="200" stroke-width="0" stroke="#000" fill="#ffffff">Slot: ', slotOf(tokenId_).toString(), '</text>', ' <text xml:space="preserve" text-anchor="start" font-family="Noto Sans JP" font-size="24" id="svg_4" y="160" x="150" stroke-width="0" stroke="#000" fill="#ffffff">ERC3525 GETTING STARTED</text>', ' </g></svg>' ) ); }
這將生成一張黑色背景的 SVG 圖像,顯示如下:
注意,其中 Slot、TokenId 和 Balance 的數值都是直接從 ERC-3525 通證的當前狀態中提取的。
8. 部署到本地節點
Hardhat 框架自帶一個以太坊本地節點的實現,特別針對開發過程中的需求做了不少優化。我們推薦在開發調試過程中將合約部署到這個節點上。
在 deploy 目錄修改 deploy.ts 如以下內容:
import { ethers } from"hardhat";asyncfunction main() {const GettingStarted = await ethers.getContractFactory("ERC3525GettingStarted");const gettingStarted = await GettingStarted.deploy(); gettingStarted.deployed();console.log(`GettingStarted deployed to ${gettingStarted.address}`);}// We recommend this pattern to be able to use async/await everywhere// and properly handle errors.main().catch((error) => {console.error(error); process.exitCode = 1;});
打開一個新的 Terminal,運行 hardhat 內建節點
npx hardhat node
運行結果如下(為節約篇幅,省略其它賬號):
在項目主目錄執行以下命令:
npx hardhat run --network localhost scripts/deploy.ts
執行成功後將看到如下結果。注意紅框的地址部分,後面的交互會用到
智能合約部署之後,可以通過 hardhat console 命令與之進行交互,這是 Hardhat 節點的一個重要優勢,能夠大大簡化測試和調試階段的工作。輸入以下命令:
npx hardhat console --network localhost
交互指令及結果如下:
~/Sources/erc3525-getting-started$ npx hardhat console --network localhostWelcome to Node.js v16.18.1.Type ".help"for more information.> const GettingStarted=await ethers.getContractFactory("ERC3525GettingStarted")undefined> const gettingStarted=await GettingStarted.attach('< 此处替换成你部署的地址,也就是上一图的红框处的地址>')undefined> const [owner, otherAccount] = await ethers.getSigners()undefined> await gettingStarted.mint(otherAccount.address, 3525, 10000){hash: '0x94d428b32da7e66e8f0e2d48a37ddb9072dca54013130d95779495e1e443df2c',...}
讀者可以自行輸入一些 TypeScript 代碼來嘗試與智能合約進行交互。
9. 在 Sepolia 測試網絡上部署
在開發環境下測試和調試完畢之後,就需要部署到測試鏈上了。測試鏈提供了基本等同於主鏈的運行環境,但在上面進行測試和調試無需繳納高昂的 gas 費用。另一方面,有些智能合約的功能必須在測試鏈上才能運行,比如與 Oracle 的交互,在開髮用的虛擬節點上是不支持的。我們這個案例非常簡單,用不到 Oracle,但是作為一個原則,一個智能合約在上主鏈之前,一定是要在測試鏈上運行測試無誤才可以。
以太坊已經於 2022 年 9 月 15 日昇級到 POS,因此之前幾個流行的測試鏈,如 Ropsten, Rinkeby, Kovan 等已經被廢棄。現在主要的兩個測試鍊是 Goerli 和 Sepolia。其中 Goerli 歷史較長,完全開放,比較適合於測試複雜的智能合約,而 Sepolia 較新,由一組確定的驗證者節點組成,不能隨意加入,是當前進行 DApp 開發測試的首選測試鏈。在這個例子中,我們選擇 Sepolia 測試鏈。
為了部署在 Sepolia 測試鏈,讀者需要通過 https://www.infura.io/ 申請 infura API KEY。我們假定讀者已經完成這項工作,下面直接介紹部署的過程。
修改 hardhat.config.ts 如下:
import { HardhatUserConfig } from"hardhat/config";import"@nomicfoundation/hardhat-toolbox";const config: HardhatUserConfig = {solidity: "0.8.17",networks: {sepolia: {url: process.env.SEPOLIA_URL || `https://sepolia.infura.io/v3/${process.env.INFURA_KEY}`,accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], }, }};exportdefault config;
然後在 Terminal 命令行環境中執行以下命令,設置 infura api key 和 private key:
export INFURA_KEY=<YOUR_INFURA_KEY>; export PRIVATE_KEY=<YOUR_PRIVATE_KEY>;
請注意,將<YOUR_INFURA_KEY> 替換成你申請的 infura API KEY,將<YOUR_PRIVATE_KEY> 替換成私鑰。強烈建議測試網和主網的私鑰隔離,不要使用已有的主網私鑰。
在 Sepolia 測試網中進行測試需要準備一些測試幣,即 Sepolia FaucETH。讀者可以到 https://faucet.sepolia.dev/ 去申領一些 FaucETH 以供測試之用。
這些準備工作做好之後,就可以執行腳本進行部署了:
npx hardhat run --network sepolia scripts/deploy.ts
執行成功後,結果如下。請注意紅框中的地址,我們將在下一步中使用到。
10. 鑄造 ERC3525GettingStarted 通證
下面我們來鑄造一個 ERC3525GettingStarted 通證。我們採用的方法是使用 TypeScript 調用合約功能進行通證鑄造,這與在 Web3 DApp 開發中的模式是一致的。
首先在 scripts 目錄下新建文件 mint.ts ,代碼如下:
import { ethers } from"hardhat";asyncfunction main() {const [owner] = await ethers.getSigners();const GettingStarted = await ethers.getContractFactory("ERC3525GettingStarted");const gettingStarted = await GettingStarted.attach('< 部署合约地址>');const tx = await gettingStarted.mint(owner.address, 3525, 20220905);await tx.wait();const uri = await gettingStarted.tokenURI(1);console.log(uri);}// We recommend this pattern to be able to use async/await everywhere// and properly handle errors.main().catch((error) => {console.error(error); process.exitCode = 1;});
請注意,將代碼中< 部署合約地址> 替換成上一節紅框中的地址。
最後,執行以下命令:
npx hardhat run --network sepolia scripts/mint.ts
這樣,我們就成功的鑄造了一張 ERC3525GettingStarted 通證。
怎麼確認這一點呢?可以到 Sepolia Etherscan ( https://sepolia.etherscan.io/ ) 上去查看鑄造出來的 token。在瀏覽器地址欄裡輸入:
<a href=”https://sepolia.etherscan.io/address/https://sepolia.etherscan.io/address/< 部署合約地址>
請注意,將< 部署合約地址> 替換成上一節紅框中的地址。
恭喜你!如果一切順利,你就成功的開發和部署第一個 ERC-3525 通證了,可以對它進行各種新的操作了,比如拆分、合併、在兩個通證之間轉賬,等等,趕快嘗試一下吧!
本文完整的示例代碼參見 GitHub(https://github.com/solv-finance/erc3525-getting-started)。
進階學習
本教程對於 ERC-3525 半勻質化通證(SFT)應用開發的過程進行了簡明扼要的闡述。讀者可以由此出發,開發具有豐富功能和高級外觀的 SFT。當然,如果想要深入學習 ERC-3525 的知識和開發技術,這只是一個起點,我們推薦您從以下幾個方面入手深入學習:
- 閱讀 ERC-3525 白皮書(https://whitepaper.sftlabs.io /SFT%20Whitepaper.pdf)
- 研究 ERC-3525 參考實現(https://github.com/solv-finance/erc-3525)
- 研究 SFTLabs 官方提供的 Showroom 案例(https://showroom.sftlabs.io/showroom/)
- 研究 ERC-3525 技術專家開發的以太幣現金鈔案例 Crypto Notes ( https://cryptonotes.fun/ )
我們也將繼續發表一系列文章和教程來幫助開發者掌握 ERC-3525 技術。
免責聲明:作為區塊鏈信息平台,本站所發布文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。文章內的信息僅供參考,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。