> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cryptocheckout.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Platform architecture

> Edge functions, keepers, indexers, and how the off-chain system observes and drives the on-chain one.

## Shape

```
Browser (React SPA, Vite)
   │  SIWE cookie + JWT
   ▼
Supabase Edge Functions (Deno)  ──────►  RPC / TronGrid  ──────►  Chains
   │                                                                │
   ▼                                                                │
Postgres (RLS, display-only for the fund path)  ◄──── indexers ─────┘
   ▲
   └──── keepers (cron-driven): deploy, sweep, distribute
```

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

| Role                | Functions                                                                                                                                                                                 |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Quoting and intents | `create-quote`, `create-pool-connect`, `create-pool-deposit`, `create-pool`, `create-payment-intent`, `create-deposit-intent`, `create-payment-link`                                      |
| Status              | `payment-status`, `deposit-status`, `pool-deposit-status`, `verify-payment`, `claim-payment`, `submit-payment`                                                                            |
| Keepers             | `pool-sweep-keeper`, `distribute-keeper`, `settle-keeper`, `tron-pool-keeper`                                                                                                             |
| Indexers            | `pool-indexer-poll`, `indexer-poll`, `deposit-indexer-poll`, `tron-indexer-poll`                                                                                                          |
| Auth                | `siwe-nonce`, `siwe-verify`, `siwe-whoami`, `siwe-logout`, `link-wallet`, `create-session`                                                                                                |
| Webhooks            | `deliver-webhook`, `retry-webhooks`, `webhook-test-send`                                                                                                                                  |
| Admin and cron      | `platform-admin-set-fee`, `platform-admin-set-referrer`, `platform-admin-toggle`, `bind-referral`, `set-pool-cadence`, `cron-rescan-sanctions`, `cron-verify-payments`, `audit-log-alert` |

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**.

| Keeper              | Cadence           | Does                                                      |
| ------------------- | ----------------- | --------------------------------------------------------- |
| `pool-sweep-keeper` | 1 min             | Deploys forwarders, sweeps into pools. Must stay fast.    |
| `distribute-keeper` | Moving to monthly | Conditional backstop distribute plus treasury self-claim. |
| `tron-pool-keeper`  | —                 | TRON equivalent; already handles `rail === "connect"`.    |

<Info>
  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.
</Info>

Keeper gas is fronted in native currency and recovered in the settlement token. See [Gas reimbursement](/economics/gas-reimbursement).

<Warning>
  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.
</Warning>

## 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.

<Warning>
  `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.
</Warning>

## 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()`.

<Warning>
  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.
</Warning>

* 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:

```bash theme={null}
export SUPABASE_ACCESS_TOKEN=$(grep '^SUPABASE_ACCESS_TOKEN=' .env | cut -d= -f2-)
npx --yes supabase@latest functions deploy <name> \
  --project-ref pocyrcfbpfvthtyvupqk --no-verify-jwt
```

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.
