Integrations
A guide for UIs, bots, indexers and contracts that want to trade or read perp.fun coins directly.
Coins launch into Uniswap V4 pools hosted on the protocol's own PoolManager, with LpGuardHook attached. Two consequences: swapping is fully permissionless, since the hook implements no swap callbacks and any V4-aware router can trade these pools — but providing liquidity is not, because beforeAddLiquidity reverts for every sender except the launcher. Don't build anything that expects to LP into a coin's pool.
Because these pools aren't on the canonical deployment, generic aggregators won't discover them and the canonical SwapRouter02 can't reach them. Resolve pools through the launcher.
Addresses — Robinhood Chain (chainId 4663)
| Contract | Address |
|---|---|
SingleSidedMemeV4 (launcher) | 0x4B6CB7c3Fd2B0378C121C11265FAf4a7F19F10B5 |
PoolManager (V4) | 0xA999FFb2e9046f11670e7d8C62e0523F46B036bb |
MinimalV4Router | 0xbD6D61f11C7Bf337Bf6cAB1CF8a1A05642518506 |
V4Quoter | 0x3ed514d12679dd627638a6E3921EaD6033bdce88 |
LTFactory | 0xc96c76898d2642a7c57d0f7676acb9409e00504c |
NavBatchRelay | 0x1AB833fFEa5BF661c369fBAb5D55f665AAcDb179 |
| USDG (6 dp) | 0x5fc5360d0400a0fd4f2af552add042d716f1d168 |
Uniswap V3 SwapRouter02 | 0xcaF681a66D020601342297493863E78c959E5cB2 |
Uniswap V3 QuoterV2 | 0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7 |
Source: src/single/SingleSidedMemeV4.sol, src/v4/, src/LTFactory.sol.
Discovering coins
function memesLength() external view returns (uint256);
function allMemes() external view returns (address[] memory);
function marketOf(address meme) external view returns (Market memory);Market carries everything needed to route — lt (the backing leveraged token), key (the V4 PoolKey: currencies, fee, tickSpacing, hooks), memeIsCurrency0, live, creator, and the tick bounds of the two protocol positions (mainLower/mainUpper for the MEME ask ladder, floorLower/floorUpper for the WETH floor). V4 positions are (owner, range, salt) storage slots on the singleton rather than NFTs, which is why the ranges live here and move on reanchor.
Or index the Launched(address indexed meme, address indexed lt, PoolId indexed poolId, address creator) event.
Trading
MinimalV4Router is the intended swap path:
function exactInputSingle(
PoolKey calldata key,
bool zeroForOne,
uint256 amountIn,
uint256 minAmountOut,
address to,
bool unwrapWeth
) external payable returns (uint256 amountOut);To buy with ETH, send msg.value == amountIn with WETH as the input currency and the router wraps for you. To sell to ETH, approve the coin to the router and set unwrapWeth = true when the output side is WETH.
zeroForOne follows the pool's currency ordering, so derive it from memeIsCurrency0: a buy (WETH → MEME) is !memeIsCurrency0, a sell (MEME → WETH) is memeIsCurrency0.
minAmountOut is enforced and reverts with Slippage(amountOut, minAmountOut) — there's no default tolerance. Quote through V4Quoter with simulateContract or eth_call: it's a simulate-and-revert lens, never a plain read.
Reading a coin's state
function poolWethValueUsdg(address meme) external view returns (uint256); // USDG, 6dp
function hedgeStartUsdgValue() external view returns (uint256);
function backingLtSharesOf(address meme) external view returns (uint256);
function claimableCreatorFees(address meme) external view returns (uint256 wethOut, uint256 memeOut);
function claimCreatorFees(address meme) external returns (uint256 wethOut, uint256 memeOut);A coin is hedged when poolWethValueUsdg(meme) >= hedgeStartUsdgValue(). Below that, harvest reverts BelowHedgeStart and no perp margin moves — surface that state, since it changes what the coin fundamentally is. poolWethValueUsdg is computed from position liquidity, so it can't be polluted by fees or foreign balances.
For the backing LT's value, read its oracle. peekNav() doesn't revert and is what you want for display; latestNav() reverts when stale and is what the vault uses on the money path. Always show NAV alongside its timestamp — a stale NAV means the backing value on screen isn't current and the vault is frozen, which a user needs to see rather than a confidently wrong number.
Launching programmatically
function launch(
string calldata name,
string calldata symbol,
string calldata metadataURI,
address backingLt
) external returns (address meme, PoolId poolId);backingLt must pass LTFactory.isLt() or the call reverts InvalidBackingLt, so resolve or create the LT for an (underlying, direction) pair through LTFactory first — createLtFor(underlyingSymbol, name, symbol, isShort) takes no leverage argument, since every backing LT is 5×. Supply, fee tier and range geometry come from launcher config — they aren't per-launch arguments.
Events worth indexing
Launched means a new coin and pool exist. Harvested is pool WETH → backing LT, so the hedge grew; Expanded is the reverse, margin coming home to refill depth. Reanchored means the ladder moved to track spot. BoughtBackAndBurned means perp gains bought the coin and burned part of it, so supply decreased. FeesAccrued and CreatorFeesClaimed cover the creator/protocol fee split.
Permissions
harvest, expand, reanchor and buybackAndBurn are onlyKeeper — they exist in the ABI but a third party cannot call them. Everything a trader needs (swap, quote, launch, read, claim creator fees) is permissionless.
