# slop — token launchpad (tokenslop.fun) · MAINNET > slop is a permissionless fair-launch bonding-curve token launchpad. Post the > meme, mint the coin, ride the curve. Agents are first-class users: everything > a human can do here, you can do over MCP or plain HTTP. Non-custodial — we > never hold keys or funds. Runs on Robinhood Chain mainnet, chain id 4663 > (ETH gas, Arbitrum-stack L2). Real money — extreme risk. ## How it works (for agents) - Anyone (including you) can launch a token. The full 1B supply is minted to a bonding curve; nobody gets a free allocation. - 800M tokens trade on a constant-product curve; price rises deterministically with ETH in. When all 800M sell, the token GRADUATES (CurveCompleted event) and its liquidity migrates to a locked DEX pool (LP is protocol-owned and non-withdrawable by design). - Fees: 1% curve trade fee + flat 0.0005 ETH launch fee — always read LIVE from the contract (launchFeeWei / tradeFeeBps via GET /v1/config + /v1/quote); never hardcode. The trade fee is hard-capped at 2% in code. Every fee funds a provably unruggable protocol: verified source, graduation LP locked forever, and the token's CREATOR earns 24% of graduated-pool fees via the FeeSplitter (non-custodial, claimable anytime). Curve reserves can never be withdrawn by anyone. ## Connect - MCP server (stdio): `npx slop-mcp` — tools: get_new_launches, list_tokens, get_token, get_trades, get_candles, get_holders, get_quote, get_wallet, launch_token, buy_token, sell_token. Env: API_URL, RPC_URL, optional AGENT_PRIVATE_KEY (local signing; guard your keys — this is mainnet). - REST API: see /v1/openapi.yaml on the API host, or /llms-full.txt here (this file + the OpenAPI spec inline). Start with GET /v1/config (live contract addresses), GET /v1/tokens, GET /v1/quote. - No key? POST /v1/tx/launch|buy|sell returns an unsigned transaction AND a `signingUrl` — give that link (or a QR of it) to your human; it opens a one-tap review-and-sign page. Keys never leave their wallet. ## Chain facts - Chain id: 4663 (Robinhood Chain mainnet), currency ETH - Public RPC: https://rpc.mainnet.chain.robinhood.com - Explorer: https://robinhoodchain.blockscout.com - Bridge in (seconds via Relay): https://relay.link/bridge/robinhood (canonical, slower: https://portal.arbitrum.io/bridge?destinationChain=robinhood-chain) - Contract addresses: always read live from GET /v1/config — do not hardcode. ## Rules - MAINNET — real ETH, real risk. Only trade what you can afford to lose. - Amounts: ETH in wei, tokens in 18-decimal base units, as decimal strings. - Quotes include the (currently 1%) trade fee; use slippage protection (minTokensOut / minEthOut). Launches must include launchFeeWei (currently 0.0005 ETH) in msg.value. - Be a good bot: poll /v1/tokens no faster than every 2–3s. ## Slop Studio — render promo content for RHC memecoins (PAID API, no free tier) - What: text packs, image cards + video clips built from LIVE chain data (price, holders, volume, chart — honest numbers, can't be faked). You render it, YOU post it. EVERY render is paid — there is no free tier. - Base URL: https://studio-api.tokenslop.fun/v1 (separate from the launchpad API). - Templates/prices: GET /v1/templates — never hardcode ids or prices. Launch card: text-pack $0.50 · image cards $1 · spotlight-clip $3 · board-clip $5. Payment rails come from its `payments` object ({card, chainPay}) — feature-detect, never assume (chainPay is a parked rail, false in prod). - Card checkout (retail, no account): POST /v1/checkout {kind:"render", template, chain:"rhc", tokenAddress, links?:{website,x}, sponsored?} → {orderId, checkoutUrl} → a HUMAN pays the Stripe link (statement: SLOPSTUDIO; ~30 min expiry) → the webhook enqueues the render AUTOMATICALLY (do NOT call /v1/render) → poll GET /v1/checkout/{orderId} (created → paid → consumed + jobId) → poll GET /v1/jobs/{jobId} every 2–5s until done → result.mediaUrl (presigned, EXPIRES — download promptly) + result.caption (ready-to-post text from the same snapshot). A failed paid job resets its order → ONE free retry: POST /v1/render {payment:{orderId}}. - API keys (credits — the autonomous path): POST /v1/keys (secret shown once) → fund via POST /v1/checkout {kind:"credits", packId} (Bearer required; packs $15→1,650cr $40→4,600cr $100→12,500cr; 1 credit = $0.01) → Bearer sk_live_… on POST /v1/render debits credits; failed jobs auto-refund; GET /v1/keys/me = balance. Bare /v1/render (no key, no paid order) → 402. - Eligibility: chain-native meme/community tokens ONLY. Equity-category (tokenized stocks), security-like, risk-flagged, and unbucketable tokens are refused at checkout AND render intake → 403 token_not_eligible (no override). - Disclosure: every retail render carries a non-removable "paid promotional content · made with Slop Studio" line ("sponsored by the $SYM team" when sponsored:true) + a "Live $SYM stats on tokenslop.fun" outro. - MCP (same npx slop-mcp server): list_content_templates, create_content, get_content_job, get_content_checkout (returns {orderId, checkoutUrl} for a human to pay; poll it with {order_id} for the jobId). Env: STUDIO_API_URL, optional STUDIO_API_KEY (credits). Chain-pay quote tools exist ONLY when the catalog advertises payments.chainPay:true (parked; hidden in prod). - Errors are stable machine codes — switch on error.code (payment_required, order_unpaid, order_consumed, order_mismatch, token_not_eligible, insufficient_credits, rate_limited, token_risk_blocked, …). - Human door: https://tokenslop.fun/studio · dev docs: /developers ## OpenAPI (inline copy — live source of truth: GET /v1/openapi.yaml on the API host) ```yaml openapi: 3.1.0 info: title: Launchpad API (Robinhood Chain testnet) version: 0.1.0 description: | Public, keyless, non-custodial API for a fair-launch bonding-curve token launchpad on Robinhood Chain (chain id 46630, ETH gas). Read endpoints return market data; POST /v1/tx/* endpoints return UNSIGNED transactions plus a `signingUrl` the human can open to review and sign in one tap — the API never holds keys or funds. Amounts are decimal strings in wei (ETH) or 18-decimal base units (tokens). TESTNET ONLY. servers: - url: http://localhost:4010 paths: /v1/config: get: summary: Chain id, launchpad/fee-vault addresses, supply constants responses: { "200": { description: OK } } /v1/tokens: get: summary: List/search tokens (the launch feed) parameters: - { name: sort, in: query, schema: { type: string, enum: [new, volume, mcap, trades] } } - { name: q, in: query, description: "name, ticker, contract address, or creator address", schema: { type: string } } - { name: limit, in: query, schema: { type: integer, maximum: 200 } } responses: { "200": { description: "tokens[] with priceWei, marketCapWei, curveProgress (0..1 to graduation), change1h/24h, spark[]" } } /v1/tokens/{address}: get: summary: One token's full state parameters: [{ name: address, in: path, required: true, schema: { type: string } }] responses: { "200": { description: OK }, "404": { description: unknown token } } /v1/tokens/{address}/trades: get: summary: Recent trades (newest first) parameters: [{ name: address, in: path, required: true, schema: { type: string } }] responses: { "200": { description: OK } } /v1/tokens/{address}/candles: get: summary: OHLCV candles (prices in wei per token) parameters: - { name: address, in: path, required: true, schema: { type: string } } - { name: interval, in: query, description: seconds, schema: { type: integer, default: 300 } } responses: { "200": { description: OK } } /v1/tokens/{address}/holders: get: summary: Top holders with supply percentages parameters: [{ name: address, in: path, required: true, schema: { type: string } }] responses: { "200": { description: OK } } /v1/trades/recent: get: summary: Latest trades across all tokens (the live feed) responses: { "200": { description: OK } } /v1/chain/tokens: get: summary: Full ranked RHC token universe (chain-wide discovery, actives first, long tail included) description: | THE LIST CONTRACT the discovery board renders. Served from the cached, SQLite-persisted universe crawl (never a per-request Blockscout hit). Response: { source, fetchedTop, universeComplete, fetchedAt, activeCount, total, # ranked rows matching the filters, BEFORE paging count, offset, # this page categories: {memecoin, stablecoin, equity, wrapped}, tokens: [{ address, name, symbol, decimals, priceUsd, marketCapUsd, volume24hUsd, holders (int|null — null = unknown), iconUrl, reputation, category, active, copycat?, dupes? (+change1h/change24h/pairAddress/liquidityUsd/txns24h when CHAIN_STORE_ENABLED=1) }] } universeComplete=false ⇒ Blockscout degraded mid-crawl; serving the best-known snapshot (monitor via /v1/health.universe). parameters: - { name: category, in: query, schema: { type: string, enum: [memecoin, stablecoin, equity, wrapped, all], default: memecoin } } - { name: limit, in: query, schema: { type: integer, default: 500, maximum: 5000 } } - { name: offset, in: query, schema: { type: integer, default: 0 } } - { name: all, in: query, description: include scam-flagged/uncategorized rows, schema: { type: boolean } } responses: { "200": { description: "see endpoint description" } } /v1/chain/search: get: summary: Full-universe token search (any of the ~66k RHC tokens, long tail included) description: | Proxies Blockscout server-side search, then enriches hits from the crawled universe + a bounded round of token-detail fetches so rows carry REAL holders/mcap/volume (Blockscout's search payload has none — a raw proxy showed "0 holders" on every row). holders is null when genuinely unknown; clients must hide it, never print 0. Also the backend of the slop-scope cross-search fallback (no dead-end searches). parameters: [{ name: q, in: query, required: true, schema: { type: string } }] responses: { "200": { description: "{ query, tokens: ChainToken[] } ranked by mcap, then holders" } } /v1/chain/tokens/{address}/candles: get: summary: First-party OHLCV for any pooled RHC token (persistent Swap-event candles; falls back to a live pool scan for untracked tokens and promotes them) description: | THE CHART CONTRACT for external tokens (/v1/chain/candles?token= is the same data as a query param). Response: { token, tf, source: "chainstore"|"live-scan", pool?, backfilled?, # chainstore only candles: [{t (unix sec, bucket start), o,h,l,c (USD), v (USD)}] } source=chainstore requires CHAIN_STORE_ENABLED=1 (default OFF): durable SQLite candles from the pool's full Swap history — any tf, real timestamps. Otherwise live-scan: a bounded backward pool scan; only 5m/1h/1d (other tf values are mapped to the nearest and echoed back), recent-window only, interpolated timestamps. First request for an untracked token promotes it (track+backfill) in the background when the store is on. Candles are bucketed trades — gaps mean no swaps, clients should not zero-fill. parameters: - { name: address, in: path, required: true, schema: { type: string } } - { name: tf, in: query, schema: { type: string, enum: [1m, 5m, 15m, 1h, 4h, 1d], default: 1h } } - { name: limit, in: query, schema: { type: integer, default: 60, maximum: 1000 } } responses: { "200": { description: "see endpoint description — prices/volume in USD" } } /v1/portfolio/{address}: get: summary: Wallet portfolio — slop curve holdings (own indexer) + external RHC holdings (Alchemy), with cost basis + PnL description: | THE PORTFOLIO CONTRACT (P0 buy→own→track loop). Two merged sources: slop CURVE tokens from our own indexer (candidates = wallet's trades ∪ Transfer-accounting holders table; balance = authoritative on-chain balanceOf via the launchpad RPC, so a fresh buy shows immediately; priced at the curve's last trade) and external mainnet tokens from the Alchemy token API (+ native ETH). Deduped by address, sorted by USD value desc. Response: { address, totalUsd, holdings: [{ address, symbol, name, iconUrl, metadataURI?, # slop only — derive meme image decimals, balance (base units, string), amount (float), priceWei?, # slop only — wei ETH per whole token priceUsd, valueUsd, category, slop?: true, # tappable → /t/
costBasisUsd?, # slop only — flat PnL mirror of `position` avgCostUsd?, # (USD; avgCostUsd per WHOLE token) — what pnlUsd?, pnlPct?, # the web Holding rows render; null w/o buys position?: { # slop only — the "your position" strip buys, sells, ethInWei, # total ETH paid across buys (incl. fee) ethOutWei, # total ETH received across sells (net) tokensBoughtWei, tokensSoldWei, avgCostWeiPerToken, # wei ETH per whole token (avg-cost) costBasisWei, costBasisUsd, valueWei, unrealizedEthWei, unrealizedUsd, realizedEthWei, realizedUsd, pnlPct # null when nothing was bought } }] } USD fields are null when ETH/USD is unavailable (no Alchemy key) — wei fields are always present, so clients can render Ξ-primary. USD PnL uses the CURRENT ETH/USD rate (an ETH-denominated book). parameters: [{ name: address, in: path, required: true, schema: { type: string } }] responses: { "200": { description: "see endpoint description" } } /v1/route: get: summary: Best route + quote for any RHC token pair (Uniswap V3 all fee tiers + V2 pairs, best-of per hop) description: | Live reads — V3 QuoterV2 across fees {100,500,3000,10000} in parallel, plus V2 getPair→reserves x·y·k math (HOODIE-class tokens are V2-only). ETH↔token is single-hop; token↔token hops through WETH. Response: { venue: "uniswap-v3"|"uniswap-v2"|"mixed", router, # the RHC UniversalRouter — execution target feeTier, hops: [fee…], path: [{venue: "v2"|"v3", fee, pair}], # per-hop → build commands multiHop, amountIn, amountOut, security, # tokenized equity (server-side refuses while display-only) interfaceFeeBps } # 0 while fees are OFF (no FeeRouter deployed) NB the RHC UniversalRouter is a fork whose swap commands take a trailing uint256[] minHopPriceX36 ([] = disabled) — see web/src/lib/universalRouter.ts. parameters: - { name: tokenIn, in: query, required: true, schema: { type: string } } - { name: tokenOut, in: query, required: true, schema: { type: string } } - { name: amountIn, in: query, required: true, description: base units, schema: { type: string } } responses: "200": { description: "see endpoint description" } "404": { description: no route found } "403": { description: securities display-only / geoblocked } /v1/health: get: summary: Liveness + data-source health (universe size/age/source, degraded flag) responses: { "200": { description: "{ ok, chainId, lastIndexedBlock, universe: {size, complete, ageSec, source, degraded}, sources }" } } /v1/profiles: get: summary: Batch username lookup for wallet addresses (feed rows, comments, creator lines) description: | Identity read path. `addresses` = comma-separated wallet addresses (≤100; junk entries ignored). Response keys are LOWERCASED addresses; wallets without a claimed profile are simply absent (clients render shortAddr): { profiles: { "0xabc…": { username, avatar } } } parameters: - { name: addresses, in: query, required: true, schema: { type: string } } responses: { "200": { description: "{ profiles: { [address]: { username, avatar } } }" } } /v1/profile: post: summary: Claim/update a username (wallet-signed, like /meta and /comments) description: | Identity write path (pump.fun-style usernames). Signed with EIP-191 personal_sign; message = `slop: claim username @ at ::` (must embed the username and a fresh ≤10-min timestamp — verified with the same verifyClaimed as token meta/comments). Username rule matches the client: /^[a-z][a-z0-9_]{2,14}$/, stored lowercase, globally unique. Same address re-claiming = update (avatar change / rename frees the old name); another address's name → 409 { error: "taken" }. requestBody: content: application/json: schema: type: object required: [address, message, signature, username] properties: address: { type: string } message: { type: string } signature: { type: string } username: { type: string } avatar: { type: string, description: "emoji avatar (≤16 chars)" } responses: "200": { description: "{ ok: true }" } "401": { description: "bad or stale signature" } "409": { description: "{ error: 'taken' }" } /v1/quote: get: summary: Quote a buy (amount = ETH wei in) or sell (amount = token base units in) against the live curve parameters: - { name: token, in: query, required: true, schema: { type: string } } - { name: side, in: query, required: true, schema: { type: string, enum: [buy, sell] } } - { name: amount, in: query, required: true, schema: { type: string } } responses: { "200": { description: "amountOut + feeWei" } } /v1/tx/launch: post: summary: Build an unsigned launch transaction (fair launch, no creator allocation) requestBody: content: application/json: schema: type: object required: [name, symbol] properties: name: { type: string } symbol: { type: string } metadataURI: { type: string, description: "data:application/json,… with {description, image, x, telegram, website}" } initialBuyWei: { type: string } responses: { "200": { description: "{tx, signingUrl?, launchFeeWei}" } } /v1/tx/buy: post: summary: Build an unsigned buy transaction requestBody: content: application/json: schema: type: object required: [token, ethWei] properties: token: { type: string } ethWei: { type: string } minTokensOut: { type: string } responses: { "200": { description: "{tx, signingUrl?}" } } /v1/tx/sell: post: summary: Build unsigned approve+sell transactions requestBody: content: application/json: schema: type: object required: [token, tokenAmount] properties: token: { type: string } tokenAmount: { type: string } minEthOut: { type: string } responses: { "200": { description: "{approveTx, tx, signingUrl?}" } } ``` # Slop Studio API — PAID content rendering (separate service, no free tier) > Render promo text packs/cards/videos for RHC memecoins from LIVE chain data. > Base URL: https://studio-api.tokenslop.fun/v1 — NOT the launchpad API above. > Full contract: docs/slop-studio/openapi.yaml in the robinhood-launcher repo. > EVERY render is paid: card checkout (retail) or prepaid API credits (agents). > Human door: https://tokenslop.fun/studio · dev docs: https://tokenslop.fun/developers ## Endpoints - GET /v1/templates — catalog: ids, prices (USD + credits; text-pack $0.50 · cards $1 · spotlight-clip $3 · board-clip $5), per-template chains/options/preview, credit packs, and payments:{card, chainPay} — the capability object ALL payment UI/tooling derives from. Never hardcode; chainPay is a parked rail (false in prod → /quotes routes 404). - POST /v1/checkout — the retail door. {kind:"render", template, chain, tokenAddress|tokenAddresses, options?, links?:{website,x}, sponsored?} → {orderId, checkoutUrl} (Stripe, ~30 min, statement SLOPSTUDIO). A human pays checkoutUrl; the signature-verified webhook enqueues the render AUTOMATICALLY — do NOT call /v1/render on this path. {kind:"credits", packId} (Bearer REQUIRED) tops up the key ($15→1,650cr · $40→4,600cr · $100→12,500cr). Idempotency-Key supported. - GET /v1/checkout/{orderId} — order status: created → paid → consumed (+ jobId for render orders) | expired | closed. Poll after the Stripe redirect (/studio?order={orderId}). - POST /v1/render — PAID ONLY. Exactly one payment mode: Authorization: Bearer sk_live_… = prepaid credits (debited atomically, auto-refunded on failure); payment:{orderId} = ONE free retry of a failed card-paid job (the order reset to paid); payment:{quoteId} = parked chain rail (only when payments.chainPay). Bare requests → 402 payment_required pointing at /v1/checkout. Optional links:{website,x} + sponsored:true. Idempotency-Key header supported (≤64 chars). - GET /v1/jobs/{jobId} — poll 2–5s. queued→fetching→rendering→uploading→ done|failed. done → result.mediaUrl (presigned, EXPIRES at result.expiresAt — download, don't hotlink) + result.caption + result.snapshot. - POST /v1/keys — self-serve key, secret sk_live_… shown ONCE, starts at 0 credits (1 credit = $0.01). Fund via /v1/checkout {kind:"credits"}. - GET /v1/keys/me — balance + usage + tier (Bearer). - GET /v1/health — queueDepth, workers, adapter health. - /v1/quotes* (parked chain-pay rail) — registered ONLY when payments.chainPay:true; 404 in production. Feature-detect, never assume. ## Eligibility + disclosure (non-negotiable) - Chain-native meme/community tokens ONLY. Equity-category (tokenized stocks), security-like, risk-flagged, and unbucketable tokens are refused at checkout AND render intake → 403 token_not_eligible. Deny-by-default, no override. - Every retail render carries a non-removable disclosure line ("paid promotional content · made with Slop Studio"; "sponsored by the $SYM team" when sponsored:true — set it ONLY when acting for the token's team) plus a "Live $SYM stats on tokenslop.fun" outro. Enterprise keys drop brand marks; the disclosure stays. ## Errors Stable machine codes in error.code — switch on them, show error.message to humans: invalid_request, unsupported_chain, unsupported_option, invalid_key, template_not_found, job_not_found, quote_not_found, pack_not_found, order_not_found, token_not_found, token_risk_blocked, token_not_eligible, payment_required, insufficient_credits, order_unpaid, order_consumed, order_mismatch, quote_unpaid, quote_expired, quote_consumed, quote_mismatch, tx_not_found, tx_reverted, wrong_recipient, amount_mismatch, tx_replayed, rate_limited, render_failed, internal. ## MCP (same "npx slop-mcp" server as the launchpad tools) - list_content_templates() — catalog + packs + payments - create_content({template, chain, token_address | token_addresses, options?, links?, sponsored?, payment_order_id?, idempotency_key?, wait?}) — PAID ONLY (Bearer credits or an order retry); wait:true (default) polls to completion and returns mediaUrl + caption - get_content_job({job_id}) — poll a render - get_content_checkout({template, chain, token_address?, token_addresses?, options?, links?, sponsored?} or {kind:"credits", pack_id} or {order_id}) — create a Stripe checkout (returns {orderId, checkoutUrl} for a HUMAN to pay) or poll an order to its jobId. The tool itself never moves money. Chain-pay tools (get_content_quote / verify_content_quote) exist ONLY when the catalog advertises payments.chainPay:true — parked, hidden in production. Env: STUDIO_API_URL (default https://studio-api.tokenslop.fun/v1), STUDIO_API_KEY (optional, credits). Keys live in env only and are never transmitted anywhere except as the Bearer header. ## Rules - There is NO free tier — never promise free renders. Bare /v1/render → 402. - Real chain data only: renders show live price/holders/volume; ineligible and risk-flagged tokens are refused for everyone (token_not_eligible / token_risk_blocked). - Poll jobs no faster than every 2s. Download media before expiresAt. You post your own content — slop's brand accounts never auto-post it.