Skip to content

[HARD][FINANCE] Implement an immutable double-entry ledger for all balance movements #81

Description

@Obiajulu-gif

Overview

Introduce an immutable double-entry accounting ledger as the source of truth for ChainMove wallet, investment, repayment, payout, fee, and adjustment balances.

Problem

The application currently updates denormalized fields such as User.availableBalance and writes a general transaction record. A partial failure, manual update, or future code path can cause the cached balance and transaction history to disagree. A finance platform needs balanced, auditable entries for every movement of value.

Scope

  • Define ledger accounts for investor wallets, driver balances, pool escrow, platform clearing, revenue/fees, repayments receivable, payouts payable, and adjustment accounts.
  • Add immutable journal and journal-entry models.
  • Require every journal to contain balanced debit and credit totals.
  • Create domain posting helpers for wallet funding, wallet debit, pool investment, down payment, repayment, refund, payout, fee, and administrative adjustment.
  • Make journal creation idempotent using a domain event/reference key.
  • Treat existing balance fields as cached projections, not the source of truth.
  • Add a projection/rebuild service that can recompute balances from the ledger.
  • Backfill legacy Transaction records into journals through a dry-run migration.

Security and integrity requirements

  • Posted journals cannot be edited or deleted; corrections use reversing entries.
  • Administrative adjustments require an actor, reason, and audit event.
  • Prevent a journal from posting if total debits and credits differ.
  • Keep currency explicit on accounts and entries.
  • Enforce transaction/session boundaries for journal and projection updates.

Suggested structure

  • models/LedgerAccount.ts
  • models/LedgerJournal.ts
  • models/LedgerEntry.ts
  • lib/ledger/posting.service.ts
  • lib/ledger/projection.service.ts
  • scripts/migrations/backfill-ledger.ts

Test requirements

  • balanced and unbalanced journal validation
  • idempotent reposting of the same payment reference
  • reversal entries
  • concurrent wallet credits/debits
  • projection rebuild from journal history
  • failure rollback inside MongoDB transactions
  • backfill dry run and resume behavior
  • invariant test proving system-wide debits equal credits per currency

Acceptance criteria

  • Every balance-changing workflow posts a balanced journal.
  • Ledger entries are immutable after posting.
  • Cached balances can be rebuilt and verified against the ledger.
  • Duplicate provider/domain references cannot create duplicate value.
  • Legacy transactions can be backfilled with a documented migration.
  • Reconciliation command reports zero unexplained imbalance on seeded data.
  • npm run lint, npm run typecheck, npm run test, and npm run build pass.

Evidence required

  • Sample balanced journals for at least five domain flows
  • Rebuild/reconciliation command output
  • Tests demonstrating rollback, reversal, and idempotency

Difficulty

Hard

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions