网络配置
goerli测试网
使用Infura获取goerli测试网的RPC,想办法搞点水。
使用到的合约
合约名称 | 合约地址 |
---|---|
UniswapV3Factory | 0x1F98431c8aD98523631AE4a59f267346ea31F984 |
SwapRouter | 0xE592427A0AEce92De3Edee1F18E0157C05861564 |
NonfungiblePositionManager | 0xC36442b4a4522E871399CD717aBDD847Ab11FE88 |
WETH9 | 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6 |
创建流动性池
token排序
保证token0 < token1
ETH打包
如果要创建(XXX/ETH)流动性需要将ETH打包成WETH,请使用WETH9合约进行打包,这个WETH9的合约地址可以从NonfungiblePositionManager合约的WETH9参数中得到。
const { ethers, BigNumber : BN } = require('ethers');
const provider = new ethers.providers.JsonRpcProvider(process.env.GOERLI_TEST_RPC);
const wallet = new ethers.Wallet(process.env.DEPLOYER_PK, provider);
const WETH9 = {
address:'0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6',
abi:'',
decimals:18,
}
//Create Contract for WETH9
const contract = new ethers.Contract(WETH9.address, WETH9.abi, wallet);
(async () =>{
//eg. you want wrap 1 ether to weth
const amount = ethers.utils.parseUnits("1", WETH9.decimals);
await contract.deposit({value:amount});
})()
授权合约消费额度
WETH无需授权消费额度,其他的Token需要检查allowance来确定是否需要授权新的额度
const contract = new ethers.Contract(token.address, token.abi, wallet);
const tokenAllowance = await contract.allowance(owner, spender);
if(tokenAllowance < yourDesiredAmount){
await contract.approve(spender, yourDesiredAmount);
}
创建Pool
核心方法
NonfungiblePositionManager.sol > createAndInitializePoolIfNecessary()
传入参数
token0的address、token1的address没有好说的,注意是排序后的token。
fee有几个可供选择500、3000、10000,对应的tickerSpacing分别为10、60、200。
最后一个参数price是token0相对于token1的价格的平方根的Q96.64格式,即
一个token0可以兑换多少个token1,然后将这个数值的平方根乘以2的96次方,代码实现如下
const bn = require('bignumber.js');
const {BigNumber, BigNumberish} = require("ethers");
bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 })
function encodePriceSqrt(reserve1, reserve0) {
return BigNumber.from(
new bn(reserve1.toString())
.div(reserve0.toString())
.sqrt()
.multipliedBy(new bn(2).pow(96))
.integerValue(3)
.toString()
)
}
//eg.
const sqrtPriceX96 = encodePriceSqrt(`token1's amount`, `token0's amount`);
mint流动性
核心方法
NonfungiblePositionManager.sol > mint()
传入参数为一个对象
const param = {
token0:token0.address,
token1:token1.address,
fee:fee,
tickLower:tickLower,
tickUpper:tickUpper,
amount0Desired:token0.amount,
amount1Desired:token1.amount,
amount0Min:token0.amount.mul(90).div(100),
amount1Min:token1.amount.mul(90).div(100),
recipient:wallet.address,
deadline:deadline,
sqrtPriceX96:sqrtPriceX96.toString(),
};
前3个参数没什么好解释的。
关键是第4、5个,这个tick需要一个专门的方法来处理,理论上,这个tick的计算只需下面的公式即可:
但是得出的tick需要被tickerSpacing整除才可以,否则调用会被revert掉,且没有任何有用的报错信息,如下图:
因此需要一个方法进行转换:
function priceToTick(price, tickSpacing) {
let tick = Math.floor(Math.log(price) / Math.log(1.0001));
for (let i = 1; i < tickSpacing; i++){
if((tick - i) % tickSpacing === 0){
tick = tick - i;
break;
}
if((tick + i) % tickSpacing === 0){
tick = tick + i;
break;
}
}
return tick;
}
至于tickLower和tickUpper只需要增减tickSpacing的整数倍即可
const tickLower = priceToTick(price, tickSpacing) - n*tickSpacing;
const tickUpper = priceToTick(price, tickSpacing) - n*tickSpacing;
然后执行mint方法即可。至此流动创建完成。