Skip to content

Latest commit

 

History

History
189 lines (146 loc) · 6.94 KB

File metadata and controls

189 lines (146 loc) · 6.94 KB

Project Specification: FX Trading Platform (Backend)

1. Executive Summary

Build a robust, scalable backend for a multi-currency FX trading application using NestJS and PostgreSQL. Core Philosophy: Financial integrity is paramount. We favor Accuracy and Auditability over raw speed. The system uses a Double-Entry Ledger Lite model (Append-Only Logs) rather than mutable balances to prevent race conditions and ensure perfect transaction history.

2. Technical Stack & Standards

  • Framework: NestJS (Modular Monolith architecture).
  • Database: PostgreSQL (Required for JSONB, Locking, and Decimal precision).
  • ORM: TypeORM.
  • Language: TypeScript (Strict Mode).
  • Environment: Dockerized (Postgres + App).
  • Math: decimal.js or big.js. STRICT RULE: NEVER USE NATIVE FLOATING POINT MATH.
  • Validation: class-validator and class-transformer (Whitelist enabled).
  • Documentation: Swagger (@nestjs/swagger).

3. Database Schema (The "Ledger" Model)

The database design is the most critical component. The AI must implement this schema exactly to support locking and auditing.

A. Users & Auth

  • Table: users
  • id (UUID, PK)
  • email (VARCHAR, Unique, Index)
  • password_hash (VARCHAR)
  • is_email_verified (BOOLEAN, Default: false)
  • kyc_status (ENUM: 'PENDING', 'VERIFIED', 'REJECTED') — Trading is blocked if not VERIFIED.
  • created_at (TIMESTAMP)

B. The Wallet (Locking Container)

This table exists primarily to provide a row to LOCK (SELECT ... FOR UPDATE). It does not store the balance.

  • Table: wallets
  • id (UUID, PK)
  • user_id (FK -> users)
  • currency (VARCHAR(3)) — E.g., 'NGN', 'USD'
  • created_at (TIMESTAMP)
  • Constraint: Unique(user_id, currency)

C. Ledger Entries (The Truth)

This is an append-only table. Balance is derived from the latest entry's snapshot or a summation of history.

  • Table: ledger_entries
  • id (UUID, PK)
  • wallet_id (FK -> wallets)
  • amount (DECIMAL(20, 8)) — Signed value (+Credit, -Debit)
  • balance_before (DECIMAL(20, 8)) — Snapshot for audit
  • balance_after (DECIMAL(20, 8)) — Snapshot for fast read
  • transaction_type (ENUM: 'DEPOSIT', 'WITHDRAWAL', 'TRADE_DEBIT', 'TRADE_CREDIT')
  • transaction_reference (UUID) — Links this entry to the transactions table
  • created_at (TIMESTAMP)

D. Transactions (The State Machine)

Tracks the intent and status of a user action.

  • Table: transactions
  • id (UUID, PK)
  • user_id (FK -> users)
  • type (ENUM: 'FUNDING', 'CONVERSION')
  • status (ENUM: 'PENDING', 'COMPLETED', 'FAILED')
  • source_currency (VARCHAR)
  • target_currency (VARCHAR)
  • source_amount (DECIMAL)
  • target_amount (DECIMAL)
  • rate_used (DECIMAL)
  • quote_id (UUID, Nullable) — Links to the specific FX quote used
  • created_at (TIMESTAMP)

E. FX Quotes (Slippage Protection)

  • Table: fx_quotes
  • id (UUID, PK)
  • quote_id (UUID, Index) — Public ID sent to frontend
  • user_id (FK)
  • pair (VARCHAR) — 'NGN/USD'
  • rate (DECIMAL)
  • expires_at (TIMESTAMP) — Quotes are valid for 60 seconds only.

4. Module Architecture & Logic Flow

Module 1: Auth Module

  • Register: Creates User. Triggers mock Email Event.
  • Verify: Accepts OTP (Mock implementation). Updates is_email_verified.
  • Guard: KycGuard must protect all Wallet endpoints.

Module 2: Exchange Module (The Quote System)

  • Goal: Prevent rate slippage.
  • Flow:
  1. User requests GET /fx/quote?from=NGN&to=USD&amount=5000.
  2. System fetches live rate (mock or API).
  3. System calculates output.
  4. System saves Quote to DB with expires_at = NOW() + 60s.
  5. Returns quote_id and rate to user.

Module 3: Wallet Module (The Engine)

  • Feature: Fund Wallet (Mock)
  • Input: amount, currency.
  • Logic:
  1. Start DB Transaction.
  2. Upsert Wallet (Create if not exists).
  3. Lock Wallet Row.
  4. Get Last Ledger Entry (or 0).
  5. Create new Ledger Entry (+Amount).
  6. Create Transaction Record (COMPLETED).
  7. Commit.
  • Feature: Execute Trade (The Critical Path)
  • Input: quote_id.
  • Logic (Step-by-Step):
  1. Validation: Check if quote_id exists and is valid (not expired, not used).
  2. Start DB Transaction (Isolation: READ COMMITTED).
  3. Locking:
    • SELECT * FROM wallets WHERE user_id=X AND currency=Source FOR UPDATE.
    • SELECT * FROM wallets WHERE user_id=X AND currency=Target FOR UPDATE. (Handle Deadlock risk by sorting currency locks alphabetically or checking existence first).
  4. Balance Check: Fetch latest balance_after from ledger_entries for Source Wallet. If balance < amount, THROW InsufficientFundsException.
  5. Execution:
    • Debit Source: Insert Ledger Entry (-amount, type: TRADE_DEBIT).
    • Credit Target: Insert Ledger Entry (+amount * rate, type: TRADE_CREDIT).
    • Update Transaction: Set status to COMPLETED.
    • Invalidate Quote: Set quote status to USED.
  6. Commit.

5. Security & Reliability Requirements

  1. Idempotency: The POST /trade endpoint MUST accept an Idempotency-Key header.

    • Store key in Redis or a idempotency_logs table.
    • If key exists, return the previous result without re-executing.
  2. Decimal Precision:

    • Use a custom TypeORM ValueTransformer to convert string from DB to Decimal.js object in code.
    • API responses must return money as Strings (e.g., "100.50"), not Numbers.
  3. Config Validation: Use Joi to validate .env (DB_HOST, API_KEYS) on startup. Fail hard if missing.

  4. Exception Handling: Global Filter to catch HTTP exceptions and return uniform JSON: { statusCode, message, error, timestamp }.


6. Project Structure (Scaffold Instructions)

src/
├── common/
│   ├── decorators/     # @User(), @IdempotencyKey()
│   ├── filters/        # GlobalExceptionFilter
│   ├── guards/         # JwtGuard, KycGuard
│   ├── interceptors/   # ResponseTransformInterceptor
│   └── utils/          # decimal-math.util.ts
├── config/             # typeorm.config.ts, env.validation.ts
├── database/
│   ├── entities/       # User, Wallet, LedgerEntry, Transaction, Quote
│   └── migrations/     # Timestamped migrations
├── modules/
│   ├── auth/
│   ├── wallet/
│   ├── exchange/
│   └── health/         # Health checks
└── main.ts

7. Implementation Checklist for AI

  • Setup NestJS with TypeORM and Docker Compose (Postgres).
  • Create Entities with the exact relations defined above.
  • Implement DecimalTransformer for TypeORM.
  • Implement WalletService.executeTrade() using QueryRunner for manual transaction control.
  • Implement Pessimistic Locking (lock: { mode: 'pessimistic_write' }).
  • Add Unit Test for "Double Spend": Try to run two trades of the same funds concurrently; one MUST fail.

End of Specification