随着 DeFi 的爆炸式增长,DApp 开发工具、ERC20 合约调用、ethers.js、智能合约测试、token 单位换算 等关键词已经成为开发者日常搜索的高频词。本文带你一次性吃透 ethers-token 这个小而美的库,看看它是如何用“人类语言”解决 token 交互痛点的。
为什么需要 ethers-token?
在 Ethereum 上做交互,最常见的动作无非三件事:
- 给用户看 1 DAI 就是 1 DAI,而不是
1000000000000000000; - 给用户转 100 USDC 就是 100 USDC,而不是
100000000; - 授权、查余额、交换资产时不用再写冗长的 decimals 乘除。
然而现实往往是:
- 初始化 token 合约对象要写很多
let foo: ERC20的“空壳变量”; - 18 位 / 6 位 decimals 的乘除搞得脑壳疼;
- 同一个测试脚本里「合约对象」和「可读金额」的思维模型来回切换。
ethers-token 用“一句话建对象、一两个函数调授权”的方式,把问题打了个对折。
没有 ethers-token 之前的真实痛点
下面是一段经典(折磨)场景的精简代码:
场景:在本地 Hardhat 叉子节点,用 WETH/USDC 池子做 100 DAI 兑换。
// L19–L20:先声明!再去 hook 里填!
let DAI: Contract, USDC: Contract, UniswapV2Router: Contract;
before(async () => {
DAI = await ethers.getContractAt('IERC20', DAI_ADDR);
USDC = await ethers.getContractAt('IERC20', USDC_ADDR);
});
it('swap 100 DAI for USDC', async () => {
// L30, 35:单位换算写死人
const amountIn = ethers.utils.parseUnits('100', 18);
const amountOutMin = ethers.utils.parseUnits('99', 6);
await DAI.approve(UNISWAP_ROUTER, amountIn);
await UniswapV2Router.swapExactTokensForTokens(
amountIn,
amountOutMin,
[DAI_ADDR, USDC_ADDR],
signer.address,
deadline
);
});问题总结:
- 变量声明散布:测试逻辑被
let foo: Contract污染。 - 单位换算难记:100 个 DAI=
100 * 10^18,100 个 USDC=100 * 10^6。 - 语义割裂:我们看的是“100 DAI”,代码里全是 BigNumber 。
有了 ethers-token 以后的世界
只需两段代码,世界瞬间清净。
// token.config.ts
import { Token } from 'ethers-token';
import { providers } from 'ethers';
const provider = new providers.JsonRpcProvider();
export const DAI = Token.erc20({
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
provider,
symbol: 'DAI',
decimals: 18,
});
export const USDC = Token.erc20({
address: '0xA0b86a33E6441d24E6e6c511F8B88e33DBe53C50',
provider,
symbol: 'USDC',
decimals: 6,
});// swap.test.ts
import { DAI, USDC } from './token.config';
it('swap 100 DAI for USDC with ethers-token', async () => {
// L45: approve 直接链上方法 + 语义化数字
await DAI(100).from(signer).approve(UNISWAP_ROUTER);
// L49: swapExactTokensForTokens 的传参更直观
await UniswapV2Router.swapExactTokensForTokens(
DAI(100), // ← 100 DAI
USDC(99), // ← 最少得到 99 USDC
[DAI.address, USDC.address],
signer.address,
deadline,
);
const usdcBalance = await USDC.balanceOf(signer.address);
expect(usdcBalance).gte(USDC(99)); // ← 直接用对象比较
});亮点提炼:
- 同步配置:Token 对象实例可以当作同步常量,放到任意模块。
- 语义化 API:
DAI(100)就是人类能看懂的数量,不需要额外parseUnits。 - 链上方法封装:approve、balanceOf、transfer 都挂在 token 实例上,省代码 30%+。
实战案例:批量授权 & 批量转账
场景:一个空投合约需要给 1000 个赢家分别发放 1 USDC 并提前授权。
传统写法:
const usdcDecimals = 6;
for (const winner of winnerList) {
await USDC.connect(owner).approve(winner, ethers.utils.parseUnits('1', usdcDecimals));
await USDC.connect(owner).transfer(winner, ethers.utils.parseUnits('1', usdcDecimals));
}使用 ethers-token:
for (const winner of winnerList) {
// 1 USDC 值语义化
const value = USDC(1);
await value.from(owner).approve(winner);
await value.from(owner).transfer(winner);
}不仅阅读体验好,还能防止 decimals 写少的低级 Bug。
FAQ:你最关心的 6 个高频疑问
Q1:ethers-token 支持 NFT 吗?
A:目前 0.x 版本主攻 ERC20 & native token;ERC721 已在路线图,可先在 issue 提需求占位。
Q2:和 ethers v6 的兼容如何?
A:最新 1.0 候选版已面向 ethers v6,接口完全一致,直接 npm i ethers-token@next 即可尝鲜。
Q3:测试网配置会不会很繁琐?
A:只需把 provider 指到 Goerli、Sepolia 等测试网 RPC,其余 token 配置仅替换 address 即可。
Q4:会和现有类型定义冲突吗?
A:库内自带 TypeChain 兼容声明,不会与现有 d.ts 冲突。
Q5:gas cost 会飙升吗?
A:ethers-token 只做 JS/TS 封装,实际链上仍是标准合约调用,Gas 与原生写法相同。
Q6:能否一听就会的 1 分钟上手示例?
A:
npm i ethers-token ethers
npx ts-node << EOF
import { providers } from 'ethers';
import { Token } from 'ethers-token';
const provider = new providers.JsonRpcProvider();
const USDT = Token.erc20({ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', provider, decimals: 6 });
console.log(await USDT.balanceOf('vitalik.eth')); // ← 一键查巨鲸余额
EOF结语:工具虽小,价值巨大
对于每天跟 DApp 开发工具、智能合约测试、token 单位换算 打交道的你我,ethers-token 带来的最大收益是——把链上世界翻译成人类语言。
想看到它在更复杂场景下的玩法?
👉 点击解锁更多链上花式 DeFi 组合案例
如果本文帮到你,别吝啬给 ethers-token 仓库点个 ⭐,Issues 区也欢迎一起吐槽和贡献。下一章我们将一起拆解 MetaMask Snap 与 eip-6963 的多钱包支持方案,敬请期待!