# UniSwap V3的池子和价格计算 **Published by:** [kuimale](https://paragraph.com/@kuimale/) **Published on:** 2023-06-27 **URL:** https://paragraph.com/@kuimale/uniswap-v3 ## Content 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池, 那么需要使用第二种来获取准确的价格. ## Publication Information - [kuimale](https://paragraph.com/@kuimale/): Publication homepage - [All Posts](https://paragraph.com/@kuimale/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@kuimale): Subscribe to updates