Back to Projects

ROBBED_

Memecoin Launchpad on an Arbitrum Orbit L2

Overview

Role: Main developer — owned it end-to-end: the seven Solidity contracts, the Ponder indexer, the Hono API and WS fanout, the auto-graduation keeper, the Next.js frontend, and the Docker/Cloudflare deployment.

What it is. A pump.fun-style token launchpad live on Robinhood Chain (chain ID 4663, an Arbitrum Orbit L2 with ETH gas, ~100ms blocks, and a single FCFS sequencer). One transaction launches a fixed-supply ownerless ERC-20 into a bonding curve; once the curve hits its flat ETH target, anyone can graduate it into a full-range Uniswap V3 position.

Anti-rug by construction. Seven immutable contracts, no proxies, one exact compiler pin, MIT and Blockscout-verified. Sells can never be paused — no flag or code path can block a curve sell, and no pause authority of any kind survives graduation. Both fee legs accrue in-contract as pull payments, so neither a hostile treasury nor a broken creator address can freeze trading. The graduated LP NFT lives in an ownerless vault whose only external function is `collect()` — principal mathematically cannot leave.

The stack behind it. A pnpm + Bun monorepo: a Ponder indexer writing venue-continuous candles, holder balances, and confirmation watermarks into Postgres; a Hono-on-Bun API serving 25+ REST endpoints plus a Bun WebSocket fanout driven by Redis pub/sub; a keeper service that fires the permissionless `graduate()` within a block or two; and a four-page Next.js 16 + React 19 frontend on wagmi/viem. Every cross-service type, ABI, and constant is defined once in a Zod-first shared package, with ABIs and deploy addresses generated rather than hand-written.

Deployment. Backend runs as a Docker Compose stack behind a Cloudflare Tunnel so the Redis→socket hop stays co-located; the Next.js frontend is SSR on Cloudflare Workers via OpenNext, with content-addressed images, canonical metadata, and rendered OG cards on R2.

Key Features

  • Seven immutable Solidity contracts — no proxies, one exact compiler pin, OpenZeppelin v5, MIT, Blockscout-verified at deploy
  • Sells can never be paused, and fee legs accrue as pull payments — a hostile treasury or broken creator address can never freeze trading
  • Permissionless graduation into a full-range Uniswap V3 position whose LP NFT is held by an ownerless vault with `collect()` as its only function
  • Ponder indexer → Postgres (+`pg_trgm`) with venue-continuous candles, holder balances, and three-tier confirmation watermarks
  • Hono-on-Bun API with 25+ REST endpoints plus a Bun WebSocket fanout over Redis pub/sub — event-to-browser budget under 500ms
  • Keeper service firing the permissionless `graduate()` within ~1–2 blocks of `GraduationReady`, with zero privileged role

Tech Stack

Smart Contracts

SolidityFoundryOpenZeppelin v5Uniswap V3Arbitrum Orbit

Indexer & Data

PonderPostgreSQLpg_trgmRedisTypeScript

API & Services

HonoBunWebSocketsviemZod

Frontend

Next.js 16React 19wagmi v2RainbowKitlightweight-chartsTailwind CSS

Infra & Testing

Docker ComposeCloudflare WorkersOpenNextCloudflare R2Playwrightpnpm

Challenges & Solutions

Making Rug-Pulls Structurally Impossible

Problem

Launchpad users have no reason to trust an operator's promise not to freeze withdrawals or drain liquidity, and any pause switch or push-payment fee path is a lever someone can eventually pull against them.

Solution

Removed the levers instead of promising not to use them: the only pause switches are on creates and buys — no code path can block a curve sell — and every fee leg accrues in-contract as a pull payment swept by permissionless functions, so a hostile treasury or a reverting creator address can at worst break its own claim. The graduated LP NFT goes into an immutable, ownerless vault whose sole external function is `collect()`, and no pause authority of any kind exists after graduation.

Graduating Into a Pool Someone Can Pre-Seed

Problem

Graduation mints liquidity into a Uniswap V3 pool that anyone can create and push to an arbitrary price first, turning the migration into a hostile-ratio mint that hands the attacker most of the raised ETH.

Solution

Created and initialized the V3 pool at token creation, at the deterministic graduation price, so there is no unclaimed pool to pre-seed. If the price is polluted anyway, the migrator arbs it back to the target tick from curve inventory within bounds and reverts rather than minting at a hostile ratio — leaving the curve retriable instead of drained. Because the pool starts at the curve's terminal price, the chart also continues as one series with no seam.

Sub-Second UX Without Lying About Finality

Problem

An Orbit L2 reflects trades at sequencer speed in about 100ms, but that is not settlement — showing an instant green check would be a finality claim the chain has not made, while waiting for L1 finality would throw away the entire speed advantage.

Solution

Made confirmation an explicit three-tier vocabulary — soft-confirmed, posted-to-L1, finalized — tracked by an indexer watermark poller and read-derived at query time from a row's block number, so tiers are monotonic by construction with no per-row write-back. Propagation is O(1): one broadcast per watermark advance upgrades every held event in every client. The UI renders optimistically and makes no finality claim on a fresh trade, and discloses the tier more prominently once a trade crosses 1 ETH.

Anti-Snipe Where Gas Auctions Don't Exist

Problem

The chain runs a single first-come-first-served sequencer, so priority fees cannot jump the queue — sniping is a pure latency race, and the usual gas-based mitigations are meaningless. Compounding it, `block.number` returns an L1 estimate on Orbit, so the obvious block-window guard silently misbehaves.

Solution

Capped per-transaction buys during an 8-second early window at 2.5% of the graduation target, making a latency win worth only a bounded slice instead of the whole curve, and made the creator's own initial buy atomic with creation so the launcher cannot self-snipe ahead of it. Banned `block.number` in contract logic outright — time-based logic uses `block.timestamp` or `ArbSys.arbBlockNumber()`.

Key Achievements

7 Contracts
Immutable, no proxies, MIT and Blockscout-verified at deploy
<500ms
Chain event to browser, WebSocket-only with no polling layer
Live on Mainnet
Deployed and trading on Robinhood Chain (chain ID 4663)
53 E2E Flows
Catalog enforced by a 1:1 static coverage gate in CI
ROBBED_ - Project | Oleksandr Yusypenko