获取 Uniswap V3 池刻度信息

本文介绍如何使用 JavaScript 和 Solidity 直接从链上数据获取 Uniswap V3 池的所有刻度信息(即流动性信息)

获取 Uniswap V3 池刻度信息
一键发币: SUI | SOL | BNB | ETH | BASE | ARB | OP | POLYGON | 跨链桥/跨链兑换

为了绘制 Uniswap V3 池的流动性分布或计算报价,我们需要知道所有刻度的信息。目前,我们使用子图或自定义索引来扫描 mint/burn 事件,这可能会不同步且成本高昂。没有简单的方法可以从池合约中直接获取所有信息。因此,本文将展示一种解决方案,只需一次查询区块链即可完成。

1、刻度位图

位图是一种紧凑的数据索引方式。Uniswap V3 使用此技术来了解哪些刻度有流动性。当位图中的位为 1 时,对应的刻度具有流动性。否则,该刻度没有流动性。

我们可以使用位图更快地遍历所有刻度。如果一个字为零,则跳过整个 256 个刻度。否则,我们遍历每个位。只有当对应位为 1 时,我们才获取刻度信息。这减少了存储访问并节省了 Gas。

2、一次性获取所有刻度

Uniswap V3 提供了两个视图函数:tickBitmap() 用于获取一个字,ticks() 用于获取某个刻度的信息。即使使用位图遍历方法,我们也需要向 RPC 发送大量查询,这不是一个好的选择。为了避免这种情况,我们将部署一个合约来处理所有工作。

下面 getAllTicks 函数的第一部分计算已初始化刻度的数量(具有流动性的刻度)。这是因为我们需要在创建动态数组之前知道数组长度。然后,我们再次遍历所有位图以获取刻度的信息。

// SPDX-License-Identifier: MIT  
pragma solidity ^0.8.27;  

interface IPool {  
    function tickSpacing() external view returns (int24);  

    function tickBitmap(int16 wordPos) external view returns (uint256);  

    function ticks(int24 tick) external view returns (uint128 liquidityGross, int128 liquidityNet);  
}  

struct Tick {  
    int24 index;  
    uint128 liquidityGross;  
    int128 liquidityNet;  
}  

contract UniswapV3Lens {  
    int24 internal constant MIN_TICK = -887272;  
    int24 internal constant MAX_TICK = -MIN_TICK;  

    function getAllTicks(IPool pool) external view returns (Tick[] memory ticks) {  
        int24 tickSpacing = pool.tickSpacing();  
        int256 minWord = int16((MIN_TICK / tickSpacing) >> 8);  
        int256 maxWord = int16((MAX_TICK / tickSpacing) >> 8);  

        uint256 numTicks = 0;  
        for (int256 word = minWord; word <= maxWord; word++) {  
            uint256 bitmap = pool.tickBitmap(int16(word));  
            if (bitmap == 0) continue;  
            for (uint256 bit; bit < 256; bit++) if (bitmap & (1 << bit) > 0) numTicks++;  
        }  

        ticks = new Tick[](numTicks);  
        uint256 idx = 0;  
        for (int256 word = minWord; word <= maxWord; word++) {  
            uint256 bitmap = pool.tickBitmap(int16(word));  
            if (bitmap == 0) continue;  
            for (uint256 bit; bit < 256; bit++) {  
                if (bitmap & (1 << bit) == 0) continue;  
                ticks[idx].index = int24((word << 8) + int256(bit)) * tickSpacing;  
                (ticks[idx].liquidityGross, ticks[idx].liquidityNet) = pool.ticks(ticks[idx].index);  
                idx++;  
            }  
        }  
    }  
}

我在 Arbitrum 网络上部署了上述 UniswapV3Lens 合约。你可以在 ArbiScan 上查看它。让我们使用 JavaScript 和 ethers.js 库查询一些池:

const ethers = require("ethers");  

async function main() {  
  const abi = ["function getAllTicks(address pool) external view returns (tuple(int24 index, uint128 liquidityGross, int128 liquidityNet)[] memory ticks)"];  
  const provider = new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc");  
  const lens = new ethers.Contract("0xf632a03754090B44B605C0bA417Fffe369E26397", abi, provider);  
  await getTicks(lens, "WETH-USDT-0.05%", "0x641C00A822e8b671738d32a431a4Fb6074E5c79d");  
  await getTicks(lens, "USDC-USDT-0.01%", "0xbE3aD6a5669Dc0B8b12FeBC03608860C31E2eef6");  
}  

async function getTicks(lens, name, pool) {  
  const ticks = await lens.getAllTicks(pool);  
  console.log(`${name}: ${ticks.length} ticks, ticks[0]: ${ticks[0]}`);  
}  

main();
WETH-USDT-0.05%: 1792 ticks, ticks[0]: -887270,36881048071624030145431278,36881048071624030145431278  
USDC-USDT-0.01%: 723 ticks, ticks[0]: -887272,3053998183,3053998183

3、部分获取刻度

如果一次性获取所有刻度的成本低于 Gas 限制,这种方法将完美运行。在未来,人们会添加更多流动性,从而产生新的刻度,因此我们需要一种更健壮的方法来查询。解决方案是多次查询较小的刻度集,直到获得完整信息。

我们将向合约添加一个新的视图函数:getTicks()。此函数允许我们从某个刻度索引开始获取预定义数量的刻度。它与 getAllTicks() 几乎相同,除了我们不需要计算数组长度。

function getTicks(IPool pool, int24 tickStart, uint256 numTicks) external view returns (Tick[] memory ticks) {  
    int24 tickSpacing = pool.tickSpacing();  
    int256 maxWord = int16((MAX_TICK / tickSpacing) >> 8);  
    tickStart /= tickSpacing;  
    int256 wordStart = int16(tickStart >> 8);  
    uint256 bitStart = uint8(uint24(tickStart % 256));  

    ticks = new Tick[](numTicks);  
    uint256 idx = 0;  
    for (int256 word = wordStart; word <= maxWord; word++) {  
        uint256 bitmap = pool.tickBitmap(int16(word));  
        if (bitmap == 0) continue;  
        for (uint256 bit = word == wordStart ? bitStart : 0; bit < 256; bit++) {  
            if (bitmap & (1 << bit) == 0) continue;  
            ticks[idx].index = int24((word << 8) + int256(bit)) * tickSpacing;  
            (ticks[idx].liquidityGross, ticks[idx].liquidityNet) = pool.ticks(ticks[idx].index);  
            if (++idx >= numTicks) return ticks;  
        }  
    }  
}

在我们的离线脚本中,我们每次查询 1000 个刻度。只有当返回的刻度少于 1000 个时,我们才会停止。以下是示例:

const ethers = require("ethers");  

async function main() {  
  const abi = ["function getTicks(address pool, int24 tickStart, uint256 maxTicks) external view returns (tuple(int24 index, uint128 liquidityGross, int128 liquidityNet)[] memory ticks)"];  
  const provider = new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc");  
  const lens = new ethers.Contract("0xf632a03754090B44B605C0bA417Fffe369E26397", abi, provider);  
  await getTicks(lens, "WETH-USDT-0.05%", "0x641C00A822e8b671738d32a431a4Fb6074E5c79d");  
  await getTicks(lens, "USDC-USDT-0.01%", "0xbE3aD6a5669Dc0B8b12FeBC03608860C31E2eef6");  
}  

async function getTicks(lens, name, pool) {  
  const step = 1000;  
  let tickStart = -887272; // MIN_TICK  
  let results = [];  
  while (true) {  
    let ticks = await lens.getTicks(pool, tickStart, step);  
    ticks = ticks.filter((t) => t.liquidityGross > 0n);  
    console.log(`>>> ${name}: got ${ticks.length} ticks`);  
    results = results.concat(ticks);  
    tickStart = results[results.length - 1].index + 1n;  
    if (ticks.length < step) break;  
  }  

  console.log(`${name}: ${results.length} ticks, ticks[0]: ${results[0]}`);  
}  

main();
>>> WETH-USDT-0.05%: got 1000 ticks  
>>> WETH-USDT-0.05%: got 792 ticks  
WETH-USDT-0.05%: 1792 ticks, ticks[0]: -887270,36881048071624030145431278,36881048071624030145431278  
>>> USDC-USDT-0.01%: got 723 ticks  
USDC-USDT-0.01%: 723 ticks, ticks[0]: -887272,3053998183,3053998183

此方法帮助我们更快、更高效地检索 Uniswap V3 池的流动性。我们可以绘制流动性分布并计算报价,而无需自定义索引器或子图。


原文链接:Get all ticks of Uniswap V3 pools

DefiPlot翻译整理,转载请标明出处

免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。
通过 NowPayments 打赏