Requirements
- Node.js 20+
- pnpm
- PostgreSQL 16+ (Supabase works great)
sessions table, so PostgreSQL is the only datastore — no Redis needed.
Environment variables
All configuration is loaded from.env in the API package. Copy
apps/api/.env.example to apps/api/.env and fill it in:
Solana chains/RPC/program id are not env. They live in the chain registry (DB), seeded bypnpm db:seedand edited in the workspace admin (GET /v1/chains,POST /v1/admin/chains). RPC API keys, if any, must be public or proxied — the registry is served to clients as-is.
Fiat (DOKU Checkout + internal ledger)
Fiat is one namespace: DOKU Checkout is money-in only (no Sub-Account); every balance lives on the internal fiat ledger. See Fiat & payments.* DOKU endpoints fail at call time until these are set. The global fee policy
is versioned in the DB (fiat_fee_policies) and can be published by an admin
(POST /v1/fiat/admin/fee-policy); per-app fees are configured per app in the
workspace.
* Google endpoints return 503 until these are set. Dev and prod run the
identical OAuth code path — only the values differ per environment. Create a
client per environment at the
Google Cloud console:
register GOOGLE_CALLBACK_URL (e.g. http://localhost:3301/v1/auth/google/callback)
on the dev client and the prod callback URL on the prod client.
Local development with Docker
Deploy to Vercel + Supabase
-
Supabase — create a project, then run migrations against it (use the session pooler,
port
5432, for migrations): -
Vercel — API (
apps/api/vercel.jsonis already configured for serverless). Add a project rooted atapps/apiand set these env vars:DATABASE_URL(transaction pooler, port6543,?pgbouncer=true),JWT_ACCESS_SECRET,JWT_REFRESH_SECRET,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,GOOGLE_CALLBACK_URL,COOKIE_SECURE=true,COOKIE_SAMESITE,CLIENT_SUCCESS_URL,CORS_ORIGINS. -
Mintlify — docs (canonical; the board user deploys manually). The Mintlify
source lives in
apps/docs(docs.json+ pages +openapi.yaml). In Mintlify: connect theperidot-idrepo, set the docs path toapps/docs, and attach custom domainsdocs.peridot.id(prod) +sandbox.docs.peridot.id(sandbox) via CNAME at Hostinger. The legacy Fumadocs site (apps/docs) keeps serving the oldpid.peridotvault.com/docs*paths until cutover; at cutover it lands a redirect old-docs → the Mintlify host. - Attach your custom domains and point DNS at Vercel.
Production build
Legacy-database migration notes
Migrations are additive where possible, but the PID renames (identityId → pid,
PidAccount removal (1 identity = 1 wallet) rewrite identity-linked tables. For a fresh
database just run db:deploy + seed. For a database with existing rows from older
releases:
-
Email-duplicate audit — a non-null email is unique (one email = one PID).
Resolve collisions before migrating (the uniqueness index fails otherwise):
-
Deploy with
pnpm --filter @peridotvault/pid-api db:deploy(same flow as the Supabase steps above). -
No backfill. Existing users get no wallet rows — they are created only by
explicit user action (
POST /v1/account), so nothing is silently created for existing accounts.