03 · docs/ARCHITECTURE.mdOpenPons manual

Architecture

            ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
  humans ─► │  Web app     │   │  SDK (TS)    │   │  MCP server  │ ◄─ agents
            │  Next.js     │   │ packages/sdk │   │ packages/mcp │
            └──────┬───────┘   └──────┬───────┘   └──────┬───────┘
                   │  fetch /api/v1   │  fetch /api/v1   │  fetch /api/v1
                   ▼                  ▼                  ▼
            ┌─────────────────────────────────────────────────────┐
            │  REST API  (src/app/api/v1/*)  — JSON envelope,     │
            │  validation, rate limit, consistent errors          │
            └──────────────────────┬──────────────────────────────┘
                                   ▼
            ┌─────────────────────────────────────────────────────┐
            │  Pons data layer (src/lib/pons)                     │
            │   reads.ts    launch index, provenance, token/curve │
            │               reads, trades, portfolio, launches    │
            │   prepare.ts  quotes, prepare buy/sell/launch/      │
            │               claim/sweep, simulate, integrity      │
            │   quote.ts    pure curve math (shared w/ browser)   │
            │   addresses/abis  verified contracts + ABIs         │
            └──────────────────────┬──────────────────────────────┘
                                   ▼
            viem public client (official RPC → optional fallbacks,
            retry, 4-way concurrency gate, Multicall3 batching)
                                   ▼
                     Robinhood Chain (4663) · Pons V2

  Browser signing path (never server-side):
  TxFlow → POST /prepare/* → preview + allow-list check
        → WalletProvider.sendPrepared → selected EIP-6963 provider
        → receipt via OpenPons RPC → verify

Key decisions

  • No indexer, no database. Everything is derived from chain state and logs at request time, with small in-process caches (globalThis.__openpons):
    • A rolling launch index of the last 150k blocks (about 4 h), refreshed incrementally.
    • A background backwards scan of LaunchSwept logs for graduated tokens.
    • Immutable token metadata is cached forever; curve state for 10 s.
  • Provenance by registry. A token counts as a Pons token only if factory.getLaunchedToken(token).exists holds and the token matches. Lists built from factory events are provenance by construction.
  • Quotes use the verified curve formula (buy: fee/tax/snipe then constant product with a virtual reserve; sell: constant product then fee/tax). They are checked wei-exact against eth_call.
  • Prepare = simulate. simulateContract from the user's address; custom errors are decoded from the verified ABIs. A sell without allowance is simulated with an eth_call state override of the OZ _allowances slot, and flagged as such.
  • Integrity pin. Factory and router runtime codehashes must match the pinned values before any write is prepared.
  • The server never signs. There is no key material anywhere in the codebase.

Rate-limit strategy

The public RPC returns 429 under bursts and caps eth_getLogs at 10k results. OpenPons:

  • gates concurrency (4 server-side),
  • batches reads via Multicall3,
  • chunks log scans (50k blocks for launches, adaptive halving for Transfer scans),
  • caches aggressively, and polls UI data at 12–20 s, only while the tab is visible.
Independent · not affiliated with Pons or Robinhood@openpons