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)

ContractAddress
SingleSidedMemeV4 (launcher)0x4B6CB7c3Fd2B0378C121C11265FAf4a7F19F10B5
PoolManager (V4)0xA999FFb2e9046f11670e7d8C62e0523F46B036bb
MinimalV4Router0xbD6D61f11C7Bf337Bf6cAB1CF8a1A05642518506
V4Quoter0x3ed514d12679dd627638a6E3921EaD6033bdce88
LTFactory0xc96c76898d2642a7c57d0f7676acb9409e00504c
NavBatchRelay0x1AB833fFEa5BF661c369fBAb5D55f665AAcDb179
USDG (6 dp)0x5fc5360d0400a0fd4f2af552add042d716f1d168
Uniswap V3 SwapRouter020xcaF681a66D020601342297493863E78c959E5cB2
Uniswap V3 QuoterV20x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7

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.