Integration
Reading token state
Deriving a pool id, and the view calls that read a launch, its fees and its curve.
View as MarkdownA 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.
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. |
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.