Uniswap V4 Hooks DCI

Complete Indexing Solution for All Uniswap V4 Hooks


Introduction

What is the Hooks DCI?

The Hooks DCI (Dynamic Contract Indexer) is Tycho's specialized indexing plugin for all Uniswap V4 hooks. It extends the standard DCI with capabilities designed specifically for hooks, including automatic balance tracking, sophisticated entrypoint generation, and optional external metadata collection.

The Hooks DCI is required for indexing all Uniswap V4 hooks. It provides a complete solution with sensible defaults that work out-of-the-box for most hooks, and optional extension points for hooks with advanced requirements.

In this document, we break down UniswapV4 pools into different categories, describe the challenges to index each one, and provide a guide on how to index pools that need custom integration to be indexed by Tycho.

Hook Types:

Before diving into the solution, we need to understand the different categories that differentiate hooks Indexing:

1. Composable vs Non-Composable

  • Composable Hooks: Work with empty hookData in swaps

  • Non-Composable Hooks: Require custom hookData for before or after swap hooks

2. Internal vs External Liquidity

  • Internal Liquidity: Tokens accounting in PoolManager as ERC6909 claims

  • External Liquidity: Tokens in external contracts, outside UniswapV4's Pool Manager.

We define deeper these categories further on the Hook Classification section

circle-exclamation

Why Hooks DCI Exists

The standard DCI works well for self-contained protocols, but Uniswap V4 hooks require some extra steps for correct indexing. For Tycho to index all the state necessary for simulating each hook, it needs to have well-defined Entrypoints that cover all the possible Hook execution paths. This was achieved by adding:

  • V4-specific entrypoint generation with custom swap encoding and state overrides, aiming to cover all the paths that a hook might take

  • Flexible metadata collection supporting both internal (automatic) and external (custom) liquidity sources

  • Registry-based extension system for hooks with specialized requirements

  • State-aware processing to optimize performance and handle failures gracefully

On Background & Concepts section below, we provide detailed explanations of hook types and architecture.

Background & Concepts

Uniswap V4 Hooks Primer

Uniswap V4 introduces hooks - smart contracts that can execute custom logic at specific points in the pool lifecycle. Hooks enable powerful features like:

  • Dynamic fees based on market conditions

  • Custom oracle integrations

  • Liquidity management strategies

  • Integration with external DeFi protocols

Each hook address encodes permissions in its bytes, indicating which lifecycle events it handles:

The Hooks DCI only processes hooks with swap permissions (beforeSwap and/or afterSwap), as these are the ones that manage liquidity and affect swap behavior.

Hook Classification

Understanding hook types helps determine what (if anything) you need to implement for your hook.

1. Composable vs Non-Composable Hooks

1.1 - Composable Hooks (Currently Supported)

Composable hooks do NOT require custom calldata (hookData) to be passed during swaps. They work with empty or default hookData.

Examples:

  • Dynamic fee hooks (calculate fees from pool state)

  • Oracle integration hooks (read from external oracles, no user input needed)

  • Internal liquidity management hooks

  • Eulerswap'sarrow-up-right external liquidity hooks

1.2 - Non-Composable Hooks (Future Support)

circle-exclamation

Examples (not yet supported):

  • Hooks requiring user signatures per swap

  • Intent-based routing hooks

  • Hooks with swap-specific configuration

2. Internal vs External Liquidity (Primary Classification)

This is the key distinction that determines what you, as a hook integrator, need to implement.

2.1 - Internal Liquidity Hooks

No Custom Implementation Required - Composable Internal liquidity hooks are automatically indexed by Tycho.

Characteristics:

  • All liquidity tracked in PoolManager as ERC6909 claimsarrow-up-right

  • Balances automatically extracted from blockchain state

  • No external calls needed for Metadata (Pool balances and Limits)

  • Works with default orchestrator out-of-the-box

How It Works:

  1. Hooks DCI extracts pool balances from BlockChanges.balance_changes

  2. Default orchestrator's enrich_metadata_from_block_balances() builds metadata from the pool internal balance

  3. Entrypoint generator creates state overrides for PoolManager ERC6909 only

  4. Everything works automatically - no custom code needed

Examples:

  • Dynamic fee hooks using PoolManager liquidity

  • Hooks with custom AMM curves but standard storage

  • Time-weighted average price (TWAP) hooks

  • Most hooks that don't integrate with external DeFi

2.2 - External Liquidity Hooks

⚙️ Requires Custom Metadata Implementation

Characteristics:

  • Liquidity stored in external contracts (lending vaults, yield protocols, etc.)

  • Requires custom RPC or API calls to fetch current balances and withdrawal limits

  • Needs custom MetadataRequestGenerator and MetadataResponseParser

  • May need balance slot detection for accurate simulations

What You Need to Implement:

  1. MetadataRequestGenerator - Creates RPC requests for balances/limits

  2. MetadataResponseParser - Parses RPC responses into structured data

  3. (Optional) Custom HookOrchestrator - Only if entrypoint encoding is non-standard

On Metadata Collection Systemwe go deeper on the Metadata collection and how you can implement to track any hook with External Liquidity. We also provide an Hook Integration Guideto guide you through the implementation steps.

Examples:

  • Euler Hooks: Tokens in Euler lending vaults

  • Yearn Integration: Tokens in Yearn vaults earning yield

  • Staking Hooks: Tokens locked in staking contracts

What You Need to Implement (Decision Tree)

Architecture Overview

High-Level System Diagram

Core Components

1. UniswapV4HookDCI

The main orchestrator that coordinates all hook indexing operations. It:

  • Filters components with swap hook permissions

  • Categorizes components by processing state

  • Coordinates metadata collection

  • Manages component lifecycle (success, failure, retry, pause)

  • Delegates to inner DCI for tracing operations

2. Metadata Orchestrator System

Purpose: Collects external metadata for hooks with external liquidity. Optional - only used when a metadata generator is registered for a hook.

For increased performance, the external data collection is split into a three-layer architecture:

Layer 1: Request Generation (Protocol-Specific - Optional)

  • Creates MetadataRequest objects specifying what data to fetch

  • Supports different request types: Balances, Limits, TVL

  • Not needed for internal liquidity hooks - system uses block balances instead

Layer 2: Request Execution (Transport-Specific)

  • Implemented by providers (e.g., RPCMetadataProvider)

  • Handles batching, deduplication, retries

  • Routes requests to appropriate backends (RPC, HTTP APIs)

Layer 3: Response Parsing (Protocol-Specific - Optional)

  • Converts raw responses into structured metadata

  • Handles errors and validation

Fallback for Internal Liquidity: When no metadata generator is registered, the default orchestrator automatically enriches metadata from BlockChanges.balance_changes - no RPC calls needed.

3. Hook Orchestrator Registry

Maps hook addresses/identifiers to orchestrators that handle component processing. The default orchestrator (DefaultUniswapV4HookOrchestrator) handles both internal and external liquidity hooks automatically.

Lookup Priority:

  1. By Hook Address: Direct mapping for specific hook deployments

  2. By Identifier: String-based lookup (e.g., "euler_v1")

  3. Default Orchestrator: Fallback for all hooks

Orchestrator Responsibilities:

  • Generating entrypoints with appropriate tracing parameters

  • Injecting balances and limits into components

  • Updating component state attributes

Key Feature: The default orchestrator's enrich_metadata_from_block_balances() method automatically extracts balances from blockchain state for hooks without custom metadata generators. This means internal liquidity hooks work with zero custom code.

Internal vs External Liquidity Paths

The system automatically chooses the appropriate path based on whether a metadata generator is registered:

Path A: Internal Liquidity (Automatic)

Path B: External Liquidity (Custom Metadata)

Key Takeaway: The only difference is Step 3 (Metadata Collection). The rest of the flow is identical. This is why internal liquidity hooks require no custom implementation - they automatically use Path A.

Metadata Collection System

circle-info

💡 For Internal Liquidity Hooks: You can skip this entire section! The default orchestrator automatically extracts balances from blockchain state using enrich_metadata_from_block_balances(). This section is only relevant for hooks with external liquidity.

The metadata collection system uses a three-layer architecture that separates protocol-specific logic from transport concerns. This system is optional and only activated when you register a custom metadata generator for your hook.

Two Paths for Metadata Collection

Path A: Internal Liquidity (Automatic - No Implementation Needed)

  • System checks: generator_registry.get_generator(component)None

  • Default orchestrator calls enrich_metadata_from_block_balances()

  • Balances extracted from BlockChanges.balance_changes

  • Zero RPC calls, zero custom code required

Path B: External Liquidity (Requires Implementation)

  • System checks: generator_registry.get_generator(component)Some(generator)

  • Generator creates RPC requests for external data

  • Provider executes requests

  • Parser converts responses to structured metadata

  • Requires implementing Generator + Parser traits

Layer 1: Request Generation (Protocol-Specific - External Liquidity Only)

Purpose: Create metadata requests specific to your hook's data needs.

Interface:

Metadata Request Types:

  • ComponentBalance: Fetch token balances for the component

  • Limits: Fetch maximum swap amounts (withdrawal limits, liquidity caps)

  • Tvl: Total value locked calculation

  • Custom: Extensible for hook-specific needs

Euler Example - Balance Request:

Euler Example - Limits Request with State Overrides:

The lens contract pattern allows querying multiple values in a single RPC call using a custom contract deployed via state overrides.

Layer 2: Request Execution (Transport-Specific)

Purpose: Execute metadata requests efficiently, handling batching and retries.

Interface:

RPCMetadataProvider Features:

  • Batching: Groups multiple eth_call requests into JSON-RPC batches

  • Deduplication: Avoids duplicate requests in the same batch

  • Retry Logic: Exponential backoff for transient RPC failures

  • Concurrency Limiting: Prevents overwhelming RPC endpoints

Configuration:

Request Flow:

Layer 3: Response Parsing (Protocol-Specific)

Purpose: Convert raw RPC responses into structured metadata.

Interface:

Metadata Value Types:

Euler Example - Balance Parsing:

Euler Example - Limits Parsing:

Assembled Metadata

All parsed metadata for a component is assembled into:

Note that each field is Option<Result<...>>:

  • None: Metadata type not requested

  • Some(Ok(...)): Successfully collected

  • Some(Err(...)): Collection failed (triggers component failure)

4.3 Hook Orchestrators

Hook orchestrators coordinate the processing of components, including entrypoint generation and metadata injection.

Orchestrator Responsibilities

  1. Entrypoint Generation: Create EntryPointWithTracingParams for tracing

  2. Balance Injection: Add balances to ProtocolComponent for storage

  3. Limits Injection: Provide limits for RPC query optimization

  4. State Updates: Modify component state attributes as needed

Interface

Parameters:

  • block_changes: Mutable reference to modify transactions and components

  • components: Components to process in this call

  • metadata: Collected external metadata (balances, limits, TVL)

  • generate_entrypoints: true for full processing, false for balance-only

Registry Lookup Mechanisms

The HookOrchestratorRegistry provides multiple lookup strategies:

1. By Hook Address (Highest Priority)

2. By Hook Identifier (Medium Priority)

3. Default Orchestrator (Lowest Priority)

Lookup Order:

  1. Try hook address lookup

  2. Try hook identifier lookup (from component static attributes)

  3. Fall back to default orchestrator

  4. Return error if no orchestrator found

Default Orchestrator

The DefaultUniswapV4HookOrchestrator handles most hook types:

Features:

  • Extracts balances from block changes for components without external metadata

  • Delegates entrypoint generation to UniswapV4DefaultHookEntrypointGenerator

  • Injects balances and limits into BlockChanges

  • Handles both full processing and balance-only updates

When to Use Custom Orchestrator:

  • Hook requires special entrypoint encoding

  • Balance/limit data needs transformation before injection

  • Component state updates follow custom logic

  • Hook uses non-standard token accounting

Euler Example - When Default is Sufficient:

For Euler hooks, the default orchestrator works well because:

  • Balances come directly from metadata (no transformation needed)

  • Limits are standard max withdrawal amounts

  • Entrypoints follow standard Uniswap V4 swap encoding

  • No special state updates required

Therefore, Euler only requires custom metadata generator/parser, not a custom orchestrator.

4.4 Entrypoint Generation

Entrypoints define the calls that will be traced to understand how a component behaves under different conditions.

Entrypoints allow Tycho to:

  • Simulate swaps at various amounts to understand pricing curves

  • Test edge cases (e.g., swaps at 1%, 50%, 95% of liquidity)

  • Understand touched contracts and state that are necessary for reproducing a hook's behavior

For hooks with external liquidity, accurate entrypoints require:

  • Correct balance overwrites (both in PoolManager and external contracts)

  • Appropriate swap amounts based on limits

  • State overrides to simulate external contract states

Swap Amount Estimation

The system supports two estimation strategies:

1. Limits-Based Estimation (Preferred)

When limits are available, generate samples at:

  • 1% of limit (test small swaps)

  • 10% of limit (test medium swaps)

  • 50% of limit (test large swaps)

  • 95% of limit (test near-maximum swaps)

2. Balance-Based Estimation (Fallback)

When limits are unavailable, generate samples at:

  • 1% of balance

  • 2% of balance

  • 5% of balance

  • 10% of balance

Euler Example - Limits-Based Amounts:

V4MiniRouter Pattern

For Uniswap V4, entrypoints use a custom router deployed via state overrides:

Purpose: Execute swap operations against the PoolManager with proper token settlements

Pattern:

ERC6909 Overwrites

Uniswap V4 uses ERC6909 for internal PoolManager accounting. To simulate swaps, we must set balances:

Balance Slot Detection

For hooks with external liquidity, tokens may need balances set in external contracts:

Optional Feature: EVMBalanceSlotDetector

Euler Example - Balance Overwrites:

For Euler hooks, tokens are held in external vaults. The entrypoint generator:

  1. Detects balance slots for vault tokens (wstETH, WETH, etc.)

  2. Overwrites those slots with swap amounts

  3. Ensures PoolManager has ERC6909 balances

  4. Simulates full swap flow including vault withdrawals

This allows accurate tracing even though liquidity is external to PoolManager.

\

Last updated

Was this helpful?