Skip to main content

Shape

The frontend is a Vite React SPA on Vercel. Everything server-side is a Supabase edge function. There is no separate backend service.

Edge functions by role

All functions deploy with verify_jwt: false because they use custom cc_session or bearer authentication rather than Supabase Auth.

Keepers

The keeper is the platform’s only privileged runtime actor, and its privilege is liveness only.
The keeper re-resolves the recipient on-chain via effectiveRecipient and gates deploy and sweep on the re-derived address. It cannot be pointed at a different destination by tampering with the database.
Keeper gas is fronted in native currency and recovered in the settlement token. See Gas reimbursement.
The mainnet keeper wallet is unfunded on every chain. This is a launch blocker in its own right, and it needs low-balance alerting plus a native-gas inventory buffer.

Indexers

pool-indexer-poll watches forwarder addresses and pool events, drives pool_deposit_intents through its status machine, and enriches payer addresses via transaction lookups. It also re-derives the expected pool and forwarder independently and flags tamper_suspected rather than reconciling an unanchored pool.
WATCH_STATUSES omits swept and settled, so a terminalised intent’s forwarder stops being watched forever. A late payment after settlement is never observed — no event, no status change, no webhook. Funds are recoverable because the forwarder is re-callable, but nothing surfaces them.

RPC

Public keyless RPCs with CORS are the baseline: *.publicnode.com plus per-chain official endpoints. Alchemy is used where available. create-quote uses a viem fallback transport with two RPCs per chain and a 5-second per-attempt timeout. CSP connect-src allowlists publicnode, base.org, optimism.io, arbitrum.io, avax.network, polygon-rpc.com, llamarpc, infura, and ankr.

Data model

Merchant surfaces are pool-only since 2026-07-01. They read pool_deposit_intents, pool_distributions, and merchant_pools. paid_events and payment_intents are the legacy atomic rail and are dropped from all merchant reads. Platform-admin surfaces still use paid_events. Token amounts are stored in base units — USDC, USDT, and EURC are 6-decimal — and divided by 10^token_decimals for display.

Conventions

  • All wallet addresses are lowercased before storage and comparison, via _shared/wallet.ts:lc().
A lowercase_wallet_address trigger once corrupted TRON base58 addresses, which are case-sensitive. It is EVM-only now. Never apply case normalisation to a T… address.
  • The cc_session cookie is httpOnly and must never be read from JavaScript.
  • EIP-1271 verification uses the per-chain publicClient.verifyMessage, not viem’s top-level verifyMessage, which is EOA-only.

Deploying edge functions

Prefer the Supabase CLI over the MCP for anything non-trivial:
The CLI bundles real on-disk source with eszip: byte-exact, follows out-of-tree JSON and ABI imports natively, and has no 8KB string-literal truncation. The MCP path silently corrupts large hex literals such as contract bytecode. Use the MCP only for tiny single-file functions.