5/5
## Understanding and Creating Uniswap v4 Pool Identifiers In the Uniswap v4 ecosystem, every liquidity pool is uniquely identified by a `PoolID`. This identifier is not arbitrary; it's deterministically derived from a set of parameters that define the pool's characteristics. These parameters are bundled together in a struct called a `PoolKey`. Understanding how to construct a `PoolKey` and derive its corresponding `PoolID` is a fundamental skill for interacting with or building on top of Uniswap v4. This lesson walks you through the practical steps of creating a `PoolKey`, calculating its `PoolID`, and explores the crucial role of User Defined Value Types (UDVTs) in the process. ### The PoolKey Struct: The Blueprint of a Pool The `PoolKey` is a Solidity struct that contains all the essential parameters needed to define a specific liquidity pool. Think of it as the unique recipe for a pool. Its structure, defined in `PoolKey.sol`, is as follows: ```solidity struct PoolKey { // The lower currency of the pool, sorted numerically Currency currency0; // The higher currency of the pool, sorted numerically Currency currency1; // The pool LP fee, capped at 1,000,000. uint24 fee; // Ticks that involve positions must be a multiple of the tick spacing. int24 tickSpacing; // The hooks of the pool IHooks hooks; } ``` Let's break down each field: * **`currency0` & `currency1`**: These are the token addresses for the two assets in the pool. They are of the type `Currency`, which is an alias for `address`. For canonical ordering, the addresses are sorted numerically, with `currency0` being the lower value. * **`fee`**: The `uint24` representing the swap fee for the pool. This value is expressed in units of 1/1,000,000. For example, a 0.3% fee is represented by the number `3000`. * **`tickSpacing`**: The `int24` that defines the granularity of price ticks for the pool. Specific fee tiers have standard tick spacings; for instance, a 0.3% fee tier typically has a `tickSpacing` of `60`. * **`hooks`**: The contract address of any associated hooks. Hooks are contracts that execute custom logic at various points in a pool's lifecycle. If no hooks are used, this is set to the zero address (`address(0)`). ### Constructing a PoolKey in Practice To illustrate, let's create a `PoolKey` for a hypothetical ETH/WBTC pool with a 0.3% fee, a standard tick spacing of 60, and no hooks. In Solidity, the instantiation looks like this: ```solidity // Example PoolKey for an ETH/WBTC 0.3% fee pool PoolKey memory key = PoolKey({ currency0: address(0), // ETH is represented by address(0) currency1: WBTC, // The contract address of WBTC fee: 3000, // 3000 / 1,000,000 = 0.3% tickSpacing: 60, // Standard tick spacing for the 0.3% fee tier hooks: address(0) // No hooks are attached to this pool }); ``` Here, `address(0)` is used as a conventional representation for the native token (ETH). `WBTC` would be a constant holding the token's contract address. ### From PoolKey to PoolID: The On-Chain Identifier While the `PoolKey` struct contains all the necessary data to describe a pool, it is not what's used on-chain to identify it. For gas efficiency, Uniswap v4 uses a `PoolID`, which is a `bytes32` hash of the `PoolKey`. The conversion is handled by the `toId` function within the `PoolIdLibrary`. This function takes a `PoolKey` as input, ABI-encodes it, and computes its `keccak256` hash. ```solidity // PoolId.sol library PoolIdLibrary { function toId(PoolKey memory poolKey) internal pure returns (PoolId) { // The assembly computes keccak256(abi.encode(poolKey)) // 0xa0 is the size of the PoolKey struct in bytes (5 fields * 32 bytes/field = 160 bytes = 0xa0 hex) assembly { poolId := keccak256(poolKey, 0xa0) } } } ``` Using this library, you can easily derive the `PoolID`: ```solidity // The PoolKey 'key' from the previous example PoolId id = PoolIdLibrary.toId(key); ``` For our ETH/WBTC example, running this calculation would produce the following unique `PoolID`: `0x54c72c46df32f2cc455e84e41e191b26ed73a29452cdd3d82f511097af9f427e` This `bytes32` value is the single, canonical identifier used by the `PoolManager` contract to look up and manage the state of this specific pool. ### User Defined Value Types (UDVTs): Enhancing Type Safety You may have noticed the `PoolId` and `Currency` types. These are not standard Solidity types; they are **User Defined Value Types (UDVTs)**. UDVTs are a feature introduced in Solidity 0.8.8 that allows you to create a new type alias for an existing value type. In Uniswap v4's codebase: * `type PoolId is bytes32;` * `type Currency is address;` UDVTs improve code clarity and prevent common errors by enforcing stricter type checking. For example, the compiler will not let you accidentally assign a generic `bytes32` value to a variable expecting a `PoolId` without an explicit cast. To work with these custom types, Solidity provides two built-in functions: * **`unwrap()`**: Converts a UDVT instance back to its underlying base type. * **`wrap()`**: Converts a base type value into its corresponding UDVT. This is particularly useful for tasks like logging, as `console.log` often expects primitive types. To log a `PoolId`, you must first `unwrap` it to a `bytes32`: ```solidity // 'id' is of type PoolId // To log it, we must convert it back to bytes32 bytes32 i = PoolId.unwrap(id); console.logBytes32(i); // To convert a bytes32 back to a PoolId, we use wrap() PoolId p = PoolId.wrap(i); ``` ### Key Takeaway: On-Chain Efficiency A critical architectural decision in Uniswap v4 is that the full `PoolKey` struct is **not** stored on-chain for every pool. The `PoolManager` only maintains a mapping from the `bytes32` `PoolID` to the pool's storage. The `PoolKey` is used as an in-memory or off-chain data structure to calculate the `PoolID` when needed. This design significantly reduces the on-chain storage footprint and saves a considerable amount of gas, making the protocol more efficient.
In the Uniswap v4 ecosystem, every liquidity pool is uniquely identified by a PoolID
. This identifier is not arbitrary; it's deterministically derived from a set of parameters that define the pool's characteristics. These parameters are bundled together in a struct called a PoolKey
. Understanding how to construct a PoolKey
and derive its corresponding PoolID
is a fundamental skill for interacting with or building on top of Uniswap v4.
This lesson walks you through the practical steps of creating a PoolKey
, calculating its PoolID
, and explores the crucial role of User Defined Value Types (UDVTs) in the process.
The PoolKey
is a Solidity struct that contains all the essential parameters needed to define a specific liquidity pool. Think of it as the unique recipe for a pool. Its structure, defined in PoolKey.sol
, is as follows:
Let's break down each field:
currency0
& currency1
: These are the token addresses for the two assets in the pool. They are of the type Currency
, which is an alias for address
. For canonical ordering, the addresses are sorted numerically, with currency0
being the lower value.
fee
: The uint24
representing the swap fee for the pool. This value is expressed in units of 1/1,000,000. For example, a 0.3% fee is represented by the number 3000
.
tickSpacing
: The int24
that defines the granularity of price ticks for the pool. Specific fee tiers have standard tick spacings; for instance, a 0.3% fee tier typically has a tickSpacing
of 60
.
hooks
: The contract address of any associated hooks. Hooks are contracts that execute custom logic at various points in a pool's lifecycle. If no hooks are used, this is set to the zero address (address(0)
).
To illustrate, let's create a PoolKey
for a hypothetical ETH/WBTC pool with a 0.3% fee, a standard tick spacing of 60, and no hooks. In Solidity, the instantiation looks like this:
Here, address(0)
is used as a conventional representation for the native token (ETH). WBTC
would be a constant holding the token's contract address.
While the PoolKey
struct contains all the necessary data to describe a pool, it is not what's used on-chain to identify it. For gas efficiency, Uniswap v4 uses a PoolID
, which is a bytes32
hash of the PoolKey
.
The conversion is handled by the toId
function within the PoolIdLibrary
. This function takes a PoolKey
as input, ABI-encodes it, and computes its keccak256
hash.
Using this library, you can easily derive the PoolID
:
For our ETH/WBTC example, running this calculation would produce the following unique PoolID
:
0x54c72c46df32f2cc455e84e41e191b26ed73a29452cdd3d82f511097af9f427e
This bytes32
value is the single, canonical identifier used by the PoolManager
contract to look up and manage the state of this specific pool.
You may have noticed the PoolId
and Currency
types. These are not standard Solidity types; they are User Defined Value Types (UDVTs). UDVTs are a feature introduced in Solidity 0.8.8 that allows you to create a new type alias for an existing value type.
In Uniswap v4's codebase:
type PoolId is bytes32;
type Currency is address;
UDVTs improve code clarity and prevent common errors by enforcing stricter type checking. For example, the compiler will not let you accidentally assign a generic bytes32
value to a variable expecting a PoolId
without an explicit cast.
To work with these custom types, Solidity provides two built-in functions:
unwrap()
: Converts a UDVT instance back to its underlying base type.
wrap()
: Converts a base type value into its corresponding UDVT.
This is particularly useful for tasks like logging, as console.log
often expects primitive types. To log a PoolId
, you must first unwrap
it to a bytes32
:
A critical architectural decision in Uniswap v4 is that the full PoolKey
struct is not stored on-chain for every pool. The PoolManager
only maintains a mapping from the bytes32
PoolID
to the pool's storage. The PoolKey
is used as an in-memory or off-chain data structure to calculate the PoolID
when needed. This design significantly reduces the on-chain storage footprint and saves a considerable amount of gas, making the protocol more efficient.
A practical guide to constructing Uniswap v4 Pool Identifiers - Learn to build a `PoolKey` struct from its core parameters like tokens, fee, and tick spacing. Understand how this struct is hashed into a `PoolID` for efficient 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