The web app for HoodPrivate: private-by-default banking on Robinhood Chain, for people and for the AI agents that spend on their behalf.
This repository holds the marketing site and the authenticated product that sits behind it. Full product documentation lives at docs.hoodprivate.com, so this README sticks to the app itself, how it is put together and how to run it locally.
- Next.js 16 with the App Router and React 19
- TypeScript throughout
- Tailwind CSS v4 for styling, with a small set of shadcn primitives
- Supabase for authentication, Postgres and row level security
You need Node 20 or newer and a Supabase project.
npm install
npm run devThe app runs on http://localhost:3000.
Before it will do anything useful, create a .env.local with these three values:
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Supabase project URL |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anon key, safe to expose to the browser |
NEXT_PUBLIC_DEPOSIT_ADDRESS |
Address shown on the wallet top up screen |
Database migrations live outside this folder in supabase/migrations. Apply those before signing in, otherwise the app will authenticate fine but every query will come back empty.
npm run devstarts the development servernpm run buildproduces a production buildnpm run startserves that buildnpm run lintruns ESLint
src/
app/
page.tsx landing page
roadmap/ public roadmap
token/ token page
status/ service status
login/ sign in
signup/ sign up
auth/callback/ Supabase email confirmation handler
app/ the authenticated product
components/ landing page sections and shared UI
lib/supabase/ browser and server clients, plus hand written DB types
middleware.ts session refresh and route guards
Everything under src/app/app sits behind auth. The sidebar groups it into banking (accounts, send payment, cards, wallet), agents (agent accounts, activity, analytics) and developer tooling (API keys, alerts), with settings pinned to the bottom.
Money never moves from the client. Transfers, top ups and policy changes all go through Postgres functions defined in the migrations. The app calls those functions and reads back the result. No code path in src writes a balance directly, which is deliberate: the database enforces the same rules the UI shows, so a bug in a form cannot become a bug in someone's balance.
Database types are hand written. src/lib/supabase/database.types.ts mirrors the schema by hand rather than being generated by the Supabase CLI. If you change a migration, change that file too. One catch that has caught us more than once: every Row, Insert, Update and Args shape in there has to be declared with type, never interface. The Supabase client constrains them against Record<string, unknown>, and TypeScript only accepts object literal type aliases for that check. Use an interface and every .from() call quietly resolves to never, with no error pointing at the cause.
Agent accounts are ordinary accounts with a policy attached. An agent gets its own account, its own handle and its own balance, plus a spend policy that caps what it can move per day and per transaction. Revoking an agent does not delete its history, it only stops the account from sending.
Every account carries one of three tiers, and the tier decides what a transfer reveals on chain:
- Public, amounts and counterparties are visible
- Confidential, amounts are encrypted, counterparties are not
- Shielded, both are hidden
Confidential is the default. The tier belongs to the account rather than to the individual transfer, so an agent cannot quietly downgrade its own privacy mid session.