This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
npm run install:all # Install all dependencies (backend + frontend)
npm run dev:backend # Start backend API on :3001 with auto-reload
npm run dev:frontend # Start frontend on :3000 with hot reload
cd backend && npm run test # Run backend tests with Vitest
cd frontend && npm run test # Run frontend tests with Vitest
cd backend && npm run build # TypeScript build (required before deployment)
cd frontend && npm run build # Build production bundleStellarStream models continuous payment streams where a sender allocates a total amount over a fixed duration and the recipient vests value over time. The app has three layers:
| Layer | Location | Port | Tech | Role |
|---|---|---|---|---|
| Frontend | frontend/ |
3000 | React + Vite + Tailwind | Dashboard for creating and monitoring streams |
| Backend API | backend/ |
3001 | Node.js + Express + SQLite | REST API + indexer + webhook worker |
| Smart Contract | contracts/ |
— | Rust + Soroban | On-chain stream logic (not yet integrated in MVP) |
- API Layer (
backend/src/index.ts): Express routes handle REST requests. Validation via Zod schemas inbackend/src/validation/schemas.ts. - Service Layer (
backend/src/services/):streamStore.ts: Core business logic—stream lifecycle, progress calculations, archivingeventHistory.ts: Persists stream events (created, claimed, canceled) instream_eventstableindexer.ts: Background worker polling Stellar for on-chain events (10s interval)webhookWorker.ts: Background worker delivering webhooks with retry logicreconciliationJob.ts: Reconciles local stream state with Stellar chaincache.ts: In-memory LRU cache for stream lookups
- Database Layer (
backend/src/services/db.ts): SQLite with WAL mode, usesbetter-sqlite3. - Frontend polls
/api/streamsevery 5 seconds (target: WebSocket push updates).
Key tables:
streams: Active stream records (sender, recipient, asset, amount, duration, start_at, archived_at, etc.)stream_archive: Historical archive of completed/canceled streamsstream_events: Immutable event log (created, claimed, canceled, etc.)webhook_deliveries: Pending webhook deliveries with retry metadatawebhook_dead_letters: Failed webhook deliveries for manual inspectionindexer_cursor: Tracks last ledger sequence polled from Stellar
Important fields:
archived_at: NULL for active streams; timestamp when archived (streams older than 30 days are eligible)paused_at/paused_duration: Handle stream pause logicmetadata: JSON field for extensibility
Stream Status: Computed real-time from timestamps—scheduled, active, paused, completed, canceled.
Progress Calculation (calculateProgress() in streamStore.ts):
elapsedSeconds = now - startAt - pausedDurationvestedAmount = min(totalAmount, (elapsedSeconds / durationSeconds) * totalAmount)percentComplete = vestedAmount / totalAmount
Archiving: archiveOldStreams() moves streams with completed_at || canceled_at older than 30 days to stream_archive (configurable cron).
Webhooks: On each stream event, triggerWebhook() enqueues a delivery. The webhook worker retries with exponential backoff (max 3 attempts).
src/App.tsx: Root component, defines main routes and layoutsrc/hooks/useWebSocket.ts: Manages WebSocket connections with exponential backoff reconnect (currently not used for stream updates)src/services/: API client wrappers (stream CRUD, event history)src/components/: Reusable UI componentssrc/types/: TypeScript interfaces- Current polling model: Frontend calls
GET /api/streams?q=...&status=...every 5 seconds
Backend:
- Test files:
*.test.tsand*.integration.test.tsinbackend/src/ - Run all:
cd backend && npm run test - Run single test:
cd backend && npx vitest run src/services/streamStore.test.ts - CI runs:
npx vitest run --coverage(also builds TypeScript)
Frontend:
- Test files:
*.test.ts/*.test.tsxinfrontend/src/ - Run all:
cd frontend && npm run test - E2E tests:
npm run test:e2e(Playwright)
Workflows (.github/workflows/):
ci.yml: Frontend + backend build checks on every PR/pushbackend-ci.yml: Backend tests + coverage + build (on backend changes)frontend-ci.yml: Frontend linting + build (on frontend changes)contract-ci.yml: Rust contract testsplaywright-e2e.yml: E2E test suitecodeql.yml: Static security scangitleaks.yml: Secret scanning
The Soroban contract uses multi-level size optimization:
Cargo Release Profile (contracts/Cargo.toml):
opt-level = "z"- Optimize for minimal sizelto = true- Link-time optimization across all dependenciesstrip = "symbols"- Remove debug symbolscodegen-units = 1- Maximum optimization opportunitiespanic = "abort"- Minimal panic handling
wasm-opt Post-Build (contracts/build.rs):
- Automatically runs on
soroban contract build(release mode) - Uses wasm-opt
-O4(aggressive size reduction, ~10-15% additional) - Requires:
npm install -g wasm-optorbrew install binaryen
Build Commands:
cd contracts
make build # Standard build with Cargo
make build-optimized # Build + explicit wasm-opt -O4
make profile-size # Show current binary size
make test # Run contract testsSize Tracking: See SIZE_PROFILE.md for baseline metrics and optimization history.
Parameter Binding in SQL: Use @name syntax for better-sqlite3 prepared statements (not ?):
db.prepare("SELECT * FROM streams WHERE sender = @sender").run({ sender: "G..." });Error Handling: Use sendApiError(req, res, statusCode, message, { code: "ERROR_CODE" }) for consistent error responses.
Rate Limiting: Applied via Express middleware (rateLimit). Configurable per-endpoint via env vars: READ_RATE_LIMIT, MUTATION_RATE_LIMIT, AUTH_CHALLENGE_RATE_LIMIT, CLAIMABLE_RATE_LIMIT.
Validation: Zod schemas in backend/src/validation/schemas.ts. Parse, transform, and refine before passing to services.
Caching: Use getCache() to access the in-memory LRU cache. Call resetStatsCache() after mutations affecting stats.
Migrations: New schema changes go in the migrate() function in db.ts. Use incremental addColumnIfMissing() pattern for backwards compatibility.
Backend env vars (see .env or GitHub Actions):
PORT: API port (default: 3001)DB_PATH: SQLite file path (default:data/streams.db)ALLOWED_ASSETS: CSV list of allowed assets (default:USDC,XLM)ARCHIVE_CRON_INTERVAL_MS: Cron interval for archiving (default: daily)ALLOWED_ORIGINS: CORS allowed origins (CSV)READ_RATE_LIMIT,MUTATION_RATE_LIMIT: Per-minute limits (defaults: 120, 10)STELLAR_NETWORK:testnetorpublic(default:testnet)- Soroban contract address, webhook signing key, etc.
Frontend env vars:
VITE_API_BASE_URL: Backend API URL (default: http://localhost:3001)VITE_STELLAR_NETWORK:testnetorpublic
| File | Purpose |
|---|---|
backend/src/index.ts |
Express app, all route handlers |
backend/src/services/streamStore.ts |
Stream CRUD, progress, archive logic |
backend/src/services/db.ts |
SQLite init and schema migrations |
backend/src/validation/schemas.ts |
Zod request/response validation |
backend/src/swagger.ts |
OpenAPI 3.0 spec (auto-generated from code) |
frontend/src/App.tsx |
React root, routing |
frontend/src/hooks/useWebSocket.ts |
WebSocket client hook |
MAINTAINER_GUIDE.md |
Issue triage, PR review, release checklists |
SQLite WAL mode is already enabled in db.ts (line 24). If issues #360 mentions additional pragmas, they should be added to the DB init:
PRAGMA synchronous=NORMAL: Balance durability and speedPRAGMA busy_timeout=5000: Prevent SQLITE_BUSY on concurrent writesPRAGMA cache_size=-64000: 64MB page cache for read perf
- Rate limiting protects mutation endpoints from abuse
- Helmet sets HSTS, CSP, and other HTTP headers
- CORS is configurable via
ALLOWED_ORIGINSenv var - Auth: JWT-based challenge-response (optional, per route via
authMiddleware) - Webhook signing: HMAC-SHA256 in
webhookSignature.ts - SQL injection: Prevented by parameterized queries (
@namebinding)