---
title: "Index the protocol"
description: "Build a reorg-safe index of Clank launches, trades, and graduations."
---

> Documentation Index
> Fetch the complete documentation index at: https://developer.clank.trade/llms.txt
> Use this file to discover all available pages before exploring further.

# Index the protocol

Start from the canonical Clank factory, then discover each token and bonding
curve from its launch event. This keeps the index scoped to contracts created
by Clank rather than accepting arbitrary token addresses.

## Discover launches

Watch `TokenLaunched` on the factory. Store the indexed `token`, `curve`, and
`deployer` together with the quote asset, launch configuration, and graduation
threshold carried by the event.

The factory on Robinhood Chain (`4663`) is
`0x798daAa0707C1e538bB5Acf0867Ac0e1A84cccF2`. It was deployed at block
`69067760`, so backfill from there.

| Contract | Event | Use |
| --- | --- | --- |
| Factory | `TokenLaunched` | Register a token and its curve. |
| Curve | `CurveBuy` | Record a bonding-curve buy, fees, and launch tax. |
| Curve | `CurveSell` | Record a bonding-curve sell, fees, and launch tax. |
| Curve | `StateChanged` | Track the curve state. `Ready` means the sale allocation sold out. |
| Curve | `CurveCompleted` | Record reserves moved out of the curve. |
| Factory | `LaunchSwept` | Record reserves moved out of the curve. |
| Factory | `PoolGraduated` | Mark the Uniswap V4 pool as available. |
| PoolManager | `Initialize` | Confirm the graduated pool's ID and opening price. |
| PoolManager | `Swap` | Record a trade in the graduated pool. |

## Index trades

After discovering a curve, scan it from the `TokenLaunched` block and subscribe
to `CurveBuy` and `CurveSell`. Use `(chainId, transactionHash, logIndex)` as the
event identity so retries do not create duplicate trades.

Keep the buyer or seller separate from the recipient. Smart accounts, routers,
and delegated execution can make those addresses different. For a launch made
with `launchAndBuy`, `buyer` is the LaunchAndBuy forwarder. Credit that first
buy to `recipient` or the transaction sender.

`CurveBuy.quoteIn` is what the buyer paid, including `fee` and `tax`. On the
final buy it already excludes any `CurveBuyRefunded` refund, so do not subtract
the refund again. `CurveSell.quoteOut` is what the seller received after `fee`.
The amount that moved the curve's reserves is `quoteIn - fee - tax` for a buy
and `quoteOut + fee + tax` for a sell. Quote amounts are in the launch's pair
token, so use its `decimals()` rather than assuming 18.

## Track graduation

Treat `PoolGraduated` as the authoritative transition to pool trading.
`StateChanged` to `Ready` means the bonding-curve allocation sold out.
`CurveCompleted` and `LaunchSwept` are emitted together when the reserves leave
the curve.

From `Ready` until `PoolGraduated`, neither the curve nor the pool accepts
trades. Show the token as graduating. The final buy normally sweeps the curve
in the same transaction, but pool creation is always a separate transaction:
anyone can call `createGraduatedPool(token)` on the factory once the curve is
`Swept`. If the curve emits `AutoGraduationFailed`, the sweep did not happen,
so call `graduate(token)` first.

If the factory emits `LaunchGraduationRescued`, the owner recovered the
reserves of a launch that could not graduate. No pool will be created for that
token.

## Index pool trades

`PoolGraduated` does not include the pool ID. Rebuild the pool key from the
launch and hash it. `poolFee`, `tickSpacing`, and `pairToken` come from
`getLaunchedToken(launchToken)` on the factory. Read the hook once from
`initializationHook()` on the factory.

```ts title="pool-id.ts"
import {
  encodeAbiParameters,
  keccak256,
  parseAbiParameters,
  type Address,
} from "viem";

export function poolIdFor(
  launch: {
launchToken: Address;
pairToken: Address;
poolFee: number;
tickSpacing: number;
  },
  hook: Address,
) {
  // Currencies are sorted by address, so native ETH is always currency0.
  const [currency0, currency1] =
BigInt(launch.pairToken) < BigInt(launch.launchToken)
  ? [launch.pairToken, launch.launchToken]
  : [launch.launchToken, launch.pairToken];
  return keccak256(
encodeAbiParameters(
  parseAbiParameters(
    "address currency0,address currency1,uint24 fee,int24 tickSpacing,address hooks",
  ),
  [currency0, currency1, launch.poolFee, launch.tickSpacing, hook],
),
  );
}
```

Confirm the ID against the PoolManager `Initialize` event in the
`PoolGraduated` transaction. Its `sqrtPriceX96` is the pool's opening price.

Then follow `Swap` on the PoolManager,
`0x8366a39CC670B4001A1121B8F6A443A643e40951`, filtered by pool ID. `id` is
indexed, so one `eth_getLogs` request can cover many pools. Split large ID
lists into groups, for example 100 IDs per request.

```ts title="pool-swaps.ts"
import { parseAbiItem, type Hex } from "viem";

const swaps = await publicClient.getLogs({
  address: "0x8366a39CC670B4001A1121B8F6A443A643e40951",
  event: parseAbiItem(
"event Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee)",
  ),
  args: { id: poolIds as Hex[] },
  fromBlock,
  toBlock,
});
```

Read each swap like this:

- `amount0` and `amount1` are the swapper's balance changes. Negative was paid
  in and positive was paid out. A negative launch-token amount is a sell, and a
  positive one is a buy.
- `sender` is the router or contract that called the PoolManager, not the
  user. Credit the trade to the transaction sender.
- `sqrtPriceX96` is the pool price after this swap. See below for how to turn
  it into a token price.
- `fee` is the swap fee in hundredths of a basis point, so `3000` is 0.3%.
  The Clank hook takes no extra fee, so the amounts are the whole trade.

### Pool price

`sqrtPriceX96` encodes how much `currency1` one unit of `currency0` is worth.
Two details matter when you convert it into a price for the launch token:

- **Order.** Currencies are sorted by address. ETH is always `currency0`, but
  an ERC-20 pair token can be either one, so check which side the launch token
  is on.
- **Decimals.** Launch tokens always have 18 decimals. Pair tokens may not, so
  read `decimals()` on the pair token. ETH has 18.

```ts title="pool-price.ts"
// Returns the price of one launch token, in pair tokens.
export function poolPrice(
  sqrtPriceX96: bigint,
  launchTokenIsCurrency0: boolean,
  pairDecimals: number,
) {
  // currency1 per currency0, in each token's smallest units
  const raw = Number(sqrtPriceX96) ** 2 / 2 ** 192;
  const pairPerLaunch = launchTokenIsCurrency0 ? raw : 1 / raw;
  return pairPerLaunch * 10 ** (18 - pairDecimals);
}
```

For example, for an ETH pair, `poolPrice(sqrtPriceX96, false, 18)` returns the
price in ETH. The result is a floating-point number, which is fine for display.
Use bigint math if you need exact values.

## Track holders

Launch tokens are standard ERC-20s. Build balances from `Transfer` events on
each token. Leave protocol addresses out of holder counts and top-holder lists:

- the token's curve, which holds the unsold tokens and the pool allocation
  until the sweep
- the factory, which holds reserves between the sweep and pool creation
- the PoolManager, which holds the graduated pool's liquidity
- the protocol locker, which holds the locked position and leftover token
  dust. Read `locker()` on the contract returned by the factory's
  `graduationExecutor()`.

## Handle reorgs

Persist the block number and block hash with every checkpoint. Before resuming,
verify that the saved block hash is still canonical. On a mismatch, rewind to a
previous confirmed checkpoint and replay logs in ascending block and log order.

Do not use polling searches as the source of truth. Backfill bounded block
ranges, follow new logs, and reconcile both paths through the same idempotent
event reducer.

Source: https://developer.clank.trade/guides/index-the-protocol/index.mdx
