Skip to content

Repository files navigation

LedgerX

Event-driven digital wallet & immutable ledger backend — built as a hands-on system design project to learn the patterns used in real fintech infrastructure (Razorpay, Juspay, Stripe): atomic transactions, idempotency, event-driven architecture, and reconciliation.


Why this isn't a typical "wallet app"

Most toy wallet projects do this:

User clicks "Send Money" → UPDATE balance → Done

LedgerX does this instead:

Transfer Request
      ↓
Authentication
      ↓
Idempotency Check
      ↓
Row-Level Lock + DB Transaction
      ↓
Immutable Ledger Entries (never updated/deleted)
      ↓
Commit
      ↓
Publish Event (RabbitMQ)
      ↓
Webhook Dispatch (BullMQ, retries + backoff)
      ↓
Reconciliation Worker (catches its own inconsistencies)

Core principle: there is no Balance table. A wallet's balance is never stored — it's always SUM(credits) − SUM(debits), computed from an append-only ledger and cached with explicit invalidation. This is how real accounting/ledger systems work, and it eliminates an entire category of bugs where a cached balance and the actual transaction history silently drift apart.


Architecture

                        Client
                           │
                    REST API (Express)
                           │
                     Auth Middleware (JWT)
                           │
              ┌────────────┼────────────┐
              │            │            │
        Wallet Service  Transaction   Ledger Service
              │          Service           │
              │            │            │
              └────────────┼────────────┘
                           │
                    Idempotency Layer (Redis)
                           │
                    PostgreSQL (Prisma 7)
                     - Ledger (append-only)
                     - Transaction
                     - Wallet
                           │
                 Event Publisher (RabbitMQ, durable)
                           │
          ┌────────────────┼────────────────┐
          │                │                │
   Webhook Consumer     BullMQ Workers   Reconciliation
   (retries + backoff)  (background)     Worker (cron)

Tech Stack

Layer Choice Why
Language TypeScript Strict typing across financial logic
Framework Express.js Full architectural control
Database PostgreSQL ACID transactions + row-level locking
ORM Prisma 7 Driver adapters + raw SQL where needed
Auth JWT + bcrypt Secure authentication
Validation Zod Runtime validation
Cache / Locks Redis Idempotency + balance caching
Background Jobs BullMQ Retryable async processing
Messaging RabbitMQ Durable event publishing
Logging Pino Structured JSON logging
Testing Jest + Supertest Automated integration tests
Containers Docker Compose Local infrastructure

Features

Authentication & Wallets

  • User signup/login
  • JWT authentication
  • Secure password hashing with bcrypt
  • Wallet creation

Immutable Ledger

  • Atomic deposits
  • Atomic transfers
  • Append-only ledger
  • Balance computed from ledger entries

Concurrency Safety

  • SELECT ... FOR UPDATE
  • Prevents race conditions
  • Concurrent transfer protection

Idempotency

  • Redis-backed idempotency keys
  • Duplicate request prevention
  • Safe retries

Event-Driven Architecture

  • RabbitMQ event publishing
  • BullMQ webhook workers
  • Exponential retry

Reconciliation

  • Background consistency checks
  • Admin reconciliation endpoint

Rollback & Reversal

  • ACID rollback guarantees
  • Compensating transactions
  • Immutable history

Redis Cache

  • Cache-aside pattern
  • Explicit invalidation
  • Safe balance computation

Design Decisions

  • Money stored as integer values (paise/cents)
  • No floating point arithmetic
  • Environment-driven configuration
  • Zod validation for all configuration
  • Balance never stored directly
  • Generic authentication error messages for security

Project Structure

src/
  modules/
    auth/
    wallet/
    transaction/
    ledger/
    webhook/
    reconciliation/
    admin/
  common/
  config/

prisma/
  schema.prisma

Local Setup

cp .env.example .env

docker compose up -d

npm install

npx prisma migrate dev

npm run dev

npm test

Environment Variables

Variable Purpose
DATABASE_URL PostgreSQL connection
REDIS_URL Redis connection
RABBITMQ_URL RabbitMQ connection
JWT_SECRET JWT signing secret
JWT_EXPIRES_IN Token expiry
BCRYPT_SALT_ROUNDS Password hashing
IDEMPOTENCY_LOCK_TTL_SECONDS Lock lifetime
IDEMPOTENCY_RESPONSE_TTL_SECONDS Cached response lifetime
WEBHOOK_MAX_RETRIES Retry count
WEBHOOK_BACKOFF_DELAY_MS Retry delay
RECONCILIATION_GRACE_PERIOD_MS Reconciliation timing
RECONCILIATION_INTERVAL_MS Worker schedule
BALANCE_CACHE_TTL_SECONDS Cache TTL
RABBITMQ_EXCHANGE Exchange name

Testing

npm test

Tests include:

  • Authentication
  • Wallet ownership
  • Atomic deposits
  • Atomic transfers
  • Concurrent transfer race conditions
  • Idempotency
  • Rollback
  • Transaction reversal
  • Webhook retries
  • Reconciliation

Roadmap

  • Interactive Swagger/OpenAPI documentation
  • Production deployment
  • Docker image
  • Frontend dashboard

Background

Built to deeply understand ACID transactions, immutable ledger design, concurrency control, idempotency, event-driven architecture, webhook delivery, reconciliation, and distributed systems concepts commonly used in modern payment infrastructure.

About

Event-driven digital wallet backend showcasing real-world fintech system design patterns and distributed systems

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages