Fintech

Fintech Escrow & Event-Sourced Ledger Backend

Context

Two fintech backends engineered to the same regulatory standard. The first is an SEC Regulation CF investment crowdfunding platform for a US startup, covering offerings, escrow, and the ledger behind APIs consumed by a Next.js 16 front end. The second is an investment platform where holdings are issued and tracked as tokenized positions rather than plain database balances, covering campaigns, escrow, an event-sourced ledger, on-chain settlement, and the compliance surface around all of it — an engagement that has run across multiple milestones and is ongoing.

The Hard Part

Money movement leaves no room for drift between what happened and what is recorded, so balances are never stored — they are derived from an immutable event stream. Escrow moves only through a strict state machine with optimistic locking and idempotent operations, so a partial release, a retry, or a failed settlement can never leave the ledger and the holder register disagreeing.

What I Built

  • Event-sourced ledgers as the single source of financial truth — 31 event types on the Reg CF backend, 54 on the tokenized platform
  • 6-state escrow machines governing how funds move through an offering, with optimistic locking and idempotent operations
  • 28-model Prisma schema on NestJS covering the full Reg CF investment domain
  • 30 documented API endpoints consumed by a Next.js 16 front end
  • Securities tokenization: issuance planning, mint authorization, and on-chain settlement
  • Holder positions with lockup and freeze handling, reconciled against issued supply
  • Two-tier role-based access control across 15 roles with per-action permission checks
  • Test suites gating every change — 248 tests on the Reg CF backend, 1,820 cases on the tokenized platform

The Build

Two engagements held to one standard

This case study covers two separate fintech backends, and the figures throughout belong to one or the other rather than to a single system. The first is an SEC Regulation CF investment crowdfunding backend for a US startup: offerings, escrow and the ledger, a 28-model Prisma schema on NestJS, 30 documented endpoints, a ledger of 31 event types, and a 248-test suite. The second is an investment platform where holdings are issued as tokenized positions: campaigns, escrow, an event-sourced ledger spanning 54 event types, on-chain settlement, two-tier access control across 15 roles, and 1,820 test cases — an engagement still running across multiple milestones.

They are worth reading together because they answer the same question — how do you build a system whose account of itself cannot drift from what actually happened — and they reach for the same two primitives to answer it: an immutable event stream in place of stored balances, and an explicit escrow state machine in place of status inferred from numbers. What differs is how far each system has to carry that answer, and the second one carries it past the point where mistakes can still be corrected.

Why "financial-grade" means something specific here

Most applications tolerate a small gap between what happened and what the database says. A stale cache, a retried request that double-counts, a figure that drifts by one — these are bugs, and they are survivable. A platform moving investor money through an offering has no equivalent tolerance. When the system's record of a transaction and the transaction itself disagree, the disagreement is the incident, regardless of which one turns out to be right.

That is the distinction between software that handles money and software engineered to a regulatory standard. The first is judged on whether it works. The second is judged on whether it can demonstrate what it did, in what order, and why the current state follows from it — which is a different target, and one that has to be designed in from the first schema decision rather than added once the product exists.

Scope compounds it. The Reg CF backend covers the full investment lifecycle: offerings, escrow, and the ledger. Money changes hands, changes custody, and changes meaning at several points along that path, and every one of those transitions has to be both correct at the time and explainable long afterwards.

The tokenized platform raises the stakes of that same requirement, and the distinction it rests on is between holdings held as plain database balances and holdings issued as tokenized positions. A database balance belongs entirely to the system that stores it. If it is wrong, it can be corrected — carefully, with an audit trail, but corrected, because the system is the only place the number exists.

A tokenized position does not work that way. Once settlement has happened on-chain, that half of the record is outside the platform's control and cannot be edited by anyone operating it. The platform's own accounting and the settled reality now have to match, and only one of them can be changed after the fact.

That is what turns "money and ownership have to agree at every moment" from a principle into a hard requirement. In a purely internal system, disagreement between records is a bug to reconcile later. Here, a disagreement that has already settled is not reconcilable at all — the only remaining options are compensating action and explanation. Preventing the divergence is the entire job, because there is no cleanup path worth relying on.

The decision that shaped everything: no stored balances

The governing constraint was that Reg CF money movement leaves no room for drift between what happened and what is recorded. The design answer was to stop storing balances at all.

A stored balance is a cached answer. It reads quickly, it is convenient, and it can be wrong — a failed update, a race between two writes, a well-meaning correction applied straight to the database, and the number no longer reflects the events that produced it. The deeper problem is that once it is wrong, nothing indicates so, because the number carries no account of how it got there.

Deriving balances from an immutable event stream inverts the relationship. The events are the truth and the balance is a computation over them, so it cannot silently disagree with its own history — it has no independent existence from which to disagree. Reconstructing what a balance was at some past moment becomes a matter of replaying events up to that point, rather than hoping a snapshot happened to be taken.

The cost is real and worth stating. Every read becomes a computation. Event shapes become effectively permanent in a way ordinary table columns are not. Correcting a mistake means appending a compensating event rather than editing a row. Those are acceptable prices in a domain where an unexplained balance is the worst available outcome; in most domains they would not be, and treating event sourcing as a default rather than a response to this specific constraint is how it becomes overhead instead of protection.

Three ways the records can drift apart

On the tokenized platform the constraint names the failure modes precisely, and each is a different mechanism rather than three descriptions of one problem.

A partial release. Escrow moving some of what it holds but not all of it leaves a position that is neither fully released nor fully held. Without a defined state for that condition, the system has to infer what happened from balances — which is exactly the inference that goes wrong under pressure.

A retry. Any operation that can be attempted twice can be applied twice, and in a financial context the second application is not a duplicate record but duplicated money movement. Retries are not an error condition here; they are ordinary behaviour of any system with a network in it.

A failed settlement. Settlement is asynchronous and, once complete, irreversible. The gap between initiating it and knowing the outcome is a window in which the platform's belief about ownership and the actual state of ownership can differ, and no database transaction spans that gap.

The answers are stated in the record and each targets one mechanism. Optimistic locking handles concurrent operations reaching the same escrow, so two actions cannot both proceed on a stale view of its state. Idempotent operations make a repeat attempt produce the same outcome as the first rather than a second effect. And a six-state machine means a partial or in-flight condition is a state the system explicitly holds rather than something inferred from balances — the awkward middle is representable, which is what stops it being guessed at.

How the systems are put together

The Reg CF ledger carries 31 event types and is the single source of financial truth. The count matters less than what it implies: every distinct thing that can happen to money here has its own named, recorded shape. Nothing changes value through a generic update — each movement is an event somebody deliberately defined and can later point to.

Escrow runs on a six-state machine. Escrow is where funds sit between belonging to the investor and belonging to the offering, which makes it the point in the lifecycle where an invalid transition would be most expensive. Encoding it explicitly means illegal moves are not merely avoided by convention — they are unrepresentable, because no transition exists to make them. A state machine is also the rare piece of documentation that cannot drift, since the diagram and the executing code are the same artifact.

A 28-model Prisma schema on NestJS covers the investment domain. Twenty-eight models is substantial for one product, and it reflects a domain modelled at its real granularity rather than flattened for convenience — in a system whose job is explaining itself, collapsing two concepts into one table is how you lose the ability to answer a question later. NestJS earns its place for a reason beyond preference: its module and dependency-injection structure is what makes a suite of this size affordable, because units can be tested with their dependencies substituted instead of through the whole stack.

Thirty documented endpoints serve a Next.js 16 front end. Documented is the operative word. An API consumed by a separate application is a contract, and in a regulated context that contract needs to be written down rather than inferred from whatever the client happens to send today.

A 248-test suite gates every change. In an event-sourced system tests do more than catch regressions — they pin the meaning of each event type, so a later change that quietly alters what an event signifies fails loudly instead of retroactively changing how history is interpreted.

The tokenized platform's ledger spans 54 event types, the same principle carried across a wider domain. Fifty-four distinct types means every way value or ownership can change has its own named, recorded shape — nothing moves through a generic update, and every movement is something somebody defined deliberately and can point to afterwards.

Tokenization is sequenced as issuance planning, then mint authorization, then on-chain settlement, and the reason there are three stages rather than one is the irreversibility described above. Planning is where what will be issued is decided and can still change. Authorization is the deliberate gate — the point at which a human decision is recorded and after which the process proceeds. Settlement is the step that cannot be undone. Putting an explicit authorization boundary immediately before the irreversible action is the whole point of separating them; collapsing planning and settlement into one operation would mean the only check on an unrecoverable action is that nobody made a mistake.

Holder positions carry lockup and freeze handling, which is where the compliance surface meets the ledger. A position is not simply an amount; it can be restricted, and the restriction has to be enforced wherever the position is acted on rather than displayed as a status. Those positions are reconciled against issued supply, which is the check that closes the loop: the sum of what holders own has to agree with what was actually issued, and that reconciliation is what would catch a divergence the other mechanisms missed.

Access is two-tier across 15 roles with per-action permission checks. Two tiers rather than a flat list reflects that authority here has more than one dimension, and per-action checking rather than per-screen is what makes it an access model instead of a presentation choice — in a system where an action can move money irreversibly, a permission that only hides a button is not a control.

What the numbers actually say

On the Reg CF backend, twenty-eight models, thirty endpoints and thirty-one event types describe scope: this is a full domain, not a prototype with a payments integration bolted on. Two hundred and forty-eight tests describe the discipline applied to that scope, and the ratio is the interesting part — a suite that large relative to the surface area is what a codebase looks like when correctness is the requirement rather than an aspiration.

Zero production bugs belongs to that backend specifically, and it is the figure that only means anything alongside the other four. It is not a claim that the code is flawless. It is what the preceding decisions were for: deriving balances rather than storing them removes an entire class of drift, a state machine removes a class of invalid transitions, a domain modelled properly removes ambiguity about what a record means, and a suite of that size makes whatever remains expensive to introduce. The number is a consequence of the architecture rather than a separate achievement, which is also why it is the least portable thing on this page — it belongs to these decisions, not to the developer who made them.

On the tokenized platform the figure that deserves the most attention is 1,820 test cases, and the other three numbers explain why it is that large. A six-state machine has a defined set of legal transitions and a much larger set of illegal ones that must be rejected. Fifty-four event types each have semantics that have to hold. Fifteen roles multiplied across per-action permission checks produce a substantial matrix of what each may and may not do.

Those do not add together, they compound — and in a system where the failure modes are a partial release, a retry, and a failed settlement, the cases worth testing are precisely the ones that do not occur while using the software normally. A suite that size is what it costs to assert that the illegal transitions are actually rejected rather than merely unlikely.

That engagement is ongoing across multiple milestones, so there are no outcome figures for it here, and the counts that do appear were verified against the codebase rather than estimated. The client's own assessment names what the work was aimed at — financial-grade integrity, serializable transactions, idempotency, and audit-safe design patterns — which is a description of properties rather than results, and properties are the honest thing to claim while a build is still in progress.

Measured Results

1,820

test cases · tokenized

248

passing tests · Reg CF

54

ledger event types · tokenized

28

database models · Reg CF

30

documented endpoints · Reg CF

15

access roles · tokenized

6

escrow states · both

0

production bugs · Reg CF

Financial-grade integrity… serializable transactions, idempotency, and audit-safe design patterns.

Client review — fintech startup, engagement in progress

Tech Stack

NestJSPrismaPostgreSQLNext.js 16React 19
Fintech Escrow & Event-Sourced Ledger Backend — interface screenshot

More Case Studies

Have a similar project?

I would love to help you build something great. Let's discuss your requirements.