Testnet
Reading token state
Bull Launch is running on Arc Chain Testnet. Balances are test units with no value, and the stack may be redeployed.

Integration

Reading token state

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

View as Markdown

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.

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.

ReadCall
Launch recordHook.launches(poolId)
Pool orientationHook.baseIsCurrency0(poolId)
Swap fee, liveHook.swapFeeBps()
Protocol share, liveHook.protocolFeeShareBps()
Unclaimed feesHook.pendingFees(poolId, token)
Graduation target for a baseConfigRegistry.baseCurve(base), returns (startAmount, fillAmount)
Exact raise to graduateConfigRegistry.baseFillAmountActual(base)
Enabled basesConfigRegistry.getEnabledBases()
Creator tax ceilingConfigRegistry.maxTaxBps()
Total supply constantLauncherFactory.TOTAL_SUPPLY(), 1e27
Remaining raisePositionManager.getPositionLiquidity(mainBandTokenId). Answers 0 once graduated, because the band is withdrawn whole.
Spot priceStateView.getSlot0(poolId)
Quote a tradeV4Quoter. 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.