Skip to main content

Architecture overview

The core idea

GrinGo Pay is a stateless orchestration layer between three actors:

  1. The user's Privy embedded smart wallet (holds USDC on Base).
  2. Bridge (KYC, virtual accounts for on-ramp, liquidation addresses for off-ramp).
  3. 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.bridgeEventId is unique — a replayed webhook is a no-op.
  • On-chain confirmation is best-effort. For USDC transfers we decode Transfer events 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-workers against a real Neon branch. Only external services we don't own (Bridge) are mocked.