Part 8 · 2 chapters · ~20 min
The Client Half of the Ledger
Each guarantee the Core Banking module built (double-entry, derived balances, idempotency with a fingerprint, holds and tiers, the event log, journal-first posting, reconciliation, the versioned cache) mapped to its client half, one transfer followed end to end through both halves, the six places trust is kept, and the course in one table.
20
The ledger's guarantees, on screen
eight mechanisms, eight client halves
- Double-entry → the receipt and the statement. Every movement is two entries that sum to zero; the receipt shows both sides in the user's words; the running balance is one side summed. The client renders entries and never invents a movement.
- Balance as derived state → "which balance" (part 4). Available, ledger, holds and pendings derived server-side from entries and holds; shown labelled, never computed; the CBA module's snapshot and cache are why it is fast; the version is why it never goes backwards.
- Idempotency keys with a fingerprint → one key per intent (part 3). The server stores the key with a payload fingerprint and refuses a different payload under it; the client mints at review, binds, re-mints on change. "Exactly-once is a lie": at-least-once with a key is the truth on both sides.
- Holds and tiers → pending states and limits (parts 2 and 3). A hold placed atomically at authorisation is pending out and a reduced available on screen; tiers are limits enforced server-side and shown as a path.
- The event log → the feed and live updates (part 4, the FSD course M8). The feed is a projection of the log; balance versions are sequence numbers; replay-from-last-seen is the reconnect. The dual-write problem the CBA module solved is why an event and a balance agree.
- Journal-first → "we are confirming." The intent is appended before posting, so a lost response is answered by key; the client's confirming state is the journal's user-facing face.
- Reconciliation → pending for days, honestly, and statement versions (part 6). The ledger against the rails is why a pending row can resolve days later with a true reason, and why an issued statement can be re-issued as a numbered version.
- The balance cache that cannot go stale → the apply-if-newer rule (part 4). The server's invalidation discipline is mirrored by the client's version check; together, no screen shows a balance older than one it already showed.
THE LEDGER'S GUARANTEES, ON SCREEN
each mechanism the Core Banking module built, and the screen element that is its client half
swipe the figure sideways, or tap expand for full screen
1/6
double-entry
Double-entry → the receipt and the statement. Every movement is two entries (a debit and a credit) that sum to zero; the receipt shows both sides in the user's terms (from your wallet, to ADEOLA FAITH; fee from your wallet, to the bank), and a statement's running balance is the sum of one side. The client renders entries; it never invents a movement the ledger does not have.
21
One transfer, end to end
both halves, every step
- Review: the client resolves the account name (a server lookup), takes the fee from the server, mints key K bound to {amount, destination, currency}, records what was shown, and authorises bound to the same payload (part 1). The server owns every number; the client owns the key and the record.
- Submit: POST with K and the signed payload. The server checks the fingerprint, validates against available, places a hold atomically (the CBA module part 2's lock or CAS), journals the intent, returns 201 with the reference and the new balances. The client replaces its optimistic row by K and takes the balances from the response (part 3).
- In flight: the server posts the entries (wallet to clearing, the fee entries), sends to the rail, emits "transfer.submitted" with a sequence; the client's subscription updates the sub-state and the balance version. A lost response is answered by the journal: "what happened to K" (part 3's confirming state).
- Settlement: the rail confirms; settlement entries release the hold and move clearing to the rail; "transfer.settled" is emitted; the row settles, the balance updates by version, the receipt becomes final. A rail failure runs reversal entries; the row fails with the rail's reason and the money's whereabouts; the balance shows the return.
- The receipt: a projection of the entries for K: both sides in the user's words, the fee, the reference, the key for support, the authorisation method, every timestamp. Reachable from the list forever; identical in the statement; versioned if a correction ever touches it (part 6).
- Where trust is kept: the key table (no duplicates), the hold (no overdraft), the journal (no lost intent), the entries (no invented money), the event sequence (no stale screen), the receipt (no dispute without evidence). The client shows the truth the server holds: fast, labelled, and never claiming more than it knows.
the course, in one table
| surface | the user's fear | the client's rule | the ledger mechanism behind it |
|---|---|---|---|
| Authentication (1) | Locked out of my money | Expiry keeps the form; the lockout screen reassures first and offers paths with honest times | Sessions revocable server-side; recovery with delay and notification |
| KYC (2) | Rejected by a machine; asked again | Resumable at any step; review states with estimates; rejection maps to an action | Tiers as limits; the journey state server-held |
| Deposits and withdrawals (3) | Sent twice; lost; failed silently | One key per intent; confirming before failing; optimistic reconciled by key | Idempotency with a fingerprint; journal-first; holds |
| Wallets and balances (4) | The number lied | Which balance, labelled; integers with an exponent; apply-if-newer | Balance as derived state; the versioned cache |
| Trading (5) | Filled at a price I did not see | A live confirmation that re-arms; age on every price; slippage explained at the fill | The order state machine; the event sequence |
| Portfolio and statements (6) | The documents disagree | Server totals; an as-of boundary; exports from the same query | Double-entry; the event log; reconciliation versions |
| Edge cases (7) | Something went wrong and I do not know what | Nine designed states; three sentences; no forbidden words; copy tested as code | The journal answers "what happened to K" |
the exercise, for the course
Take the riskiest action in a product you own and write its end-to-end the way this chapter did: both halves, every step, and the six places trust is kept. Any step where the client is the authority for a number, or where the server's guarantee has no client face, is where the next incident lives.
ONE TRANSFER, END TO END
what the client and the ledger each do at every step, and where the trust is kept
swipe the figure sideways, or tap expand for full screen
1/6
review
Review: the client resolves the account name (a server lookup), computes nothing about money (the server quotes the fee), mints key K bound to {amount, destination, currency}, records what was shown, and asks for authorisation bound to the same payload (part 1). Authority: the server for the fee and the name; the client for the key and the record of what was shown.