Farcaster Frames

Every Regista 11 market is also a Farcaster Frame v2 surface. Cast regista11.xyz/frame/<market> in Warpcast and any user can stake in three taps: pick amount → pick side → sign. The flow runs entirely inside the cast — they never leave the feed.

The frame is implemented as three server routes that return Frame v2 metadata and handle the signed POSTs. None of it ships to the client bundle — it adds zero KB to the dApp.

The three routes

  • /frame/[market] — the initial GET that Warpcast renders. Returns Frame v2 metadata + the per-market image.
  • /api/frame/[market]/image — the dynamic OG image for the market (question, persona, current side balances).
  • /api/frame/[market]/sign — POST: produces the EIP-3009 typed-data the user's wallet will sign.
  • /api/frame/[market]/submit — POST: takes the signed typed-data, hands it to the x402 facilitator, and returns the resulting tx hash so Warpcast can show success.

What the user sees

warpcast flow
┌───────────────────────────────────────────────┐
│  [persona color bar]                    LIVE  │
│  Il Regista · 23'                             │
│                                               │
│  Will HOME keep a clean sheet                 │
│  for the next 30 minutes?                     │
│                                               │
│       OVER 46% ▌▌▌▌▌▌▌▌    54% UNDER         │
│                                               │
│  [ $1 ]   [ $5 ]   [ $20 ]   [ custom ]      │
└───────────────────────────────────────────────┘
        │
        ▼  tap $5
┌───────────────────────────────────────────────┐
│  Stake $5 on:                                 │
│         [ OVER ]      [ UNDER ]               │
└───────────────────────────────────────────────┘
        │
        ▼  tap OVER
┌───────────────────────────────────────────────┐
│  Sign with your wallet:                       │
│  USDT0 transfer · $5.00 · valid 5 min         │
│            [ Sign ]                           │
└───────────────────────────────────────────────┘
        │
        ▼  wallet signs
        ▼  /api/frame/[market]/submit
┌───────────────────────────────────────────────┐
│  ✓ Staked  ·  view on OKLink ↗                │
└───────────────────────────────────────────────┘

Auth cache

Between /sign and /submit the server holds a per-nonce pending authorization in an in-process Map. That lets /submit verify the signed payload matches the one we issued at /sign — no replay, no substitution. Cache entries TTL after the 5-minute validBefore window.

ts
// packages/web/src/app/api/frame/[market]/auth-cache.ts (excerpt)
interface PendingAuth {
  nonce: Hex;
  validBefore: bigint;
  expiresAt: number;
  marketAddress: Address;
  amountMicros: bigint;
  side: "OVER" | "UNDER";
}

const cache = new Map<Hex /* nonce */, PendingAuth>();

export function rememberAuth(auth: PendingAuth) {
  cache.set(auth.nonce, auth);
}

export function consumeAuth(nonce: Hex): PendingAuth | null {
  const e = cache.get(nonce);
  if (!e || Date.now() > e.expiresAt) return null;
  cache.delete(nonce);
  return e;
}