ethers.js的一些使用
写在开头:ethers的官方文档不够详细,如果看不懂,可以去web3的官方文档找对应的函数查看解释
- 首先需要npm安裝ethers
然后再在项目里引入
const ethers = require("ethers") // nodejs import ethers from "ethers" // front-end // 如果是非webpack前端,也可以直接用script标签引入.js文件
关联钱包
const wallet = new ethers.Wallet('your private key') // 这个必须用私钥。还有另一个方式是用助记词
将钱包关联到contract,这样就可以发起签名,进行transfer、approve等操作
const account = wallet.connect(provider) const someContract = new ethers.Contract( 'contract address for example, USDT, BUSD, panca etc...', ['function approve(address spender, uint amount) public returns(bool)', 'function allowance(address owner, address spender) view returns(uint256)'], // 这个数组放你需要用到的abi account )
配置provider,用于链上交互
const provider = new ethers.providers.WebSocketProvider("wss://bsc-ws-node.nariox.org:443") // 示例里的链接为bsc主网的社区wss,测试用可以,大型项目建议使用付费的或者自建full node
例子1:发起approve。注意,await需要在async类型的函数里运行
const gasPrice = await provider.getGasPrice(); // bsc大多时候返回5000000000 (5e9) const approveResult = await someContract.approve( "spender's address", // 允许让谁调用,可以是别人的账号,也可以是诸如pancakeswap的router address等 123456, // 最高0xffff(64个F) { gasPrice: gasPrice, gasLimit: "450000" } // 为什么gasPrice>gasLimit? ) // approveResult包含了此次approve事件的hash等信息
例子2:查看某个approve的授权额
const allowance = await someContract.allowance(wallet.address, "spender's address") // 返回值为uni256的BigNumber,如果是0,用0===allowance依然false,但0==allowance则为true // 如果从来没approve过,则allowance为0
例子3:取消某个approve:
- 方法跟例子1一样,只不过第二个参数授权额度设置为0即可
例子4:根据token0和token1获取它们在LP的pair address
// 想要获取pair address需要先构造对应AMM的factory contract const factory = new ethers.Contract( "AMM's factory address", ["function getPair(address tokenA, address tokenB) external view returns (address pair)"], // factory的abi不止这个,详情查看对应的factory.sol,如果是bsc则在bscscan上查AMM's factory address应该也能查到 account ) const pairAddress = await factory.getPair("token0 address", "token1 address") // 两个address顺序调换不影响
例子5:获取某个token对在LP里的储量
// 想要获得token0和token1在LP里的储量,需要先构造pair contract const pair = new ethers.Contract( pairAddress, ["function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"], // 如果想要更多其它abi,假设是bsc链的pairAddress,可以去bscscan搜,里面直接列出了所有的function account ) const reserves = await pair.getReserves() // reserves.reserve0和reserves.reserve1就是token0和token1在LP里的储量,具体顺序可能需要打印log才知道 // 此时我们就得到大名鼎鼎的X*Y=K了,reserve0和reserve1分别就是X和Y // 利用reserve0/reserve1或者reserve1/reserve0即可得到他们之间互相的swap价格,如果不考虑swap数量的话。如果考虑数量对价格的impact,需要用到下面[例子7]的方式
- 例子6:ethers.utils类的使用:直接看官方文档吧,这个包含了wei和ether等单位转换之类的小工具
例子7:根据tokenIn的数量获取swap价格
// 首先要构建router contract const router = new ethers.Contract( 'router contract of AMM, for example pancake', [ "function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts)", "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)", ], account ) // 然后再设置tokenIn的数量。变量里的in和out分别指放入LP(sell)和从LP取出(buy) const amountIn = ethers.utils.parseEther("123"); // parseEther表示精度为18的token,如果精度不为18需要用另一个parse const amounts = await router.getAmountsOut(amountIn, [ 'tokenIn contract address', 'tokenOut contract', ]); // amounts是一个数组,amounts[0]和amounts[1]跟tokenIn和tokenOut的对应关系可以自己打log测试一下 // 为什么不直接用X/Y去计算价格?因为amountIn会导致X和Y数量的变化,价格也跟着变,如果K大还好,K小的话,影响很大
例子8:发起swap,接上例
const tx = await router.swapExactTokensForTokens( amountIn, amounts[1].sub(amounts[1].div(10)), // 这个参数称为amountOutMin,如果最终swap得到的tokenOut数量低于这个值系统就不swap [tokenIn address, tokenOut address], wallet.address, Date.now() + 1000 * 60 * 10, // 链上排队?超过10分钟则放弃swap { gasPrice: gasPrice, gasLimit: "450000", // gasCost = gasPrice * min( gasLimit, gasUsed ) nonce: null, // ??? } ) const receipt = await tx.wait(); // receipt包含了本次swap的hash等信息
2021.10.05补充:
ethers.Contract abi如果包含两个同名函数,该函数名会无法调用,即使函数的参数不一样。同名函数参数不一样,这个在solidity是允许的,并且web3.js调用也正常。这个应该是ethers.js的bug
2021.09.28补充:
如果需要做event listening 或者 get past events, ethers.js没有web3.js方便,特地去看了ethers.js作者的github,作者好像也没打算对这部分进行优化,所以如果用到这两个功能,建议还是web3.js
- ankr的免费api挺好用的,支持wss和https