本文档旨在为Uniswap V4的Hook开发者、数据索引者、分析师建立Hook数据标准,以便所有用户在处理Hook数据时能够使用相同的共享框架, 详细介绍了 Hook 的标准事件、数据索引方式以及 TVL 和 Volume 等关键指标的计算方法。
正如我们在之前的 v4 数据指南 中介绍的那样,hooks 是 Uniswap v4 的决定性特征。它们使开发者能够自定义和扩展流动性池的行为。在本指南中,我们的目标是为 hook 开发者、hook 数据索引者和分析师建立一个标准,以便所有用户,从开发者和 LP 到分析师,在使用 hook 数据时都可以使用相同的共享框架。
加入我们在 Telegram 群组中关于 v4 hook 数据标准的讨论:https://t.me/v4_hooks_data。
但首先,让我们澄清 hook 如何融入 v4。可以将它们视为 PoolManager 合约的插件或外部 API。每个池只能链接到 一个 hook,但单个 hook 合约可以服务于 多个 池。一旦池使用 hook 初始化,该池就成为“hooked 池”(相对于“vanilla 池”),并且 hook 本身被认为是“已初始化”。相比之下,没有池的 hook 合约只是一个未使用的 Solidity 合约,它实现了 核心 hook 函数 的一个子集。
请记住,Uniswap v4 是一个 单例:PoolManager 从一个地方管理 所有 v4 池及其状态。下面的可视化展示了这些部分——池、hook 和 PoolManager——是如何关联的。
虽然 hook 可以包含任何 Solidity 代码,但它们都锚定在 Uniswap v4 中的 10 个核心 hook 函数 上。这些函数对应于池生命周期中的关键点(例如,互换或流动性修改之前/之后),并为自定义提供清晰的“入口点”。有关参考,请参阅 Uni v4 核心库中的 Hooks.sol 合约和 OpenZeppelin 制作的 BaseHook.sol 模板,该模板定义了所有 hook 入口点。
除了这些核心函数外,还有 4 个“delta-returning”标志,使可能的“hook 标志”总数达到 14。每个 hook 合约都通过其地址的最后 14 位来指示它实现了哪些 14 个标志(请参阅我们 之前的 v4 数据指南 关于如何读取这些位的文章)。这允许开发者和索引者快速确定 hook 的属性。如果你不熟悉 return deltas,请参阅 v4 文档中的 自定义会计、解锁回调和 Deltas 和 BeforeSwapDelta。
虽然 hook 可以做的不仅仅是标准的 10 个核心函数,但这些函数创建了一个一致的基础:
开发者可以依靠已知的“入口点”来自定义池行为。
索引器可以可靠地解析来自不同 hook 的相同核心 hook 调用的跟踪数据。
在许多情况下,hook 只会修改或扩展正常的 v4 PoolManager 流程,从而继承 Uniswap v4 核心合约中的常用事件。但是,某些 hook 可能会绕过或替换某些逻辑,从而导致跳过默认的 v4 事件或使其不准确。为了填补这一空白——并确保交易量、TVL 和费用的准确指标——我们提出了一组 hook 特定的事件。通过在正确的时间发出它们,hook 保持可发现性,分析保持可靠,并且 LP 获得完整的风险回报图。
我们正在与 OpenZeppelin 合作,将这些标准纳入 Uniswap Hooks 库,并与 Atrium Academy 合作,以便在开发者培训中采用它们。与任何主要标准(例如,ERC20、EIP1559)一样,广泛的社区支持是成功的关键。
我们强烈建议你发出这些事件,以便开发者、LP、分析师和用户都可以共享一个统一的hook数据解释框架。 通过遵循此标准,你还可以访问我们正在开发的开源存储库,从而可以轻松检索 hook 的指标,将其集成到前端显示中,并将其用于内部分析——确保你的 hook 在更广泛的生态系统中保持可见且有价值。
event HookSwap(
bytes32 indexed id, // v4 池 ID
address indexed sender, // 互换的路由器
int128 amount0,
int128 amount1,
uint128 hookLPfeeAmount0,
uint128 hookLPfeeAmount1
);
event HookFee(
bytes32 indexed id, // v4 池 ID
address indexed sender, // 互换的路由器
uint128 feeAmount0,
uint128 feeAmount1
);
event HookModifyLiquidity(
bytes32 indexed id, // v4 池 ID
address indexed sender, // 路由器地址
int128 amount0,
int128 amount1
);
event HookBonus(
bytes32 indexed id, // v4 池 ID
uint128 amount0,
uint128 amount1
);
你不需要发出 全部 四个事件——只需发出与你的 hook 行为相关的事件即可。
以下图表说明了在 hook 的生命周期中 何时 发出每个事件。请记住,这些场景可以 重叠。你的 hook 可能同时位于“自定义曲线”和“hook 费用”中,因此你需要为 两种 流程发出事件。
在 v4 中,开发者可以通过使用 自定义会计 来绕过内置的 v3 集中流动性,从而允许他们实现自己的互换曲线。这实现了自定义流动性供应和互换逻辑。自定义曲线 通过 Hooks.BEFORE_SWAP_FLAG
和 Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG
启用。此类曲线的示例包括:
非对称曲线
动态曲线
阶梯式曲线
由于这些自定义曲线绕过了内置的 ModifyLiquidity
事件的发出,并产生了不正确的 Swap
事件信息,因此我们强烈建议在 hook 合约中发出 HookSwap
和 HookModifyLiquidity
事件。
异步互换 将互换执行推迟到稍后的时间,从而允许 hook 控制互换的顺序并保护互换者免受 MEV 三明治攻击。此外,异步互换还可以用自定义曲线替换 v4 互换逻辑,从而有效地绕过类似 v3 的互换实现。
异步互换 使用 Hooks.BEFORE_SWAP_FLAG
和 Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG
启用。由于基于批处理的执行模型,我们建议在批处理完成时而不是针对每个单独的互换发出 HookSwap 事件。
你可以在 此处 找到 OpenZeppelin 开发的异步互换模板。
Hook 可以选择对互换或流动性修改收取费用,无论是作为固定金额还是动态计算。如果 hook 为互换者提供有价值的服务,它可能会选择在互换期间收取费用。同样,hook 可以对流动性修改征收费用。例如,hook 可能会通过对在特定时间范围内移除流动性收取高额费用来惩罚即时 (JIT) 流动性。有关实施静态 hook 费用的示例,请查看此内容。
Hook 奖励与 hook 费用相反——hook 不是向互换者收费,而是向用户返利,以激励通过实施这些奖励机制的池进行互换。Hook 可以使用 return-delta-flags 来实现此目的,即 BEFORE_SWAP_RETURNS_DELTA_FLAG
和/或 AFTER_SWAP_RETURNS_DELTA_FLAG
。
现在我们已经介绍了开发者将如何集成 hook 标准,让我们讨论如何有效地索引 hook 数据。
识别已初始化的 Hook
此事件指示池是 hook 池(你将看到有效的 hook 地址)还是 vanilla 池(地址为 null 地址)。
通过跟踪这些地址,你可以编译一个完整的数据索引初始化 hook 列表。
索引 Hook 活动
通过监控这些函数和事件的日志和跟踪数据(请参阅附录中的 ABI),你可以以一致、标准化的方式捕获每个 hook 的活动。
恭喜你走到这一步——我们几乎到达终点线了!
现在我们已经探讨了 hook 与 Uniswap v4 的关系,以及 hook 标准和各种用例流程,现在是时候处理指标了。建立一个共享术语确保了当我们讨论与 hook 相关的指标和 v4 指标时,我们都对内容有一个清晰、一致的理解。
我们将专注于两个关键指标类别——TVL 和交易量。下面,我们将定义这些指标并提供有关其计算的建议。
Uniswap v4 中的总体 TVL 是锁定在协议中的所有流动性的总价值(通常以美元计)。这包括保存在单例 v4 Pool Manager 合约中的流动性,以及保存在外部已初始化 hook 合约中的任何流动性。hook 合约一旦在至少一个 Uniswap v4 池中注册,就被认为是“已初始化”。
单例 TVL 是指仅保存在 Uniswap v4 的核心(单例)Pool Manager 合约中的总流动性部分。即使池使用外部 hook 初始化,单例 TVL 也仅计算锁定在核心合约本身中的资产,不包括 hook 合约持有的任何外部流动性。
外部 TVL 是指保存在 Uniswap v4 核心 Pool Manager 合约之外的总流动性,特别是保存在已初始化的 hook 合约中。hook 合约一旦在至少一个 Uniswap v4 池中注册,就被认为是“已初始化”。
Hook TVL 是指使用 hook 的池中的总流动性。它包括 常规 hook 池(流动性保留在 Uniswap v4 Pool Manager 合约中)和 特殊 hook 池(例如,自定义曲线)(流动性驻留在外部 hook 合约中)。
关于高级 Hook 场景的说明
Hook 可能会引入更复杂的流动性流,从而超出上述简单的“锁定”模型。例如,重新抵押 hook 可能会将流动性部署到外部借贷协议中,在那里可以赚取收益,而不是停留在 v4 Pool Manager 合约中。同样,即时 (JIT) 流动性 hook 可能会仅在互换需要时才从其他地方提取流动性。这些场景意味着 v4 中的“TVL”(单例和外部)可能与用于互换或在任何给定时刻驻留在协议中的实际资本不同。
我们正在与更广泛的 Uniswap 社区和 hook 开发者积极合作,以改进我们针对这些高级用例的 TVL 定义和测量方法。我们将在最终确定这些修订后的 TVL 标准后立即分享更新。
可以通过两种方式确定 TVL:
跟踪流动性修改事件 – 监视包括互换在内的流动性变化,以得出总流动性净值。
跟踪代币流 – 衡量相关合约中的流入和流出。
由于 v4 的单例架构,在 池级别,TVL 不再能仅从代币流中得出。但是,对于更广泛的总体 TVL 计算,跟踪代币流通常比监视流动性修改事件更有效。
交易量表示 Uniswap v4 上的总交易活动(通常以美元计),按每笔交易衡量。例如,将 2,673.83 USDC 兑换为 1 ETH(使用 2 月 13 日下午 5:23 的 ETH 价格)计为 2,673.83 美元的交易量,而不是交易中两种资产的总额的两倍。
总交易量(在一段时间内)是给定时间范围内所有 Uniswap v4 池中的累计交易量,包括 vanilla 池和 hook 池。
Hook 交易量(在一段时间内)是指给定时间范围内 hook 池 中的总交易量。这包括由使用其自己的自定义逻辑和/或流动性的返回 delta hook(例如,自定义曲线或异步互换)促成的互换金额。
Vanilla 交易量(在一段时间内)是指 vanilla 池 中的总交易量,即未使用任何 hook 初始化的池。
为了准确衡量 Uniswap v4 中的交易量,有两种主要方法:
基于事件的跟踪 – 你可以跟踪发出的事件,但要获得完整的画面,你必须同时监视:
来自 v4 核心合约的互换事件。
来自 delta-returning hook 合约的 HookSwap 事件(例如,自定义曲线)。仅依赖互换事件将错过由 delta-returning hook 促成的交易量。
swapDelta
字段以重建 amount0 和 amount1。此方法确保捕获所有互换,包括通过 delta-returning hook 处理的互换。你已经可以从 Dune 和 Allium 上的 dex.trades
查询整体交易量,它们使用跟踪数据索引。我们还在与 Uniswap Labs 合作将此应用于 v4 开源子图。
我们希望本指南有助于阐明 Uniswap v4 hook 数据的格局,从标准和事件到索引和分析。在我们结束时,以下是我们主要的行为号召:
Hook 开发者: 采用四个标准事件并在建议的点发出它们。这有助于生态系统中的每个人轻松理解和集成你的 hook 的行为。通过坚持此标准,你还可以使用我们即将推出的开源存储库,使你能够无缝检索你的 hook 的指标,在前端界面上展示它们并在内部进行分析——使你的 hook 在更广泛的生态系统中保持最前沿并节省你的团队大量工时。
索引器和分析师: 更新你的管道以检测初始化时的 hook 并解析标准 hook 事件。这确保了 v4 中互换、费用和流动性变化的全面覆盖。
通过像 ERC20 或 EIP1559 标准出现的方式一样共同努力,我们可以为 Uniswap v4 hook 塑造一个一致、可发现和透明的未来!
加入我们在 Telegram 群组中关于 v4 hook 数据标准的讨论:https://t.me/v4_hooks_data。
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"int128","name":"amount0","type":"int128"},{"indexed":false,"internalType":"int128","name":"amount1","type":"int128"},{"indexed":false,"internalType":"uint128","name":"hookLPfeeAmount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"hookLPfeeAmount1","type":"uint128"}],"name":"HookSwap","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint128","name":"feeAmount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"feeAmount1","type":"uint128"}],"name":"HookFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"int128","name":"amount0","type":"int128"},{"indexed":false,"internalType":"int128","name":"amount1","type":"int128"}],"name":"HookModifyLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"HookBonus","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"Currency","name":"currency0","type":"address"},{"internalType":"Currency","name":"currency1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"},{"internalType":"contract IHooks","name":"hooks","type":"address"}],"internalType":"struct PoolKey","name":"","type":"tuple"},{"internalType":"uint160","name":"","type":"uint160"}],"name":"beforeInitialize","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"Currency","name":"currency0","type":"address"},{"internalType":"Currency","name":"currency1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"},{"internalType":"contract IHooks","name":"hooks","type":"address"}],"internalType":"struct PoolKey","name":"","type":"tuple"},{"internalType":"uint160","name":"","type":"uint160"},{"internalType":"int24","name":"","type":"int24"}],"name":"afterInitialize","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"Currency","name":"currency0","type":"address"},{"internalType":"Currency","name":"currency1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"},{"internalType":"contract IHooks","name":"hooks","type":"address"}],"internalType":"struct PoolKey","name":"","type":"tuple"},{"components":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"int256","name":"liquidityDelta","type":"int256"},{"internalType":"bytes32","name":"salt","type":"bytes32"}],"internalType":"struct IPoolManager.ModifyLiquidityParams","name":"","type":"tuple"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"beforeAddLiquidity","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"components":[{"internalType":"Currency","name":"currency0","type":"address"},{"internalType":"Currency","name":"currency1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"},{"internalType":"contract IHooks","name":"hooks","type":"address"}],"internalType":"struct PoolKey","name":"","type":"tuple"},{"internalType":"uint256","name":"amountPerToken","type":"uint256"}],"name":"afterAddLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"}]
- 原文链接: uniswapfoundation.mirror...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!