Architecture overview
The core idea
GrinGo Pay is a stateless orchestration layer between three actors:
- The user's Privy embedded smart wallet (holds USDC on Base).
- Bridge (KYC, virtual accounts for on-ramp, liquidation addresses for off-ramp).
- The GrinGo mobile app (Expo, presents everything in USD/BRL — no crypto UI).
GrinGo itself:
- Verifies every incoming request via Privy access tokens.
- Creates and reads Bridge resources on the user's behalf.
- Records a ledger of transactions in Postgres (Neon, via Cloudflare Hyperdrive).
- Processes Bridge webhooks to update transaction status.
- Never holds USDC. Never touches fiat. Never signs on-chain transactions.
System topology
Repo layout
See stack for tool-by-tool versions and data-model for the Postgres schema.
Request lifecycle: a PIX payment
The load-bearing flow. Every other flow is a variant of it.
Key architectural choices
- Stateless API. Every request is authenticated by Privy JWT alone; no server sessions.
- Hyperdrive in production, HTTP driver in dev/tests. Same Drizzle schema, two connection factories (
apps/api/src/db/index.ts). - Webhook idempotency at the API layer.
webhook_events.bridgeEventIdis unique — a replayed webhook is a no-op. - On-chain confirmation is best-effort. For USDC transfers we decode
Transferevents from the tx receipt to verify amount + recipient (apps/api/src/chain/usdc.ts), but authoritative status comes from Bridge's drain webhook. - No mocks in tests. API tests use
@cloudflare/vitest-pool-workersagainst a real Neon branch. Only external services we don't own (Bridge) are mocked.