Private USDC tips for creators — support anonymously, settled gaslessly on Circle's Arc.
Thermal lets you back creators and journalists with private, anonymous USDC support. Public tipping turns who-you-fund into a permanent, searchable record — risky for supporters of dissidents or controversial voices. Thermal breaks that link: you pick an amount and support in one tap, and the fan→creator relationship never touches a public ledger. Creators only ever see an anonymous running total — never a supporter list, not even to us.
Under the hood, balances live in a shielded pool (deposits/withdrawals are the only on-chain footprint; who-pays-whom is not), signed off-chain authorizations are aggregated per creator, and settled in near-instant, gasless USDC transactions on Circle's Arc network. A one-click "Run Demo" replays the full path end-to-end against mock adapters for judges, and the exact same code flips to real on-chain settlement via a single MOCK config switch.
Monorepo, two services.
Frontend (web/): Next.js 15 (App Router) + TypeScript + Tailwind. Dynamic for wallet connect (falls back to a simulated multi-wallet picker in demo mode). The support UI: amount selector (presets + custom) → one shared supportOnce() core that signs a tip authorization (EIP-191 message) and posts it; an in-app "DEMO MODE" badge + scripted Run Demo for a reproducible judge walkthrough. State is in React hooks (no localStorage). Three.js/WebGL thermal-dither background.
Backend (server/): Fastify (Node 22, TS). A frozen shared API contract (onboard / deposit / tip / me/spent / creator balance / withdraw). The core is a ports + adapters architecture: every external service (Unlink privacy accounts, Circle settlement on Arc) is an interface with two implementations — mock and real — selected at one place by a MOCK config flag (assertRealConfig fail-fasts if real keys are missing). A per-creator batcher accumulates verified authorizations and settles them as a single aggregated, gasless USDC transaction. PRIVACY by construction: settlement items deliberately carry NO fanAccountId, so the fan→creator link never reaches the settlement layer; nonce-monotonic anti-replay + signature verification on every tip.
Real-mode rails (studied against Circle's arc-nanopayments / x402-batching and the Unlink admin SDK): viem on Arc testnet (USDC 0x3600…0000, Circle Gateway), real withdraw() via a funded settler. Settlement submission and fan registration are gated behind precise TODOs + DevRel questions rather than faked — so the demo stays honest and the switch to real is non-destructive.
Stack: Next.js 15, React 19, TypeScript, Tailwind, Three.js, Fastify, viem, Dynamic, @unlink-xyz/sdk, Circle Arc (USDC). Notably hacky-but-clean bit: the entire judged demo runs on mock adapters yet exercises the real money-path code, so MOCK=false is the only change needed to go live.

