Network Configuration
goerli testnet
Use Infura to obtain the RPC of the goerli test network and find a way to get some water.
Contracts used
Contract name | Contract address |
UniswapV3Factory | 0x1F98431c8aD98523631AE4a59f267346ea31F984 |
SwapRouter | 0xE592427A0AEce92De3Edee1F18E0157C05861564 |
NonfungiblePositionManager | 0xC36442b4a4522E871399CD717aBDD847Ab11FE88 |
WETH9 | 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6 |
Create a liquidity pool
token sorting
Guaranteed token0 < token1
Weth packing
If you want to create XXX/ETH liquidity and need to package ETH into WETH, please use the WETH9 contract for packaging. The contract address of this WETH9 can be obtained from the WETH9 parameter of the NonfungiblePositionManager contract.
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});
})()
Authorized contract consumption limit
WETH does not need to authorize a consumption limit. Other Tokens need to check the allowance to determine whether a new limit needs to be authorized.
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);
}
Create Pool
core methods
NonfungiblePositionManager.sol > createAndInitializePoolIfNecessary()
Pass in parameters
There is nothing to say about the address of token0 and the address of token1. Note that they are sorted tokens.
There are several fee options to choose from: 500, 3000, and 10,000, and the corresponding tickerSpacing are 10, 60, and 200 respectively.
The last parameter price is the square root of the price of token0 relative to token1 in Q96.64 format, that is
How many token1 can be exchanged for one token0, and then multiply the square root of this value by 2 to the power of 96. The code is implemented as follows
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 liquidity
core methods
NonfungiblePositionManager.sol > mint()
The incoming parameter is an object
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(),
};
The first 3 parameters are nothing to explain.
The key is the 4th and 5th ticks. This tick requires a special method to handle. In theory, the calculation of this tick only requires the following formula:
However, the resulting tick needs to be evenly divided by tickerSpacing , otherwise the call will be reverted without any useful error information, as shown below:
So a method is needed to convert:
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;
}
Then execute the mint method. At this point, the flow creation is completed