|
| 1 | +# CLAUDE.md - PolyPay App |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +PolyPay is a privacy-preserving payroll platform built on Horizen blockchain. It enables organizations, DAOs, and global teams to run payroll privately using zero-knowledge proofs (Noir circuits). Key features: private payments, private multisig approvals, escrow/milestone-based transfers, real-time notifications via WebSocket, and JWT authentication. |
| 6 | + |
| 7 | +## Tech Stack |
| 8 | + |
| 9 | +**Monorepo** (Yarn Workspaces, Node.js >= 20.18.3) |
| 10 | + |
| 11 | +| Layer | Stack | |
| 12 | +|-------|-------| |
| 13 | +| Frontend | Next.js 15, React 19, TypeScript 5.8, Tailwind CSS 4 + DaisyUI 5 | |
| 14 | +| State | Zustand 5 (persist middleware), TanStack React Query 5 | |
| 15 | +| Forms | React Hook Form 7 + Zod 3 | |
| 16 | +| Web3 | wagmi 2, viem 2, RainbowKit 2, Uniswap SDK | |
| 17 | +| ZK | @noir-lang/noir_js, @aztec/bb.js | |
| 18 | +| UI | Radix UI + shadcn/ui pattern, CVA for variants, Lucide React icons | |
| 19 | +| Notifications | Sonner (primary), react-hot-toast (secondary) | |
| 20 | +| Real-time | socket.io-client | |
| 21 | +| Backend | NestJS 11, TypeScript 5.7, Prisma 7 (PostgreSQL), JWT auth | |
| 22 | +| Smart Contracts | Hardhat 2, Solidity, OpenZeppelin 5, Poseidon circuits | |
| 23 | +| Shared | @polypay/shared - DTOs with class-validator/class-transformer | |
| 24 | + |
| 25 | +## Folder Structure |
| 26 | + |
| 27 | +``` |
| 28 | +/ |
| 29 | +├── docker/ # Docker Compose, Dockerfiles, .env.example |
| 30 | +├── docs/ # Gitbook documentation |
| 31 | +├── packages/ |
| 32 | +│ ├── nextjs/ # Frontend (Next.js App Router) |
| 33 | +│ │ ├── app/ # Pages (dashboard, transfer, batch, contact-book, quest, leaderboard) |
| 34 | +│ │ ├── components/ |
| 35 | +│ │ │ ├── ui/ # Base UI components (shadcn pattern: button, input, dialog...) |
| 36 | +│ │ │ ├── form/ # Form components (Form, FormField, FormInput, FormTextarea) |
| 37 | +│ │ │ ├── Common/ # Shared components (Sidebar, DisclaimerChecker) |
| 38 | +│ │ │ ├── Dashboard/ # Dashboard feature components |
| 39 | +│ │ │ ├── Transfer/ # Transfer flow components |
| 40 | +│ │ │ ├── Batch/ # Batch operation components |
| 41 | +│ │ │ ├── NewAccount/ # Account creation components |
| 42 | +│ │ │ ├── modals/ # Modal dialogs (~22 modals, lazy-loaded via ModalRegistry) |
| 43 | +│ │ │ ├── skeletons/ # Loading skeleton components |
| 44 | +│ │ │ └── icons/ # Custom icon components |
| 45 | +│ │ ├── hooks/ |
| 46 | +│ │ │ ├── api/ # React Query hooks (useTransaction, useAccount, useNotifications...) |
| 47 | +│ │ │ ├── app/ # App logic hooks (useAuth, useGenerateProof, useSocketEvent...) |
| 48 | +│ │ │ ├── form/ # Form hooks (useZodForm) |
| 49 | +│ │ │ └── scaffold-eth/ # Scaffold-eth hooks |
| 50 | +│ │ ├── services/ |
| 51 | +│ │ │ ├── api/ # Axios API services (apiClient, authApi, transactionApi...) |
| 52 | +│ │ │ ├── store/ # Zustand stores (useIdentityStore, useAccountStore...) |
| 53 | +│ │ │ ├── web3/ # Web3 utilities |
| 54 | +│ │ │ ├── socket/ # Socket.io client |
| 55 | +│ │ │ └── queryClient.ts # React Query config |
| 56 | +│ │ ├── utils/ # Utilities (formatError, errorHandler, signer, network...) |
| 57 | +│ │ ├── lib/form/ # Form schemas (Zod) and validation helpers |
| 58 | +│ │ ├── types/ # TypeScript types (modal, form, abitype) |
| 59 | +│ │ ├── constants/ # Constants (API_BASE_URL, timing) |
| 60 | +│ │ ├── configs/ # Route config, scaffold config |
| 61 | +│ │ └── contracts/ # Contract ABIs |
| 62 | +│ ├── backend/ # NestJS Backend |
| 63 | +│ │ └── src/ |
| 64 | +│ │ ├── auth/ # JWT authentication module |
| 65 | +│ │ ├── account/ # Multi-sig account management |
| 66 | +│ │ ├── transaction/ # Transaction CRUD & voting |
| 67 | +│ │ ├── user/ # User management with ZK commitments |
| 68 | +│ │ ├── notification/ # Real-time notifications (WebSocket) |
| 69 | +│ │ ├── quest/ # Quest/gamification system |
| 70 | +│ │ ├── admin/ # Admin analytics |
| 71 | +│ │ ├── zkverify/ # ZK proof verification (Horizen) |
| 72 | +│ │ ├── common/ # Shared utilities & middleware |
| 73 | +│ │ ├── database/ # Prisma module |
| 74 | +│ │ └── ... # ~24 feature modules total |
| 75 | +│ ├── shared/ # Shared DTOs and types (@polypay/shared) |
| 76 | +│ └── hardhat/ # Smart contracts & deployment |
| 77 | +``` |
| 78 | + |
| 79 | +## Validation Commands |
| 80 | + |
| 81 | +```bash |
| 82 | +# Root level |
| 83 | +yarn lint # ESLint (frontend + hardhat) |
| 84 | +yarn next:check-types # TypeScript type checking (frontend) |
| 85 | +yarn next:build # Production build (frontend) |
| 86 | +yarn build # Build all packages (shared → backend → frontend) |
| 87 | +yarn test # Run hardhat tests |
| 88 | +yarn format # Prettier format all packages |
| 89 | + |
| 90 | +# Frontend (packages/nextjs) |
| 91 | +yarn dev # Dev server (port 3000) |
| 92 | +yarn build # Next.js production build |
| 93 | +yarn lint # ESLint |
| 94 | +yarn check-types # TypeScript check |
| 95 | +yarn format # Prettier |
| 96 | + |
| 97 | +# Backend (packages/backend) |
| 98 | +yarn start:dev # Dev server with watch (port 4000) |
| 99 | +yarn lint # ESLint |
| 100 | +yarn format # Prettier |
| 101 | +yarn test # Jest unit tests |
| 102 | +yarn test:cov # Jest with coverage |
| 103 | +yarn test:e2e # End-to-end tests |
| 104 | +yarn test:e2e:staging # Staging E2E tests |
| 105 | + |
| 106 | +# Smart Contracts (packages/hardhat) |
| 107 | +yarn chain # Local Hardhat node |
| 108 | +yarn compile # Compile contracts |
| 109 | +yarn deploy # Deploy contracts |
| 110 | +yarn test # Hardhat tests |
| 111 | +``` |
| 112 | + |
| 113 | +## Code Style & Conventions |
| 114 | + |
| 115 | +### Naming |
| 116 | +- **Components**: PascalCase files and exports (`FormField.tsx`, `NotificationPanel.tsx`) |
| 117 | +- **Hooks**: camelCase with `use` prefix (`useZodForm.ts`, `useTransaction.ts`) |
| 118 | +- **Utils/Services**: camelCase (`apiClient.ts`, `formatError.ts`) |
| 119 | +- **Types/Interfaces**: PascalCase (`ModalProps`, `IdentityState`) |
| 120 | +- **Stores**: camelCase with `use` prefix (`useIdentityStore.ts`, `useAccountStore.ts`) |
| 121 | +- **API services**: camelCase with `Api` suffix (`transactionApi`, `authApi`) |
| 122 | +- **Constants**: UPPER_SNAKE_CASE (`API_BASE_URL`, `DEFAULT_PAGE_SIZE`) |
| 123 | + |
| 124 | +### Import Order |
| 125 | +1. React / Next.js |
| 126 | +2. Third-party libraries |
| 127 | +3. @heroicons |
| 128 | +4. @polypay/shared |
| 129 | +5. Local imports (`~~/...`) |
| 130 | + |
| 131 | +### Formatting (Prettier) |
| 132 | +- Print width: 120 |
| 133 | +- Tab width: 2 spaces |
| 134 | +- Arrow parens: avoid |
| 135 | +- Trailing comma: all |
| 136 | +- Path alias: `~~/*` → project root, `@polypay/shared` → shared package |
| 137 | + |
| 138 | +### Component Pattern |
| 139 | +- "use client" directive for client-side components |
| 140 | +- Functional components with hooks |
| 141 | +- Named exports for UI components, default exports for pages |
| 142 | +- Props interfaces defined inline in component files |
| 143 | + |
| 144 | +## Common Patterns |
| 145 | + |
| 146 | +### Design Tokens & Colors |
| 147 | +- **Source of truth**: `packages/nextjs/styles/tokens.ts` — all color definitions live here |
| 148 | +- **Sync script**: `packages/nextjs/styles/sync-colors.ts` — generates CSS variables into `globals.css` and Tailwind color config into `tailwind.config.ts` |
| 149 | +- **Command**: `yarn sync-colors` (from `packages/nextjs`) |
| 150 | +- **Flow**: Edit `tokens.ts` → run `yarn sync-colors` → CSS vars + Tailwind config auto-updated |
| 151 | +- **Never edit colors directly** in `globals.css` or `tailwind.config.ts` — they will be overwritten by the sync script |
| 152 | + |
| 153 | +### State Management (Zustand) |
| 154 | +```typescript |
| 155 | +// services/store/useIdentityStore.ts |
| 156 | +// All stores use create() with persist middleware for localStorage |
| 157 | +const useIdentityStore = create<IdentityState>()( |
| 158 | + persist( |
| 159 | + (set) => ({ |
| 160 | + accessToken: null, |
| 161 | + // ...state and actions |
| 162 | + setTokens: (access, refresh) => set({ accessToken: access, refreshToken: refresh }), |
| 163 | + logout: () => set({ accessToken: null, isAuthenticated: false }), |
| 164 | + }), |
| 165 | + { name: "identity-storage" } |
| 166 | + ) |
| 167 | +); |
| 168 | +// Use getState() for sync access outside React (e.g., in Axios interceptors) |
| 169 | +``` |
| 170 | + |
| 171 | +### API Services |
| 172 | +```typescript |
| 173 | +// services/api/transactionApi.ts |
| 174 | +// Object export pattern with typed async methods |
| 175 | +export const transactionApi = { |
| 176 | + getAll: async (params) => { const { data } = await apiClient.get(...); return data; }, |
| 177 | + create: async (dto) => { const { data } = await apiClient.post(...); return data; }, |
| 178 | +}; |
| 179 | +``` |
| 180 | + |
| 181 | +### React Query Hooks |
| 182 | +```typescript |
| 183 | +// hooks/api/useTransaction.ts |
| 184 | +// Query key factory pattern for cache invalidation |
| 185 | +const transactionKeys = { all: ["transactions"], list: (filters) => [...] }; |
| 186 | +// useInfiniteQuery for pagination, useMutation with onSuccess invalidation |
| 187 | +``` |
| 188 | + |
| 189 | +### Forms (React Hook Form + Zod) |
| 190 | +```typescript |
| 191 | +// useZodForm hook → Form component → FormField with Controller |
| 192 | +const form = useZodForm({ schema: contactSchema }); |
| 193 | +// Zod schemas in lib/form/schemas.ts, reusable validators in lib/form/validation.ts |
| 194 | +``` |
| 195 | + |
| 196 | +### Error Handling |
| 197 | +```typescript |
| 198 | +// utils/formatError.ts - pattern matching for friendly error messages |
| 199 | +// utils/errorHandler.ts - ErrorCode enum, parseError(), handleError() |
| 200 | +// Axios interceptor handles 401 auto-refresh with token rotation |
| 201 | +``` |
| 202 | + |
| 203 | +### Notifications |
| 204 | +```typescript |
| 205 | +import { notification } from "~~/utils/scaffold-eth"; |
| 206 | +notification.success("Transaction submitted!"); |
| 207 | +notification.error(formatErrorMessage(error)); |
| 208 | +``` |
| 209 | + |
| 210 | +### Real-time (Socket.io) |
| 211 | +```typescript |
| 212 | +// hooks/app/useSocketEvent.ts - wrapper with auto-cleanup on unmount |
| 213 | +useSocketEvent("transaction:updated", (data) => { /* handle */ }); |
| 214 | +``` |
| 215 | + |
| 216 | +### Modals |
| 217 | +```typescript |
| 218 | +// Lazy-loaded via ModalRegistry with dynamic imports to prevent SSR issues |
| 219 | +// components/modals/ - ~22 modal components |
| 220 | +``` |
| 221 | + |
| 222 | +### Authenticated Queries |
| 223 | +```typescript |
| 224 | +// hooks/api/useAuthenticatedQuery.ts |
| 225 | +// Wrapper that auto-disables queries when user is not authenticated |
| 226 | +``` |
| 227 | + |
| 228 | +### Routes |
| 229 | +```typescript |
| 230 | +// configs/routes.config.ts - centralized route definitions |
| 231 | +// hooks/app/useAppRouter.ts - type-safe navigation (goToDashboard, goToTransfer, etc.) |
| 232 | +``` |
| 233 | + |
| 234 | +## Environment Setup |
| 235 | + |
| 236 | +### Prerequisites |
| 237 | +- Node.js >= 20.18.3 |
| 238 | +- Yarn (workspaces) |
| 239 | +- Docker & Docker Compose (for PostgreSQL) |
| 240 | + |
| 241 | +## Do's and Don'ts |
| 242 | + |
| 243 | +### Do's |
| 244 | +- Use `@polypay/shared` DTOs for API contracts between frontend and backend |
| 245 | +- Use `useZodForm` hook for all forms with Zod schemas |
| 246 | +- Use `notification.success/error/info` for user feedback |
| 247 | +- Use `formatErrorMessage()` for user-friendly error messages |
| 248 | +- Use `useAuthenticatedQuery` wrapper for queries requiring auth |
| 249 | +- Use query key factory pattern for React Query cache management |
| 250 | +- Use `useSocketEvent` hook for real-time subscriptions (auto-cleanup) |
| 251 | +- Use `~~/*` path alias for local imports |
| 252 | +- Use Zustand `persist` middleware for state that needs to survive refresh |
| 253 | +- Use `apiClient` (configured Axios instance) for all API calls |
| 254 | +- Follow feature-based component organization |
| 255 | +- Use Radix UI + CVA for new UI components (shadcn pattern) |
| 256 | + |
| 257 | +### Don'ts |
| 258 | +- Don't import from `@polypay/shared` directly in smart contracts |
| 259 | +- Don't make API calls without going through `apiClient` (it handles auth tokens) |
| 260 | +- Don't create new notification systems - use existing `notification` utility or Sonner |
| 261 | +- Don't hardcode API URLs - use `API_BASE_URL` from constants |
| 262 | +- Don't skip Zod validation for forms - always define schemas |
| 263 | +- Don't store auth tokens manually - use `useIdentityStore` |
| 264 | +- Don't create new Zustand stores without `persist` middleware unless state is truly ephemeral |
| 265 | +- Don't use `useQuery` directly for authenticated endpoints - use `useAuthenticatedQuery` |
| 266 | +- Don't put business logic in components - extract to custom hooks in `hooks/app/` |
| 267 | +- Don't use inline styles - use Tailwind CSS classes |
| 268 | +- Don't commit `.env` files or expose secrets |
0 commit comments