---
name: rohme-launchpad-interface
description: Build and ship a web interface for the ROHME Launchpad on Robinhood Chain (chain 4663) — explore every launched DAO, read live prices and treasury backing, swap on the Uniswap v4 curves, and found new DAOs. Use when the user wants to build a launchpad explorer, an alternative launchpad frontend, or any app that reads or writes the launchpad contracts.
---

# Build an interface for the ROHME Launchpad

You are building a web interface against the ROHME Launchpad — a live, on-chain
DAO factory on Robinhood Chain. Every launched token is a complete
treasury-backed DAO (token, staking, bonds, governor, redemption floor) trading
on two Uniswap v4 curves. All state you need is on-chain and permissionless to
read; no API key is required.

If the user wants an interface for ONE specific DAO rather than the whole
launchpad, prefer the companion skill `rohme-dao-interface`
(https://forum.rohme.capital/skills/rohme-dao-interface/SKILL.md).

## Ground rules

1. **On-chain reads first.** Every screen must render from RPC reads alone.
   Never blank a screen because an off-chain service is down.
2. **Never hand-write ABIs or guess addresses.** Fetch them from the hosted
   sources below at build time and commit them into the project.
3. **Never use a stock Uniswap SDK to encode swaps.** The chain's
   UniversalRouter is modified (details below); stock calldata mis-decodes.

## Chain

Define a custom viem chain — this chain is not in `viem/chains`. Include
`multicall3` or wagmi/viem batching silently breaks:

```ts
import { defineChain } from "viem";

export const robinhood = defineChain({
  id: 4663,
  name: "Robinhood Chain",
  nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
  rpcUrls: {
    default: {
      http: ["https://rpc.mainnet.chain.robinhood.com"],
      webSocket: ["wss://feed.mainnet.chain.robinhood.com"],
    },
  },
  blockExplorers: {
    default: {
      name: "Blockscout",
      url: "https://robinhoodchain.blockscout.com",
      apiUrl: "https://robinhoodchain.blockscout.com/api",
    },
  },
  contracts: {
    multicall3: { address: "0xcA11bde05977b3631167028862bE2a173976CA11" },
  },
});
```

## Addresses and ABIs (fetch these, do not transcribe)

Download once at project setup and commit into your repo:

- **Address manifest** (the launchpad singletons — factory, locker, registry,
  lens, hook, WETH, gROHM, start block, tick spacing, launch price):
  `https://forum.rohme.capital/skills/launchpad.4663.json`
- **ABIs** (JSON, viem-compatible), all under
  `https://forum.rohme.capital/skills/abi/`:
  `launch-factory.json`, `launchpad-lens.json`, `launch-registry.json`,
  `launch-locker.json`, `launch-token.json`, `staking.json`,
  `distributor.json`, `bond-depository.json`, `treasury.json`,
  `redemption-module.json`, `rohme-governor.json`, `timelock-controller.json`,
  `launch-policy-guard.json`, `grohm.json`, `universal-router.json`

Contracts are verified on Blockscout; use it to cross-check, but the hosted
files above are the canonical build inputs. If a fetch fails, stop and tell the
user — do not substitute a guessed ABI.

## Reading the launchpad

Everything hangs off the **LaunchFactory** (address in the manifest):

- `launchCount() → uint256` — DAOs ever created; ids are `1..launchCount`.
- `getLaunch(launchId) → Launch` — one struct with EVERY per-DAO address and
  parameter: `phase`, `abandoned`, `creator`, `totalSupply`, `launchPriceUsd9`,
  `tickSpacing`, `token`, `treasury`, `distributor`, `stakedToken` (rebasing),
  `govToken` (ERC20Votes), `staking`, `bondDepository`, `redemptionModule`,
  `policyGuard`, `timelock`, `governor`, `lister`, `pair`, `poolIdWeth`,
  `poolIdGRohm`, `startTickWeth`, `startTickGRohm`, `receipt`, `metadataURI`,
  `name`, `symbol`, and a `defaults` sub-struct of the economics. Use the
  hosted ABI for the exact field order — never hand-write this tuple.
- `isOpened(launchId) → bool` — **gate every trading/staking surface on this**,
  not on `phase` comparisons. A created-but-unopened launch has addresses but
  no live pools. Skip `abandoned` launches in listings.

The **LaunchpadLens** (address in the manifest) is a pure-view aggregator made
for interfaces — prefer it over assembling reads yourself:

- `launchView(launchId)` — the DAO's live dashboard numbers in one call.
- `poolView(...)`, `twapTick(...)`, `currentFee(...)` — per-pool price, TWAP,
  and the current dynamic fee.
- `tokenUsd9(launchId)` — the display price (9-dec USD). Display-only by
  protocol design: nothing on-chain mints or prices against it.
- `pendingFees(launchId)` — uncollected LP fees awaiting the crank.
- `legValues(...)`, `positions(...)`, `receiptOf(...)` — curve-leg backing.

Per-DAO deep reads (addresses from `getLaunch`): `treasury.totalReserves()` and
`treasury.excessReserves()` (the mint gate), `staking.index()`, bond markets on
the `bondDepository`, governance on the `governor`. Token amounts are
**9-decimal**; gTOKEN/gROHM are 18-decimal; USD values are 9-decimal
(`1e9 == $1.00`).

`metadataURI` is a hosted image URL (or empty). Render a neutral placeholder
when empty — an empty URI is normal, not an error.

## Swaps (the part everyone gets wrong)

Each DAO trades on two Uniswap v4 pools: TOKEN/WETH and TOKEN/gROHM. The
launch TOKEN is **always `currency0`**. Reconstruct each PoolKey as:

```
{ currency0: token, currency1: WETH-or-gROHM address (manifest),
  fee: 0x800000 (the dynamic-fee flag), tickSpacing: manifest tickSpacing,
  hooks: manifest hook }
```

v4 infra (same addresses live on this chain):
- PoolManager `0x8366a39CC670B4001A1121B8F6A443A643e40951`
- UniversalRouter `0x8876789976dEcBfCbBbe364623C63652db8C0904` ← **modified; canonical. A look-alike stock router exists on-chain — do not use it.**
- V4Quoter `0x8Dc178eFB8111BB0973Dd9d722ebeFF267c98F94` (unmodified stock — quote with standard encoding)
- StateView `0xf3334192d15450cdd385c8b70e03f9a6bd9e673b`
- Permit2 `0x000000000022D473030F116dDEE9F6B43aC78BA3`

**The router modification:** the swap-action structs carry one extra
`uint256 minHopPriceX36` field inserted between the amount bounds and
`hookData` (per hop on multi-hop variants). Setting it to `0` disables the
check and behaves exactly like the stock router. Because of this field, **stock
@uniswap SDK calldata mis-decodes** — the hookData offset word is read as the
price floor — so a stock-encoded swap may revert or pass with a garbage floor.
Encode `execute()` calldata yourself with the hosted `universal-router.json`
ABI. Command byte `V4_SWAP = 0x10` and action bytes
(`SWAP_EXACT_IN_SINGLE = 0x06`, `SETTLE_ALL = 0x0c`, `TAKE_ALL = 0x0f`) are
unchanged from stock.

ERC20 legs route through Permit2: token → Permit2 (ERC20 approve), then
Permit2 → router (`approve(token, router, amount, expiration)`). **Check the
expiration, not just the amount** — a pre-existing allowance with MAX amount
but an expired timestamp reverts `AllowanceExpired (0xd81b2f2e)` and looks
exactly like a healthy allowance if you only compare amounts.

Two fee facts to surface in the UI: the LP fee is **dynamic** — it opens high
(up to ~30%) and decays over a ~120s anti-MEV window after open to a ~1% steady
rate; read the live value from `lens.currentFee`. And fees accrue to locked
protocol positions — anyone may call `locker.collectAndSplit(launchId)` to
settle the 50/50 DAO/ROHME split (a nice "crank" button to include).

## Founding a DAO (write path)

`factory.launch(name, symbol, metadataURI, epochChoice)` — one transaction.
`epochChoice`: `0 = 1h, 1 = 2h, 2 = 4h, 3 = 8h` staking rebase cadence
(the ONLY economic knob a founder controls). Before calling:

1. Read `factory.launchFeeGRohm()` — a launch fee in gROHM (10 gROHM at
   deploy; mutable, so always read it live). The factory pulls it via
   `transferFrom` and burns it, so the founder needs a gROHM balance **and an
   ERC20 approval of at least the fee to the factory**.
2. Simulate first (`eth_call`) and surface revert reasons.

If the single `launch()` ever exceeds the chain's 32M tx gas ceiling, the
staged path `create()` → `build()` → `open()` (same args on `create`;
`build`/`open` are permissionless by `launchId`) does the same thing in three
transactions. Prefer `launch()`; fall back to staged on gas estimation failure.

## Shipping

- Any static-hostable stack works (Next.js, Vite, etc.); there is no required
  backend. The only runtime dependency is the public RPC.
- Deploy to the user's preferred host (Vercel works well). No secrets needed.
- Before calling it done, verify against mainnet with read-only calls:
  `launchCount()` returns ≥ 0, `getLaunch(1)` decodes (if any launch exists),
  `lens.launchView(1)` renders, and a swap **simulation** (`eth_call` of
  `execute()`) passes for a small WETH→TOKEN hop on an opened launch. Do not
  send funds during verification.

## What NOT to do

- Do not index events yourself to discover DAOs — `launchCount` + `getLaunch`
  is the enumeration; event indexing is an optional enrichment.
- Do not derive anything economic from the display price (`tokenUsd9`);
  backing, bonds, and mints derive from `treasury` reads on-chain.
- Do not hardcode the launch fee, the LP fee, or the launch price — all three
  are registry/factory state; read them live.
- Do not use `@uniswap/universal-router-sdk` (or any stock encoder) for swaps
  on this chain, and never re-sort PoolKey currencies.
