0/5
## Uniquely Identifying Liquidity Pools with PoolKey and PoolId In modern decentralized exchange protocols, a liquidity pool is defined by more than just its pair of tokens. A unique set of parameters determines its specific behavior, fee structure, and custom logic. To manage this complexity, protocols use a `PoolKey` struct to define a pool's identity and a `PoolId` to reference it efficiently. ### What is a PoolKey? A `PoolKey` is a Solidity struct that aggregates all the essential data required to uniquely identify a specific liquidity pool. It serves as a human-readable blueprint for a pool, containing not just the assets but also its core operational parameters. The definition of the struct is as follows: ```solidity /// @notice Returns the key for identifying a pool struct PoolKey { /// @notice The lower currency of the pool, sorted numerically Currency currency0; /// @notice The higher currency of the pool, sorted numerically Currency currency1; /// @notice The pool LP fee, capped at 1_000_000. uint24 fee; /// @notice Ticks that involve positions must be a multiple of this int24 tickSpacing; /// @notice The hooks of the pool IHooks hooks; } ``` Let's break down each member of the `PoolKey` struct: * **`currency0` and `currency1`**: These are the contract addresses of the two tokens in the liquidity pool. A critical detail is that these addresses are always sorted numerically. `currency0` will always be the address with the smaller hexadecimal value. For example, in an ETH/USDC pool, ETH is often represented by a zero address or a wrapped ETH address. Assuming the USDC contract address is numerically greater, `currency0` would be ETH and `currency1` would be USDC. This canonical ordering ensures consistency. * **`fee`**: This `uint24` represents the fee tier for the pool. It is the percentage fee that liquidity providers earn from swaps executed through the pool. * **`tickSpacing`**: This parameter defines the granularity of price ticks. In concentrated liquidity systems, liquidity can only be provided at discrete price points (ticks), and `tickSpacing` determines the distance between these valid ticks. * **`hooks`**: This is the address of an optional smart contract that can execute custom logic at various points in a pool's lifecycle, such as before or after a swap or position update. If no hook is used, this is typically the zero address. ### The Core Concept of Pool Uniqueness The most important concept to grasp is that a unique pool is defined by the unique combination of *all* parameters in its `PoolKey`. The token pair alone is not enough to identify a pool. This design allows for the creation of multiple, distinct pools for the same pair of assets. For example, you can have several different ETH/USDC pools coexisting, each catering to different trading strategies: * **Pool 1**: An ETH/USDC pool with a **0.05%** fee and a `tickSpacing` of 10. * **Pool 2**: An ETH/USDC pool with a **0.3%** fee and a `tickSpacing` of 60. * **Pool 3**: An ETH/USDC pool with a **0.3%** fee, a `tickSpacing` of 60, and a custom `hooks` contract for dynamic fees. Even though Pools 1 and 2 share the same tokens, they are considered entirely separate pools because their `fee` and `tickSpacing` values differ. Pool 3 is distinct from Pool 2 because it includes a `hooks` contract. Each of these would have a unique `PoolKey`. ### From PoolKey to PoolId While the `PoolKey` struct is excellent for defining a pool, it is too large and complex for efficient use in smart contract logic, especially for storage lookups. For this, the protocol computes a more compact, machine-readable identifier called a `PoolId`. A `PoolId` is a `bytes32` value generated by taking the `keccak256` hash of the `PoolKey` struct. This process ensures that any change, no matter how small, to any member of the `PoolKey` will result in a completely different `PoolId`. The conversion is typically handled by a library function like this: ```solidity /// @notice Library for computing the ID of a pool library PoolIdLibrary { /// @notice Returns value equal to keccak256(abi.encode(poolKey)) function toId(PoolKey memory poolKey) internal pure returns (PoolId) { PoolId poolId; assembly ("memory-safe") { // 0xa0 represents the total size of the poolKey struct (5 * 32 bytes) poolId := keccak256(poolKey, 0xa0) } return poolId; } } ``` The `toId` function takes an in-memory `PoolKey` struct, hashes its raw byte representation, and returns the resulting `bytes32` hash. This `PoolId` then acts as the unique primary key for the pool throughout the entire protocol. ### Key Takeaways To summarize, there are two primary ways to identify a liquidity pool: 1. **`PoolKey`**: A human-readable struct containing the complete set of defining parameters: the two currencies, the fee tier, the tick spacing, and the hooks contract. It is the "source of truth" for a pool's identity. 2. **`PoolId`**: A machine-efficient `bytes32` hash generated from the `PoolKey`. It is used within the protocol's core logic for uniquely and efficiently referencing the pool in mappings and function calls. This dual-identifier system allows the protocol to support a diverse ecosystem of markets, enabling multiple distinct pools for the same asset pair, each with its own unique characteristics.
In modern decentralized exchange protocols, a liquidity pool is defined by more than just its pair of tokens. A unique set of parameters determines its specific behavior, fee structure, and custom logic. To manage this complexity, protocols use a PoolKey
struct to define a pool's identity and a PoolId
to reference it efficiently.
A PoolKey
is a Solidity struct that aggregates all the essential data required to uniquely identify a specific liquidity pool. It serves as a human-readable blueprint for a pool, containing not just the assets but also its core operational parameters.
The definition of the struct is as follows:
Let's break down each member of the PoolKey
struct:
currency0
and currency1
: These are the contract addresses of the two tokens in the liquidity pool. A critical detail is that these addresses are always sorted numerically. currency0
will always be the address with the smaller hexadecimal value. For example, in an ETH/USDC pool, ETH is often represented by a zero address or a wrapped ETH address. Assuming the USDC contract address is numerically greater, currency0
would be ETH and currency1
would be USDC. This canonical ordering ensures consistency.
fee
: This uint24
represents the fee tier for the pool. It is the percentage fee that liquidity providers earn from swaps executed through the pool.
tickSpacing
: This parameter defines the granularity of price ticks. In concentrated liquidity systems, liquidity can only be provided at discrete price points (ticks), and tickSpacing
determines the distance between these valid ticks.
hooks
: This is the address of an optional smart contract that can execute custom logic at various points in a pool's lifecycle, such as before or after a swap or position update. If no hook is used, this is typically the zero address.
The most important concept to grasp is that a unique pool is defined by the unique combination of all parameters in its PoolKey
. The token pair alone is not enough to identify a pool.
This design allows for the creation of multiple, distinct pools for the same pair of assets. For example, you can have several different ETH/USDC pools coexisting, each catering to different trading strategies:
Pool 1: An ETH/USDC pool with a 0.05% fee and a tickSpacing
of 10.
Pool 2: An ETH/USDC pool with a 0.3% fee and a tickSpacing
of 60.
Pool 3: An ETH/USDC pool with a 0.3% fee, a tickSpacing
of 60, and a custom hooks
contract for dynamic fees.
Even though Pools 1 and 2 share the same tokens, they are considered entirely separate pools because their fee
and tickSpacing
values differ. Pool 3 is distinct from Pool 2 because it includes a hooks
contract. Each of these would have a unique PoolKey
.
While the PoolKey
struct is excellent for defining a pool, it is too large and complex for efficient use in smart contract logic, especially for storage lookups. For this, the protocol computes a more compact, machine-readable identifier called a PoolId
.
A PoolId
is a bytes32
value generated by taking the keccak256
hash of the PoolKey
struct. This process ensures that any change, no matter how small, to any member of the PoolKey
will result in a completely different PoolId
.
The conversion is typically handled by a library function like this:
The toId
function takes an in-memory PoolKey
struct, hashes its raw byte representation, and returns the resulting bytes32
hash. This PoolId
then acts as the unique primary key for the pool throughout the entire protocol.
To summarize, there are two primary ways to identify a liquidity pool:
PoolKey
: A human-readable struct containing the complete set of defining parameters: the two currencies, the fee tier, the tick spacing, and the hooks contract. It is the "source of truth" for a pool's identity.
PoolId
: A machine-efficient bytes32
hash generated from the PoolKey
. It is used within the protocol's core logic for uniquely and efficiently referencing the pool in mappings and function calls.
This dual-identifier system allows the protocol to support a diverse ecosystem of markets, enabling multiple distinct pools for the same asset pair, each with its own unique characteristics.
A technical breakdown of PoolKey and PoolId - Explore how the `PoolKey` struct uniquely defines a liquidity pool using its tokens, fee, tick spacing, and hooks. Understand how this struct is hashed into a `PoolId` for efficient and unique on-chain referencing.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Note:
Last updated on September 27, 2025
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 Singleton architecture
Uniswap v4 flash accounting
Uniswap v4 operations
Uniswap v4 lifecycle
How to build a Uniswap v4 swap router
Last updated on September 22, 2025
Duration: 5min
Duration: 7min
Duration: 39min
Duration: 35min
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 Singleton architecture
Uniswap v4 flash accounting
Uniswap v4 operations
Uniswap v4 lifecycle
How to build a Uniswap v4 swap router
Last updated on September 22, 2025