SafeSend is a secure, premium simulated wallet built using the PERN stack (PostgreSQL, Express + TypeScript, React + TypeScript + Tailwind) that implements a 60-Second Undo Shield and Nominee-Gated Approvals for transactions.
It protects users from accidental transfers, high-velocity fraud spikes, and concurrent double-spend race conditions using robust row-level database locking and a dual-ledger auditing design.
- ₹50,000 Seed Balance: Every new user starts with ₹50,000 seeded by the
SYSTEMuser transaction. - 60-Second Undo Shield: Transfers below the safety limit (default ₹10,000) are placed on hold. A circular visual countdown ticks on the screen. The sender can hit "Undo" to instantly cancel the transfer and refund the funds. If the timer expires, the worker captures the payment.
- Nominee-Gated Approvals:
- Transfers exceeding the user's custom safety limit require nominee approval.
- High-velocity checks flag a user if they perform 5+ transfers in 10 minutes or send > ₹5,000 to a new recipient, forcing nominee approval regardless of the safety limit.
- Real-time popups notify nominees via Socket.io. If they do not approve within 24 hours, the transfer automatically expires and is refunded.
- Idempotency Protection: All
/transferrequests are safeguarded using UUID idempotency keys. - Thread-Safe Row-Level Locking: Raw Postgres locking (
SELECT ... FOR UPDATE) protects user accounts against concurrent wallet double-spends. - Immutable Ledger Entries: Double-entry ledger audit trail tracks all actual settled debit and credit movements.
flowchart TD
subgraph Frontend [React Web Client]
UI[Vite App / Tailwind CSS]
SC[Socket.io-Client]
AC[Auth Context]
end
subgraph Backend [Express Server]
API[Express Controllers / Routing]
Auth[JWT + Bcrypt Middleware]
PP[PaymentProvider - SimulatedWallet]
Queues[BullMQ Queue Manager]
Sockets[Socket.io Server Hub]
end
subgraph Infrastructure [Docker compose]
DB[(PostgreSQL Database)]
Cache[(Redis Cache & Queue Store)]
end
UI <-->|HTTP API / JSON| API
SC <-->|WebSocket Events| Sockets
API -->|Raw SQL Transactions| DB
PP -->|Row Locking| DB
Queues <-->|Jobs / Tasks| Cache
API -->|Queue Jobs| Queues
- User: Holds wallet metadata, password hashes,
balanceCache(cached balance), andsafetyLimit. Relationships track configuration of the Nominee (nomineeandnomineeOf). - Transaction: Records transfer state (
PENDING,REQUIRES_APPROVAL,COMPLETED,CANCELLED,REJECTED,EXPIRED), idempotency keys, and scheduled settlement timestamps (settlesAt). - LedgerEntry: Immutable accounting records (
DEBIT/CREDIT) written during actual settlement captures.
SafeSend implements Row-Level Locking (SELECT ... FOR UPDATE) in Postgres transactions to guarantee that even under heavy concurrent attacks, the wallet balance can never be double-spent.
A custom concurrency script (scripts/load-test.ts) simulates 100 concurrent requests of ₹600 each (total ₹60,000 requested) on a single sender account with a ₹50,000 starting balance:
| Scenario | Row Locking | Successes | Failures | Final Balance | Ledger Audit | Status |
|---|---|---|---|---|---|---|
| Without Row-Level Locking | DISABLED | 6 | 94 | ₹48,200 | ₹50,000 | ✅ PROTECTED (Stale updates overwritten) |
| With Row-Level Locking | ENABLED | 10 | 90 | ₹44,000 | ₹50,000 | ✅ PROTECTED (Strict transaction order) |
Note
- Under DISABLED locking, concurrent database transactions overwrite each other's cache updates based on stale balance reads, leading to lost updates (only ₹1,800 total deducted instead of ₹3,600).
- Under ENABLED locking, Postgres queues transaction locks sequentially, assuring correct state transitions, preventing race conditions, and correctly rejecting transfers once the sender's balance falls below the requested ₹600.
- Docker & Docker Compose
- Node.js (v20+)
SafeSend runs Postgres on host port 5433 and Redis on host port 6380 to prevent conflicts with pre-existing local instances:
npm run db:upInstall dependencies for both folders from the project root:
npm run install-allGenerate the Prisma client and push the schema to PostgreSQL:
cd backend
npx prisma generate
npx prisma db pushStart the Express server backend and the React Vite dev server concurrently from the root directory:
npm run dev- Frontend will be accessible at:
http://localhost:5173 - Backend API will run on:
http://localhost:3000
SafeSend uses Jest + Supertest to run tests covering instant limits, 60s shields, nominee approval flow, and high velocity flags:
cd backend
npm run test:e2eRun the concurrency load test to observe row-level locking behavior:
cd backend
npm run test:concurrency