Docs & API

Build whatever you like on top: scripts, bots, dashboards, trading tools. You don't need a key or an allowlist, because your code calls the same contracts this site calls, and the only limits are your own RPC provider's. Each launch puts its entire supply into a Uniswap v4 pool paired with USDG, and that position stays locked.

Bots and snipers are welcome. There's no anti-snipe window, max buy, max wallet, cooldown or blacklist in any launch or in the hook, and the first block of every launch is open to everyone.

Contracts on Robinhood Chain

Addresses this site uses (chain id 4663)
RPC (public)https://rpc.mainnet.chain.robinhood.com
Portal0x7e2f5dEe1A846fF21eE946d2e450F64133d0fD6F
Hook0x04abDE4e77036178E0DF13d435B7b7f87265e8cc
PoolManager0x8366a39CC670B4001A1121B8F6A443A643e40951
UniversalRouter0x8876789976decbfcbbbe364623c63652db8c0904
Permit20x000000000022D473030F116dDEE9F6B43aC78BA3
USDG (every pool's pair)0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168

Decimals: USDG has 6, launch tokens have 18. Supply is always 1,000,000,000. Gas is paid in ETH; trades are in USDG, so a wallet needs a little of both.

Public JSON API

This site serves the pad's data as JSON. No key, no sign-up, and any website or bot can call it (CORS is open). Responses are cached for a few seconds at the edge, so poll as often as you like.

GET https://pad.robinlabs.fun/api/v1/config            # chain, RPC, every contract address, pool settings, live or not
GET https://pad.robinlabs.fun/api/v1/launches?limit=50  # newest first; &before=<block> pages back
GET https://pad.robinlabs.fun/api/v1/market?limit=30    # live price, market cap, change since launch
GET https://pad.robinlabs.fun/api/v1/tokens/0xToken     # one launch: pool key, price, tax waiting to flush, details
curl https://pad.robinlabs.fun/api/v1/market?limit=1
# { "live": true, "updatedAt": 1790400000, "tokens": [ { "address": "0x…", "symbol": "ROBIN",
#   "priceUsd": 0.0000021, "marketCapUsd": 2100, "changeSinceLaunchPct": 84.2,
#   "buyTaxBps": 200, "sellTaxBps": 400, "createdAt": 1790399000, "image": null } ] }

Reading launches and prices

Pick a source:

Option A: straight from the chain

Each launch is one LaunchCreated log on the portal. For a live price, read the pool's slot0 with a single extsload call on PoolManager. There's nothing else to run.

Option B: the pad-indexer HTTP API

pad-indexer stores launches, trades, holders and candles in its own database, so you can skip scanning logs yourself. The base URL below is this site's indexer when one is set.

GET https://your-indexer.example.com/launches
GET https://your-indexer.example.com/stats/:token
GET https://your-indexer.example.com/trades/:token?n=30
GET https://your-indexer.example.com/holders/:token?n=20
GET https://your-indexer.example.com/candles/:token?n=96&interval=900   # OHLCV; interval in seconds, 900 by default
curl https://your-indexer.example.com/stats/0xTokenAddress
# { "priceUsd": 0.000042, "marketCapUsd": 42000, "volume24hUsd": 3150.5,
#   "change24hPct": -2.7, "holders": 88, "txns24h": 19, "liquidityUsd": 7400 }

Buying and selling

A trade is one v4 exact-input swap on the launch's pool, sent through Uniswap's UniversalRouter. Whatever you spend (USDG on a buy, the token on a sell) needs two approvals first: the ERC-20 approves Permit2, then Permit2 approves the router. Each one lasts until it is used up, expires or is revoked. This site approves exactly the amount of each trade, never an unlimited amount, and the router's allowance expires after a day.

// Step 1: let Permit2 move the amount you're spending
erc20.approve(PERMIT2, amount)

// Step 2: let the router spend it through Permit2 (this site sets a 1-day expiry)
permit2.approve(token, ROUTER, amount, expiration)

// Step 3: the swap
router.execute(commands, inputs, deadline)
// commands = 0x10 (V4_SWAP)
// actions  = SWAP_EXACT_IN_SINGLE (0x06), SETTLE_ALL (0x0c), TAKE_ALL (0x0f)
// Robinhood Chain's UniversalRouter (v2.1.1) expects SIX fields in SWAP_EXACT_IN_SINGLE:
//   (PoolKey poolKey, bool zeroForOne, uint128 amountIn, uint128 amountOutMinimum,
//    uint256 minHopPriceX36 /* 0 turns it off */, bytes hookData)
// Encode only five and the call reverts without any error data.

// Getting a quote costs nothing and needs no balance. eth_call execute with two actions,
// SWAP_EXACT_IN_SINGLE then TAKE_ALL(currencyOut, type(uint256).max). That minimum can never
// be met, so the call reverts with V4TooLittleReceived(minAmountOutReceived, amountReceived):
// read the quote from amountReceived.

Pool order: whichever of the two addresses is smaller, compared as a number, is currency0. On a buy you pay USDG, so zeroForOne is true exactly when USDG is currency0; on a sell, when the launch token is.

Launching from code

A single transaction mints the token, opens its USDG pool and locks all 1B tokens in the pool position:

portal.createLaunch({
  name: "Sherwood Coin",
  symbol: "SHWD",
  startingMarketCapQuote: 10_000_000000n, // $10,000 in USDG's 6 decimals ($100 to $1T on-chain; the site offers up to $1M)
  buyTaxBps: 200,  // 2%, up to 1000 (10%)
  sellTaxBps: 400, // 4%
})
// emits LaunchCreated(token, creator, locker, splitter, poolId, quoteAsset,
//                     tokenIsToken0, buyTaxBps, sellTaxBps, tickLower, tickUpper,
//                     initSqrtPriceX96, name, symbol)

Every launch gets a splitter contract of its own, and the creator can claim from it whenever they like (see Fees).

Fees and claims

A trade pays two things: the pool's 1% LP fee, and the creator's tax for that side, anywhere from 0 to 10%, picked at launch and fixed from then on. The tax is always collected in USDG. The tax, plus whatever LP fees are earned in USDG, is shared 90% to the creator and 10% to the platform.

hook.flush(poolKey)            // anyone: pushes a pool's waiting tax into its splitter
locker.harvestFees()           // anyone: collects LP fees; USDG fees go to the splitter, token fees are burned
splitter.claim(to, USDG)       // creator only: pays out the creator's 90%
splitter.claimPlatform(USDG)   // anyone: pays the platform's 10% to the treasury

Swaps never pay anyone out directly, so if a payout address gets stuck or blocklisted, only its own claim fails and trading carries on. A creator can pass the role to another wallet in two steps: transferCreator, then acceptCreator from the new wallet.

Events

event LaunchCreated(
  address indexed token, address indexed creator, address locker, address splitter,
  bytes32 poolId, address quoteAsset, bool tokenIsToken0,
  uint16 buyTaxBps, uint16 sellTaxBps, int24 tickLower, int24 tickUpper,
  uint160 initSqrtPriceX96, string name, string symbol
);

event Swap( // emitted by PoolManager for every pool: filter on id
  bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1,
  uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee
);

event TaxCollected(bytes32 indexed poolId, address indexed quoteAsset, bool isBuy, uint256 amount);