UniSwap V3已经出来好几年了, 因为对defi的友好, 现在逐渐有很多项目由V2迁移到了v3.
我的自动交易机器人在前段时间也支持了V3版本的交易.
今天来讲讲V3里面新的api如何获取池子和计算价格.
我们以常见的池子类型为例, 例如USDC/USDT/WETH.
首先去ethscan上面拿到Factory V3和Pair V3的ABI, 还需要额外的erc20Abi(这是为了后续获取池子信息时使用), 然后拿到Factory的Contract对象.
improt Web3 from 'web3'
import erc20Abi from 'xxxx' // 你的ABI存放位置
import pairAbiV3 from 'xxxx' // 你的ABI存放位置
import factoryAbiV3 from 'xxxx' // 你的ABI存放位置
const contract = new Web3.eth.Contract(factoryAbiV3, contractAddress)
以上, 我们拿到了V3版本的factory contract对象, V3中获取池子的核心方法是`getPool`.
且V3中一个最重要的核心就是引入了fee的概念, 它可以根据币价浮动的风险, 使池子可以收取不同的手续费.
我们在获取池子的时候, 一定要把fee考虑进去.
目前, uniswap的fee共有上三档: 0.05%/0.3%/1%, 对应的费率值为500/3000/10000.
关于fee的具体内容这里不做更多的讲解. 而我们要获取v3池子的核心点就是使用不同的池子类型的token1合约去遍历不同的fee, 然后使用计算出的liquidity来确定最活跃的池子.
const fees = [500, 3000, 10000]
const USDTAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'
const USDCAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
const WETHAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
// 获取最活跃的池子
// tAddr为erc20代币合约地址
async function getMaxLiq(lp, tAddr) {
let pool: string = ''
let tempPool: string = ''
let liquidity: string = ''
let tempLiquidity: string = ''
for(let fee of fees) {
tempPool = await contract.methods.getPool(lp, tAddr, fee).call()
if(tempPool.toLowerCase() !== zeroAddr) {
const poolContract = new Web3.eth.Contract(tempPool, pairAbiV3)
tempLiquidity = await poolContract.methods.liquidity().call()
// 比较两个池子的大小
if (COMPARE_BIGNUM(tempLiquidity, liquidity) >= 1) {
pool = tempPool
liquidity = tempLiquidity
}
}
}
return { pool, liquidity }
}
现在使用getMaxLiq方法, 我们获取到了USDT/USDC/WETH这几种池子类型下最活跃的池子.
基于获取到的活跃池子信息, 我们使用pool来获取完整的池子信息. 这里我以获取USDC池子信息为例:
const usdcPool = await getMaxLiq(USDCAddress)
// pair contract
const pairContract = new Web3.eth.Contract(usdcPool.pool, pairAbiV3)
// get fee level
const fee = await pairContract.methods.fee().call()
// get token0 address
const t0Addr = await pairContract.methods.token0().call()
// get token1 address
const t1Addr = await pairContract.methods.token1().call()
// get t0 and t1 contract
const t0Contract = new Web3.eth.Contract(t0Addr, erc20Abi)
const t1Contract = new Web3.eth.Contract(t1Addr, erc20Abi)
// get decimals
const t0Decimals = await t0Contract.methods.decimals().call()
const t1Decimals = await t1Contract.methods.decimals().call()
// 获取池中代币份额
const t0Amount = await t0Contract.methods.balance(usdcPool.pool).call()
const t1Amount = await t1Contract.methods.balance(usdcPool.pool).call()
// 获取token0和token1代币symbol
const t0Symbol = await t0Contract.methods.symbol().call()
const t1Symbol = await t1Contract.methods.symbol().call()
以上, 我们拿到了池子的诸多信息, 不过还有一个需要注意的地方, 就是token0和token1的顺序问题.
接上面的例子:
let params = {}
if(t0Addr.toLowerCase() === USDCAddress.toLowerCase()) {
params.lpName = `${t1Symbol}/${t0Symbol}`
// BIG_TRANSFER自定义方法, 将wei数据转换为可读数据
params.count = BIG_TRANSFER(t0Amount, t0Decimals, 'from')
params.tokenCount = BIG_TRANSFER(t1Amount, t1Decimals, 'from')
}
至此, 池子的主要信息我们都拿到了. 接下来说一说价格的计算.
不同于V2的x * y = k公式, V3的价格计算复杂很多, 这里不做详细描述, 说一说计算的方法:
const poolContract = new Web3.eth.Contract(usdcPool.pool, pairAbiV3)
const sqrtPriceX96 = (await poolContract.methods.slot0().call()).sqrtPriceX96
const price = sqrtPriceX96 ** 2 / 2 ** 192
以上, 我们拿到了常规价格, 为什么是常规价格? 因为真实的价格还需要引入token0和token1的decimals共同计算得到(针对WETH).
// WETH池
const price = WETHPrice / (sqrtPriceX96 ** 2 / 2 ** 192) / Math.pow(10, t0Decimals - t1Decimals)
两种价格计算方式, 如果目标池子非WETH池, 那么第一种获取价格的方式救能计算出准确价格, 如果池子时WETH池, 那么需要使用第二种来获取准确的价格.