UniSwap V3的池子和价格计算

UniSwap V3已经出来好几年了, 因为对defi的友好, 现在逐渐有很多项目由V2迁移到了v3.
我的自动交易机器人在前段时间也支持了V3版本的交易.

今天来讲讲V3里面新的api如何获取池子和计算价格.

获取v3项目的池子

我们以常见的池子类型为例, 例如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')
}

至此, 池子的主要信息我们都拿到了. 接下来说一说价格的计算.

V3池子的价格计算

不同于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池, 那么需要使用第二种来获取准确的价格.

Subscribe to kuimale
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.