Skip to content

Execution

Uniswap v4 Pool IDs vs Contract Addresses

Two pools share the same manager, currencies, fee and hook. Changing only tick spacing changes their IDs and which liquidity boundaries are valid.

Two folded ivory paper rosettes sit in the wells of one ceramic tray, with a lime tab between them.
Illustration for execution; examples in this article are educational.

Uniswap v4 identifies a pool with a 32-byte PoolId derived from its PoolKey: the two ordered currencies, fee field, tick spacing and hook address. The PoolManager contract address identifies the shared contract holding many pools. To locate one pool's state reliably, keep the chain, PoolManager address and complete PoolId together.

A familiar token pair can appear in several v4 pools under the same manager. Matching the pair and the contract address can therefore return a plausible-looking result while selecting the wrong liquidity. The missing information is in the rest of the pool key.

What the PoolManager address tells you

Uniswap v3 gives each pool its own contract. In v4, pool state sits inside a shared PoolManager. The address gets you to that manager; the PoolId selects a particular pool within it. Uniswap's pool-state guide describes this singleton architecture and the libraries used to access its state.

This changes what a block explorer's contract page can establish. Finding the expected PoolManager does not establish which pool a swap used. A manager can handle operations for many pools, so the decoded operation or event must supply the pool identity too.

There are also other addresses in the transaction. A router receives the user's instructions. A hook supplies optional pool behavior. A PositionManager manages position ownership. None substitutes for the pool's identity. The broader instrument-mapping guide applies the same discipline to exchange symbols: keep a readable label separate from the fields that actually select the market.

The implementation details here follow Uniswap v4 core v4.0.0, pinned to commit e50237c, alongside official documentation reviewed on 28 October 2026. A fork or another deployment needs its own verification.

The five PoolKey fields behind a PoolId

The core PoolKey definition contains these fields in this order:

FieldWhat must match
currency0The numerically lower currency address
currency1The numerically higher currency address
feeA uint24 static fee value, or exactly 0x800000 for dynamic fees
tickSpacingThe int24 spacing between eligible tick boundaries
hooksThe hook contract address, or zero for no hook

Currency ordering follows address values, rather than the order of ticker names in a trading screen. Preserve the identities even if the interface displays the reciprocal price. Reversing the two fields creates different hash input. Core initialization also rejects currencies supplied out of order or equal, as the initialization checks show.

The PoolId library defines the ID as the Keccak-256 hash of the ABI-encoded PoolKey. That encoding occupies five 32-byte words, or 160 bytes. The result is 32 bytes; an Ethereum address is 20 bytes. Truncating the hash to fit an address field changes the identifier.

Use the exact field types and standard ABI encoding. Concatenating ticker strings, hashing a JSON object or using packed encoding gives different input bytes. For these five types, packed encoding occupies only 66 bytes. A correctly formatted hash of those bytes still fails to identify the intended v4 pool.

A checked example: one manager, two pools

This is a constructed example, with hypothetical currencies and no deployed pools implied. Let currency A be the 20-byte address whose numeric value is 17, and currency B the address whose numeric value is 34. In hexadecimal, these are addresses ending in 0011 and 0022 respectively, with every earlier digit zero. A comes first because 17 is less than 34.

Both proposed pools use one hypothetical manager M on one chain C. Both have a zero hook address and a fixed fee field of 3,000. Under Uniswap's documented convention, 3,000 fee pips means 0.30%; it is not 3,000 basis points. The pool-creation guide explains these inputs.

Only tick spacing changes:

Input or resultPool APool B
Ordered currenciesA, BA, B
Fee field3,0003,000
Tick spacing6010
HookZero addressZero address
PoolId, abbreviated58b838a63f41…ab756c4d961a94c57090…33d93a9b

The hash check used two encoding routes: a typed ABI encoder and an independently assembled sequence of five zero-padded words. Their 160 input bytes matched exactly for each pool, and Keccak-256 returned the displayed results. The fourth word ends in hexadecimal 3c for 60 and 0a for 10. No manager address enters either calculation.

The abbreviated IDs above are reading aids. An application must compare all 32 bytes. Keeping only the visible prefix or suffix discards information the protocol uses.

Now follow the hypothetical creation sequence. Before initialization, both hashes can be calculated locally, but neither calculation creates pool state. Next, initialize Pool A at a raw currency1-per-currency0 price of 1. Its starting square-root price is 2 raised to the power 96. Pool B remains uninitialized. Then initialize Pool B at a raw price of 4, using twice that square-root-price value. The two initialized pools now have different prices under the same manager.

Those starting prices are separate initialization arguments. They were never part of either key, so choosing a price or later changing it through swaps does not change the PoolId. Initialization alone also supplies no liquidity. Existence, current price and available liquidity require separate checks.

A proposed position from tick −20 to tick 20 exposes the practical consequence of selecting the wrong row. Both boundaries divide evenly by Pool B's spacing of 10. Neither is a multiple of Pool A's spacing of 60. This checks valid boundaries only; it does not establish whether the position is active at either example price. Looking up “A/B, 0.30%, manager M” loses the field that decides whether those bounds are valid. The tick-spacing guide explains how rounding boundaries can change the range a provider intended.

Separate a pool's identity from its changing state

The key is a configuration identity, rather than a snapshot of market conditions. Current price, active tick, liquidity and fee-growth accounting can change while the ID stays the same. The pinned PoolManager implementation stores pool state in a mapping keyed by PoolId.

Dynamic fees make the distinction especially useful. In that pool type, the key's fee field equals the special value 0x800000; it does not combine that flag with the current fee. The current LP fee can change under the same identity; inserting today's percentage into a replacement key would compute the wrong ID. The LP fee library defines the flag separately from ordinary fee values.

Likewise, a hook address is part of identity, but its presence says little about whether its behavior suits a user. A different hook means a different key. Evaluating that hook's permissions and code is a further task, covered in the v4 hook-permission guide.

Position identity belongs at another layer. Several liquidity positions can belong to the same pool while using different owners, ranges and accounting records. A position NFT's token number is not the PoolId. The LP-token and position-NFT comparison follows those ownership and withdrawal distinctions.

Recover the key, then read the matching state

The Initialize event is useful evidence because it includes the ID and all five key fields. Later Swap and ModifyLiquidity events identify the affected pool by ID. These are defined in the pinned PoolManager interface. Filter the emitting contract and the pool ID together; filtering only the manager groups unrelated pools into one stream.

A hash cannot be conveniently decoded back into its original fields. Preserve the complete key from trustworthy creation evidence or validated metadata, and recompute its ID. The manager's initialization source explicitly distinguishes emitting the key fields from storing them. Retain that evidence alongside the resulting ID.

For offchain reads, Uniswap documents StateView methods such as getSlot0 and getLiquidity that take a PoolId. Use the StateView deployment associated with the intended manager and chain. The SDK pool-data guide also demonstrates reading price and liquidity at the same block reference. This avoids combining one pool snapshot's price with another snapshot's liquidity.

Identity still needs its surrounding context. Neither chain ID nor PoolManager address is a PoolKey field. Identical keys therefore calculate the same hash in different deployment contexts, even though those contexts can hold different state. Keeping chain, manager and ID together is an application recordkeeping choice that closes that gap.

Shared custody adds one more trap: the manager's aggregate token balance is not a reserve balance for a single selected pool. Uniswap's reserve-reading guide uses the complete key and manager to isolate pool-specific principal. The v4 flash-accounting guide follows how obligations are settled across operations inside the shared manager.

A worksheet for an unresolved pool lookup

Save these checks with the observation or proposed operation:

RecordAcceptance check
Chain and PoolManagerThey match the intended deployment and RPC network
Complete PoolKeyAll five typed fields are preserved; currencies are ordered
Recomputed PoolIdEvery byte matches the candidate ID
Initialization evidenceThe expected manager emitted the matching key and ID
State referencePrice, tick, fees and active liquidity use one identified block
Position or swap parametersBounds, direction and limits refer to this exact pool

A missing search result leaves the lookup unresolved. A successful local hash calculation establishes only the identifier. A zero liquidity reading alone does not establish that initialization never happened. Uniswap's StateView guidance recommends validating results and handling failed or invalid-ID reads; combine that with creation evidence before interpreting the state.

For the constructed pair, the acceptance question is precise: did the operation select spacing 60 or spacing 10, and does its full ID agree with that key on chain C under manager M? Write those fields beside the result. That record is enough to expose the mix-up that a shared contract address would leave hidden.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.