获取 Uniswap V3 池刻度信息
本文介绍如何使用 JavaScript 和 Solidity 直接从链上数据获取 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翻译整理,转载请标明出处
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。