DApp 開發工具 ethers-token 介紹

Cyan Ho
Taipei Ethereum Meetup
6 min readJul 22, 2022

--

Photo by Shubham Dhage on Unsplash

在 Ethereum 上開發 DApp 和智能合約(smart contract,以下簡稱合約)時,常常需要經手各式各樣的 token,並且處理各種與 token 相關的事務,像是單位轉換、包裝、轉移和授權等等。隨著 DeFi 生態持續成長,token 的種類也越來越多樣,除了區塊鏈本身運作所需的 native token (原生代幣,如 ETH)之外,還衍生出許多透過智能合約實作的代幣標準,如 ERC20ERC721 等等。

ethers-token 是一個基於 ethers.js 幫助 DApp 開發者以更直覺、更語意化的方式處理 token 的 JavaScript/TypeScript 工具,目前主要支援了 native token 與 ERC20 token 等 fungible token(同質化代幣)的使用情境,未來不排除持續增加支援的 token 種類,如更多的 fungible token 標準、或是 non-fungible token(NFT,非同質化代幣)的應用情境等等。

讓我們先來了解一下,在沒有 ethers-token 之前,開發與測試 DApp 的模樣。

接下來的內容需要讀者對合約開發和測試有基本的概念,並且對 hardhatethers.js 工具有基本的認識。

沒有 ethers-token 之前

由於筆者大多經驗著重在智能合約開發,因此本文將以「在測試環境中與 UniswapV2 合約進行 token 兌換」為例子,來展示 ethers-token 所帶來的改變。

讓我們先來了解一下接下來例子裡會使用到的 UniswapV2 Router 合約的介面:

UniswapV2 Router 介面

swapExactTokensForTokens 是一個在 UniswapV2 進行 token 兌換的方法,其中 amountIn 代表使用者要拿進來交換的 token 數量,對應的 token 地址會指定在 path[0]amountOutMin 代表使用者希望最少能夠兌換出來的 token 數量,對應的 token 地址會指定在 path[path.length — 1]path 為兌換 token 的路徑;to 為 token 兌換出來後要歸誰所有;deadline 為這筆兌換的期限。

以實際的例子來說,假設今天使用者有 100 DAI,想要在 UniswapV2 進行 DAI -> USDC 的兌換,參數的示意大致如下:

在 UniswapV2 兌換 100 DAI -> USDC 參數示意

在對 UniswapV2 介面有基本的了解後,接下來將會使用 hardhat 和它的 forking network 功能,在本地的測試環境模擬 Ethereum 主網的狀態,以實際的 TypeScript 程式碼與 UniswapV2 進行 token 兌換:

沒有使用 ethers-token 與 UniswapV2 進行 token 兌換的測試

由於 token 本身是合約,在 L25–L26 可以看到,為了使用 token 的功能,必須要先建立 token 合約物件,而受限於 mocha(hardhat 內裝的測試框架)的編排模式,非同步(async/await)初始化行為只能放在 hook 或是測試案例中執行,導致在測試案例之間共用 token 合約時,需要先宣告沒有初始值的可賦值的變數(L19–L20),並主動標示適當的型別(無法享受 TypeScript 賦值時的自動型別推斷),最後在允許非同步行為的 hook 中完成賦值。

上述初始化共用變數的方式,會使得測試集(test suite)裡需要宣告許多與測試目標不相關的變數;而需要主動標示這些沒有初始值的變數型別,會強迫開發者在測試檔案中引入許多不必要的型別進來。

除此之外,在 L30 和 L34–L35 可以看到過往處理 token 數量的方式,由於 token 面額受到其小數位(decimals)的影響,儘管 1 顆 DAI 與 1 顆 USDC 所代表的面額相同,但背後的數字卻是 1 * 10¹⁸ 與 1 * 10⁶ 的差別,在實作 token 兌換邏輯、或是串接外部流動性池時,開發者需要特別留意小數位的轉換。

總的來說,我們通常會使用貨幣的心智模型來理解 token,但實際在 DApp 處理 token 時,卻需要分為鏈上操作(包含轉帳、授權等動作)以及數量匯兌兩個部分來處理,token 概念的分散使得開發者在撰寫程式時需要同時注意不同面向的細節,增加了許多認知上的負擔。

有 ethers-token 之後

ethers-token 旨在以一個概念完整性的單元來表示 token,提供符合貨幣心智模型的介面,讓開發者以更直覺的方式操作 token。

讓我們來看看前一小節的測試案例在使用 ethers-token 改寫後的樣子:

使用 ethers-token 與 UniswapV2 進行 token 兌換的測試

首先,L13–L23 可以看到,ethers-token 能以同步的方式設置好各種 token 的配置,初始化的過程不受限於非同步(async/await)操作,讓開發者能以更靈活的方式編排程式,甚至可以進一步將 token 抽取到獨立的檔案,以常數的方式 export 出去,讓不同的模組及測試共用。

得益於這樣的好處,測試集 before hook 裡可以省略初始化 token 合約的動作,也不需要額外配置儲存 token 合約物件的變數,讓測試變得簡潔與聚焦,讀者可以比對一下兩個例子中 before hook 的差異。

接著,ethers-token 建構出來的 token 建構子(L13-L23),本身能夠以函式呼叫的方式來建立代表特定數量的 token,例如 DAI(100) 即代表 100 顆 DAI,在程式中等價於 100 * 10¹⁸ 的 ethers.BigNumber,並且兼容於各種 ethers.BigNumber 的使用情境,例如 L49-L50 可以看到 token 建構子創建出來代表 token 數量的物件,能夠直接當作參數來呼叫合約的方法。

最後,ethers-token 產生出來的 token 建構子,以及 token 建構子建立代表 token 數量的物件,皆封裝了許多需要鏈上操作的 token 行為,例如 L45 使用了 DAI(100) 身上的 fromapprove 來代表使用者授權 100 顆 DAI 的額度給 UniswapV2;L57 則使用了 USDC 建構子身上的 balanceOf 方法來查詢使用者的 USDC 餘額。

更多詳細的功能介紹請參考 ethers-token README

結語

ethers-token 是一個基於 ethers.js 的 JavaScript/TypeScript 工具,透過一個概念完整性的單元來表示 token,提供符合貨幣心智模型的介面,幫助 DApp 開發者以更直覺、更語意化的方式操作 token。

筆者大多經驗著重在智能合約開發,對於 DApp 其他部分如前端、後端等等涉略較少,非常歡迎大家在 ethers-token issues 裡交流不同的使用情境 😃

--

--