# Pools

> PoolKey layout, the parameters word, pool IDs, fee tiers and finding pools on chain.

A pool on Giwater is a storage entry in CLPoolManager, not a contract. It is addressed by its `PoolKey`, and by the pool ID, the hash of that key. This page defines both, the fee fields as this deployment sets them, and how to list every pool from the chain.

The snippets use viem 2; only `find-pools.ts` talks to the chain. Addresses are on [Contracts](/docs/developers/contracts).

## PoolKey

`Currency`, `IHooks` and `IPoolManager` are addresses in the ABI, so the key encodes as `(address, address, address, address, uint24, bytes32)`.

```solidity
struct PoolKey {
  Currency currency0;
  Currency currency1;
  IHooks hooks;
  IPoolManager poolManager;
  uint24 fee;
  bytes32 parameters;
}
```

- **`currency0`, `currency1`** — the two tokens, sorted: `currency0` must be numerically lower than `currency1`, or `initialize` reverts with `CurrenciesInitializedOutOfOrder`. Native ETH is the zero address, so it is always `currency0`. WETH9 (`0x4200…0006`) is an ordinary ERC-20; a native-ETH pool and a WETH9 pool for the same pair are two different pools.
- **`hooks`** — the hook contract, or the zero address for none. Every seeded pool is hookless, and the app trades and provides liquidity only in hookless pools.
- **`poolManager`** — the CLPoolManager address. `initialize` reverts if it is not the manager being called.
- **`fee`** — the LP fee in hundredths of a basis point (pips; `1_000_000` = 100%). It may not exceed `1_000_000`. Bit 23 (`0x800000`) is the dynamic-fee flag: the pool starts with an LP fee of 0 and its hook sets the fee through `updateDynamicLPFee`. A hookless pool must not set it. No seeded pool uses it.
- **`parameters`** — the tick spacing and the hook callback bitmap, packed into one word. See [Parameters](#parameters).

Change any field and the key names a different pool, or one that was never initialized.

## Parameters

`parameters` is a `bytes32` read as a bit field, bit 0 being the least significant (`CLPoolParametersHelper`):

- **Bits 0–15** — the hook registration bitmap, one bit per callback. With `hooks` set, it must equal the hook contract's `getHooksRegistrationBitmap()`; with `hooks` at the zero address, it must be 0. Either mismatch reverts with `HookConfigValidationError`.
- **Bits 16–39** — the tick spacing, 24 bits. `initialize` accepts 1 to 32767 (`TickMath.MIN_TICK_SPACING` to `MAX_TICK_SPACING`).
- **Bits 40–255** — unused, and must be zero.

The callback bits, from `ICLHooks.sol`:

- `0` beforeInitialize · `1` afterInitialize
- `2` beforeAddLiquidity · `3` afterAddLiquidity
- `4` beforeRemoveLiquidity · `5` afterRemoveLiquidity
- `6` beforeSwap · `7` afterSwap
- `8` beforeDonate · `9` afterDonate
- `10` beforeSwapReturnsDelta · `11` afterSwapReturnsDelta
- `12` afterAddLiquidityReturnsDelta · `13` afterRemoveLiquidityReturnsDelta

For a hookless pool the word is just `tickSpacing << 16`: tick spacing 60 is `0x…003c0000`.

```ts
import { pad, toHex, type Hex } from "viem";

// Bit offsets of the CL hook callbacks in parameters bits 0-15 (ICLHooks.sol).
export const HOOK_OFFSETS = {
  beforeInitialize: 0,
  afterInitialize: 1,
  beforeAddLiquidity: 2,
  afterAddLiquidity: 3,
  beforeRemoveLiquidity: 4,
  afterRemoveLiquidity: 5,
  beforeSwap: 6,
  afterSwap: 7,
  beforeDonate: 8,
  afterDonate: 9,
  beforeSwapReturnsDelta: 10,
  afterSwapReturnsDelta: 11,
  afterAddLiquidityReturnsDelta: 12,
  afterRemoveLiquidityReturnsDelta: 13,
} as const;

// bits 0-15: hook callback bitmap, bits 16-39: tick spacing, bits 40-255: zero.
export function encodeParameters(tickSpacing: number, hookBitmap = 0): Hex {
  if (!Number.isInteger(tickSpacing) || tickSpacing < 1 || tickSpacing > 32_767) {
    throw new Error("tickSpacing must be 1 to 32767");
  }
  if (!Number.isInteger(hookBitmap) || hookBitmap < 0 || hookBitmap > 0xffff) {
    throw new Error("hookBitmap must fit in 16 bits");
  }
  return pad(toHex((BigInt(tickSpacing) << 16n) | BigInt(hookBitmap)), { size: 32 });
}

export function decodeParameters(parameters: Hex) {
  const word = BigInt(parameters);
  if (word >> 40n !== 0n) throw new Error("bits 40-255 must be zero");
  const hookBitmap = Number(word & 0xffffn);
  return {
    tickSpacing: Number((word >> 16n) & 0xffffffn),
    hookBitmap,
    callbacks: Object.entries(HOOK_OFFSETS)
      .filter(([, offset]) => (hookBitmap >> offset) & 1)
      .map(([name]) => name),
  };
}

encodeParameters(60); // 0x…003c0000
decodeParameters("0x00000000000000000000000000000000000000000000000000000000003c0000");
// { tickSpacing: 60, hookBitmap: 0, callbacks: [] }
```

## Pool ID

The pool ID is `keccak256(abi.encode(key))`: the six fields as six 32-byte words, 192 bytes, hashed. CLPoolManager stores every pool under this ID, takes it in `getSlot0`, `getLiquidity` and the other reads, emits it as the indexed `id` of `Initialize`, `ModifyLiquidity`, `Swap` and `Donate`, and maps it back to the key with `poolIdToPoolKey(id)`.

```ts
import { encodeAbiParameters, keccak256, type Address, type Hex } from "viem";

export type PoolKey = {
  currency0: Address;
  currency1: Address;
  hooks: Address;
  poolManager: Address;
  fee: number;
  parameters: Hex;
};

const poolKeyAbi = [
  {
    type: "tuple",
    components: [
      { name: "currency0", type: "address" },
      { name: "currency1", type: "address" },
      { name: "hooks", type: "address" },
      { name: "poolManager", type: "address" },
      { name: "fee", type: "uint24" },
      { name: "parameters", type: "bytes32" },
    ],
  },
] as const;

// PoolId = keccak256(abi.encode(PoolKey)): six 32-byte words, hashed.
export const getPoolId = (key: PoolKey): Hex => keccak256(encodeAbiParameters(poolKeyAbi, [key]));

// currency0 is the numerically lower address; native ETH (address 0) always sorts first.
export const sortCurrencies = (a: Address, b: Address): [Address, Address] =>
  BigInt(a) < BigInt(b) ? [a, b] : [b, a];

const [currency0, currency1] = sortCurrencies(
  "0xC7aA29e2FcB71c6396650F8Cc76dDA20714A5814", // USDC
  "0x1f6e6ffb0fC879CEC28830531C6Ec710F6110A7E", // BTC
);
console.log(
  getPoolId({
    currency0,
    currency1,
    hooks: "0x0000000000000000000000000000000000000000",
    poolManager: "0x1B24bDE0c2b7fd77450A48C4f4b5db0eA4AD6bDc",
    fee: 2011,
    parameters: "0x00000000000000000000000000000000000000000000000000000000003c0000",
  }),
); // 0xacbcd92a14745494c9d3302c3622b784d985c860202643d1f644b6348832b491 (BTC/USDC 0.30%)
```

## Fee Tiers on This Deployment

A swap pays two fees, both in pips:

- **LP fee** — the key's `fee`, paid to liquidity providers.
- **Protocol fee** — set per pool by CLProtocolFeeController when the pool is initialized, as a share of the total swap fee (`protocolFeeSplitRatio`, currently 33%, capped at 0.4%). CLPoolManager stores it as a `uint24` packing two 12-bit values: bits 0–11 for 0→1 swaps, bits 12–23 for 1→0. The controller's owner can change it later.

The protocol fee is taken first and the LP fee applies to the rest, so the total is `protocolFee + lpFee − protocolFee × lpFee / 1_000_000`, not the plain sum. The seeded pools use three tiers:

- **0.01%** — tick spacing 1, `parameters` `0x…00010000`, LP fee 67, protocol fee 32 (packed `131104`), total 99 pips.
- **0.05%** — tick spacing 10, `parameters` `0x…000a0000`, LP fee 335, protocol fee 164 (packed `671908`), total 499 pips.
- **0.30%** — tick spacing 60, `parameters` `0x…003c0000`, LP fee 2011, protocol fee 989 (packed `4051933`), total 2999 pips.

To create a pool at another tier, take the LP fee for a target total from `CLProtocolFeeController.getLPFeeFromTotalFee(totalFee)`. Pool creation is permissionless, so other combinations may exist on chain; the app lists only these three. Read a pool's actual fees from `getSlot0`, which returns the current `protocolFee` and `lpFee`.

## Finding Pools

CLPoolManager emits one `Initialize` event per pool, with the ID and both currencies indexed and the rest of the key in the data. Scan it from the deployment's start block, `33156261`, to rebuild the full key of every pool. The `poolManager` field is not in the event; it is the address that emitted it.

The public RPC serves `eth_getLogs` over at most 10,000 blocks per call, so the scan walks the range in chunks. To find one pair, filter on the indexed `currency0` and `currency1` (`args` in viem) instead of scanning everything.

`readPool` reads a pool's live state: `getSlot0` for the price (`sqrtPriceX96`, Q64.96), current tick and fees, and `getLiquidity(id)` for the liquidity in range at the current tick. A pool that was never initialized reads as all zeros. For the tick-level state, use `getPoolTickInfo` and `CLTickLens.getPopulatedTicksInWord`.

```ts
import { createPublicClient, http, parseAbi, type Address, type Hex } from "viem";
import { giwaSepolia } from "viem/chains";

const publicClient = createPublicClient({ chain: giwaSepolia, transport: http() });

const CL_POOL_MANAGER = "0x1B24bDE0c2b7fd77450A48C4f4b5db0eA4AD6bDc";
const START_BLOCK = 33_156_261n; // the deployment's first block
const MAX_RANGE = 10_000n; // the public RPC rejects wider eth_getLogs ranges

const poolManagerAbi = parseAbi([
  "event Initialize(bytes32 indexed id, address indexed currency0, address indexed currency1, address hooks, uint24 fee, bytes32 parameters, uint160 sqrtPriceX96, int24 tick)",
  "function getSlot0(bytes32 id) view returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee)",
  "function getLiquidity(bytes32 id) view returns (uint128 liquidity)",
]);

type Pool = {
  id: Hex;
  key: {
    currency0: Address;
    currency1: Address;
    hooks: Address;
    poolManager: Address;
    fee: number;
    parameters: Hex;
  };
};

// Every pool ever initialized, oldest first, from CLPoolManager's Initialize events.
export async function findPools(): Promise<Pool[]> {
  const latest = await publicClient.getBlockNumber();
  const pools: Pool[] = [];
  for (let fromBlock = START_BLOCK; fromBlock <= latest; fromBlock += MAX_RANGE) {
    const end = fromBlock + MAX_RANGE - 1n;
    const logs = await publicClient.getLogs({
      address: CL_POOL_MANAGER,
      event: poolManagerAbi[0],
      fromBlock,
      toBlock: end < latest ? end : latest,
      strict: true,
    });
    for (const { args } of logs) {
      pools.push({
        id: args.id,
        key: {
          currency0: args.currency0,
          currency1: args.currency1,
          hooks: args.hooks,
          poolManager: CL_POOL_MANAGER,
          fee: args.fee,
          parameters: args.parameters,
        },
      });
    }
  }
  return pools;
}

// A pool's live price, fees and in-range liquidity.
export async function readPool(id: Hex) {
  const [[sqrtPriceX96, tick, protocolFee, lpFee], liquidity] = await Promise.all([
    publicClient.readContract({
      address: CL_POOL_MANAGER,
      abi: poolManagerAbi,
      functionName: "getSlot0",
      args: [id],
    }),
    publicClient.readContract({
      address: CL_POOL_MANAGER,
      abi: poolManagerAbi,
      functionName: "getLiquidity",
      args: [id],
    }),
  ]);
  return { sqrtPriceX96, tick, protocolFee, lpFee, liquidity };
}
```

## Seeded Pools

The pools the deployment created and seeded with liquidity, from its manifest. Each pairs a test token with USDC, is hookless, and has `currency0` = the test token and `currency1` = USDC.

- BTC/USDC 0.01% — fee 67, tick spacing 1: `0xadcd53a3f0073e0ba22616164984977bdfed3f54b0b8725f3cfc0dcf1f93a116`
- BTC/USDC 0.05% — fee 335, tick spacing 10: `0x55a43cc4160fc5d86a96757d13d3609460e7e5a3459c342cae9999d4364e965f`
- BTC/USDC 0.30% — fee 2011, tick spacing 60: `0xacbcd92a14745494c9d3302c3622b784d985c860202643d1f644b6348832b491`
- AAPL/USDC 0.30% — fee 2011, tick spacing 60: `0x26fd02bf143e669903b4c459f13f3e10417a08713b8c8d944f7becd3f2b1b1db`
- SPCX/USDC 0.30% — fee 2011, tick spacing 60: `0xe7f088000fe753330605e620d04198c29028ccb067041b24b26104dd7a2c6a7e`
- SKHY/USDC 0.30% — fee 2011, tick spacing 60: `0x13260cdead351c67beda47265ad9cf9d87dabb9fb9d95657f5ee5278deb8f3e9`

These are a subset of what `findPools` returns: anyone can initialize further pools through CLPositionManager or CLPoolManager.

Next: [Positions](/docs/developers/positions) for minting and managing liquidity in these pools, [Math](/docs/developers/math) for ticks and prices, and [Concentrated Liquidity](/docs/product/concentrated-liquidity) for the model.
