Data model
All tables live in Neon Postgres, defined via Drizzle under apps/api/src/db/schema/. Every table has a uuid primary key, createdAt and updatedAt timestamps unless noted.
Entity relationships
Table detail
users
| Column | Type | Notes |
|---|---|---|
id | uuid | PK |
privyDid | text | Unique — from Privy user_id |
email | text nullable |
wallets
The user typically has both a privy_embedded (EOA) row and a privy_smart_wallet row — the smart wallet is the one funded and sent from.
| Column | Type | Notes |
|---|---|---|
userId | uuid | FK → users |
address | text | Unique — 0x address on Base |
signerAddress | text | Nullable — EOA signer for smart wallets |
type | enum | privy_smart_wallet | privy_embedded | external |
kyc_verifications
| Column | Type | Notes |
|---|---|---|
userId | uuid | FK → users |
bridgeKycId | text | Unique nullable — Bridge KYC link id |
bridgeCustomerId | text | Nullable — set once Bridge issues a customer id |
status | enum | pending | approved | rejected |
virtual_accounts
Destination for fiat in (deposits). One per currency per user.
| Column | Type | Notes |
|---|---|---|
userId | uuid | FK → users |
bridgeVaId | text | Unique — Bridge VA id |
currency | enum | gbp | eur |
paymentRail | enum | fps | sepa |
bankName | text | Nullable |
accountHolderName | text | Nullable |
accountNumber / sortCode | text | GBP only, nullable |
iban / bic | text | EUR only, nullable |
depositInstructions | jsonb | Raw Bridge payload |
status | enum | active | closed |
bank_details
User's own bank account, used as the withdraw destination. Unique on (userId, currency).
| Column | Type | Notes |
|---|---|---|
userId | uuid | FK → users |
currency | enum | gbp | eur |
accountHolderName | text | |
accountNumber / sortCode | text | GBP only |
iban / bic | text | EUR only |
bridgeLiquidationAddress | text | Nullable — set on first withdraw, reused thereafter |
transactions
Ledger of every USDC movement — deposits (inbound), and withdrawals + PIX (outbound).
| Column | Type | Notes |
|---|---|---|
userId | uuid | FK → users |
type | enum | deposit | withdrawal |
amountUsdc | numeric | 6-decimal USDC amount |
toAddress | text | Nullable — the liquidation address for outbound |
txHash | text | Unique nullable — Base tx hash |
bridgeTransferId | text | Nullable |
liquidationAddress | text | Nullable |
fiatAmount | numeric | Nullable |
fiatCurrency | enum | gbp | eur | brl — nullable |
status | enum | pending | completed | confirmed | failed |
failureReason | text | Nullable |
Transaction state machine
pix_payments
PIX-specific detail row, linked 1-to-1 with a transaction.
| Column | Type | Notes |
|---|---|---|
userId | uuid | FK → users |
transactionId | uuid | Nullable FK → transactions |
amountBrl | numeric | Merchant-facing amount |
amountUsdc | numeric | USDC debited from user's wallet |
exchangeRate | numeric | USD/BRL rate at creation time |
pixKey | text | |
pixKeyType | enum | cpf | cnpj | email | phone | random |
recipientName | text | Nullable |
status | enum | pending | processing | completed | failed |
bridgePaymentId | text | Nullable |
pixEndToEndId | text | Nullable — set once PIX network confirms |
failureReason | text | Nullable |
webhook_events
Idempotency ledger for Bridge webhooks — one row per unique bridgeEventId.
| Column | Type | Notes |
|---|---|---|
bridgeEventId | text | Unique |
eventType | text | e.g. liquidation_address.drain_completed |
payload | jsonb | Full raw webhook body |
processedAt | timestamp | Nullable — set when handler successfully finishes |
Zod contract — where to look
Every API endpoint request/response has a Zod schema in packages/shared/src/. See API endpoints for the full list.