> Bull Launch docs. Every page is available as Markdown by adding `.md` to its URL; the index is https://bulllaunch.fun/llms.txt. This page on the web: https://bulllaunch.fun/docs/reading-state

# Reading token state

Deriving a pool id, and the view calls that read a launch, its fees and its curve.

A pool id is derived, not looked up. It is `keccak256(abi.encode(PoolKey))`, and the two currencies have to be sorted by address rather than written base first: native USDC sorts below every launched token, but a base that sorts above one gives a mirrored pool, and an unsorted key produces an id that matches no pool at all. `launches()` then returns zeroes and the coin reads as "not a launch" instead of failing loudly.

```js
import { createPublicClient, http, keccak256, encodeAbiParameters, zeroAddress } from "viem";

const arc = {
  id: 5042002,
  name: "Arc Testnet",
  nativeCurrency: { name: "USD Coin", symbol: "USDC", decimals: 18 },
  rpcUrls: { default: { http: ["https://rpc.testnet.arc.io"] } },
  contracts: { multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" } },
};
const client = createPublicClient({ chain: arc, transport: http() });

const HOOK     = "0xe332196A3bf409899E990846373cf47255e7b044";
const REGISTRY = "0xaB53045522C110DEB14f46f39322f5bD43956bD1";

// Pool key is fixed by the factory: dynamic fee flag, tick spacing 60, the hook.
const DYNAMIC_FEE_FLAG = 0x800000;
const TICK_SPACING = 60;

// Sorted by address, not base first. This is the one step that silently
// returns a wrong-but-valid id if you skip it.
function poolKeyOf(token, base = zeroAddress) {
  const [currency0, currency1] =
    BigInt(base) < BigInt(token) ? [base, token] : [token, base];
  return { currency0, currency1, fee: DYNAMIC_FEE_FLAG, tickSpacing: TICK_SPACING, hooks: HOOK };
}

function poolIdOf(token, base = zeroAddress) {
  const k = poolKeyOf(token, base);
  return keccak256(
    encodeAbiParameters(
      [{ type: "address" }, { type: "address" }, { type: "uint24" }, { type: "int24" }, { type: "address" }],
      [k.currency0, k.currency1, k.fee, k.tickSpacing, k.hooks]
    )
  );
}
```

With the id in hand, everything else is a view call. The functions below are the ones the app itself reads.

| Read | Call |
| --- | --- |
| Launch record | `Hook.launches(poolId)` |
| Pool orientation | `Hook.baseIsCurrency0(poolId)` |
| Swap fee, live | `Hook.swapFeeBps()` |
| Protocol share, live | `Hook.protocolFeeShareBps()` |
| Unclaimed fees | `Hook.pendingFees(poolId, token)` |
| Graduation target for a base | `ConfigRegistry.baseCurve(base)`, returns (startAmount, fillAmount) |
| Exact raise to graduate | `ConfigRegistry.baseFillAmountActual(base)` |
| Enabled bases | `ConfigRegistry.getEnabledBases()` |
| Creator tax ceiling | `ConfigRegistry.maxTaxBps()` |
| Total supply constant | `LauncherFactory.TOTAL_SUPPLY()`, 1e27 |
| Remaining raise | `PositionManager.getPositionLiquidity(mainBandTokenId)`. Answers 0 once graduated, because the band is withdrawn whole. |
| Spot price | `StateView.getSlot0(poolId)` |
| Quote a trade | `V4Quoter`. It runs the real hook, so a quote includes the fee and the tax. See [Quotes and swaps](https://bulllaunch.fun/docs/quotes-and-swaps.md). |

> Read the pool through StateView and PoolManager with a pool id. Uniswap v4 has no per-pool contract, so there is no `slot0()` to call on a pool address the way there was in V3.
