Where you are. Ten lessons have rebuilt the bank-to-bank rails on your miniledger: clearing and settlement pulled apart as two separate acts, the gross rail that settles payment by payment in reserves and the liquidity bill it runs up, the batch rails that collect a day of payments and settle only the differences, the batch day where arrived is not final, and the instant rails that settle in seconds around the clock. What none of them explain is the rail you personally use most. This lesson rebuilds the card system - the machine from the course’s very first page - and this time you hold the tools to play every party in it.
The tap, this time from inside
Module 0’s first lesson followed a contactless tap for a coffee: a beep in about two hundred milliseconds, money a day and a half behind it, five parties, three ledgers changed, and one party - the network - holding not a penny of what it directed. Back then you were a passenger. You took the lesson’s word for the count, because a count was all you had.
You have more now. You own a two-tier ledger that refuses unbalanced postings, a submit and settle API with a finality moment you can point at, and a module’s worth of vocabulary about when clearing happens and when settlement does. So run the scene again with the roles recast: carla taps 100 for a coffee, her card issued by Harbour, the cafe banking at Quay, and a network - the exercise names it Lattice - carrying messages between them. The amounts are stylised, round on purpose, and every claim from that first page becomes an assert you write.
The idea in one paragraph
The card system’s own name for itself is the four-party model: cardholder and merchant at the edges, and behind each a bank - the issuer behind the card, the acquirer behind the till - with the card network in the middle, routing messages between the two banks and logging every message it routes. A tap crosses that shape in three phases, each with a different relationship to money. Authorisation, in about a second, is a question and a memo: the issuer answers yes and places a hold, and not one ledger line moves. Clearing, that night, is arithmetic: the network totals its log into value owed per issuer-acquirer pair, and still nothing moves. Settlement, the next day, is the only phase that touches money, and it is machinery you already own: the totals land as ordinary interbank payments in reserves at the central bank - lesson 7’s deferred-net pattern riding module 1’s postings. Cards are a deferred-net rail wearing a real-time costume, and the exercise ends by weighing the costume’s owner: the network’s ledger-line count is exactly zero.
Four parties, one router
Module 0 counted five parties behind the beep: you, your bank, the shop’s bank, the network and the central bank. The industry’s name for the same machine counts four, and the difference teaches at both ends. The merchant joins the count because the model names the transaction’s principals: two customers and their two banks. The central bank drops out because it is not a card party at all - it is the settlement floor under every rail this module has built. And the network is missing from the name on purpose: it is the coordinator, party to every message and to none of the money.
Wider than the screen; scroll it sideways.
The figure separates what module 0 could only narrate. Dashed paths are messages, and every one of them passes through or towards the network’s hands. Solid paths are money, and they travel the rim: carla to her issuer, issuer to acquirer as reserves at the central bank, acquirer to the cafe. No solid line touches the centre. The rest of the lesson walks the three phases that make that separation true.
Authorise: a question and a memo
At the till, carla’s card and the reader handshake, and a question leaves the cafe: will Harbour stand behind 100 on this card? The question climbs the diamond - till to Quay, Quay to the network, the network to Harbour, hops 1 to 3 in the figure - and Harbour alone can answer it. The exercise’s authorise makes the decision one line long:
available = issuer.balance(tap["cardholder"]) - holds.get(tap["cardholder"], 0)
Balance, minus every promise already outstanding. If that covers the tap, the issuer records a hold - the amount joins the holds dict - and the yes retraces the path to the till. Notice the data structure doing the work: the hold lives in a dict beside the ledger, not in an account on it. Module 1 taught you that a posting needs balanced legs; the hold has none, because nothing has been paid to anyone. It is the issuer talking to its future self: when the next question arrives, count this 100 as spoken for.
The script proves the memo has teeth. A second attempt for 200 arrives against carla’s 250, and authorise says no: 250 less the 100 hold leaves 150 available. Nothing has moved - both attempts together have moved nothing - yet the first tap already constrains the second. You met this exact shape in module 1’s project, where submit refused a payment because 120 was already owed from the queue. Authorisation is the same promise-aware arithmetic, run one layer earlier still: against memos, not even queued payments yet.
Clear: arithmetic in the night
The shop closes, and the network turns to its log. Clearing, as lesson 2 drew the line, is agreeing who owes what - and for cards the agreement is computed from the network’s own records: total the day’s taps into value owed per issuer-acquirer pair. That per-pair dict is the clearing file, and producing it is the TODO(you) in the exercise. For this day, one tap wide, it reads {("Harbour", "Quay"): 100}.
Two things about the file. First, it moves nothing: clearing is arithmetic about obligations, and every balance in the world reads exactly as it did at the beep. The file is a bill, not a payment. Second, its shape is the whole business case for the rail. Our stylised day has one tap, so the pair total and the tap are the same number - but a real day pushes a stream of taps through each pair, in both directions, and totalling per pair is where offsetting flows cancel before a unit of liquidity is spent. That is lesson 7’s deferred net settlement, wearing card branding: collect all day, net per pair, settle only the differences.
Settle: machinery you already own
The next day the file’s totals move, and the moving is the least card-like part of the story. Here is the heart of the exercise’s settle:
for tap in taps:
world.submit(tap["amount"], tap["issuer_bank"], tap["cardholder"],
tap["acquirer_bank"], tap["merchant"])
holds[tap["cardholder"]] -= tap["amount"]
world.settle()
The same submit, the same settle, the same three balanced postings you narrated in module 1 lesson 8: carla’s deposit down at Harbour, the cafe’s up at Quay, and the central bank relabelling its two reserve lines. After the run: carla 150, the cafe 100, Harbour’s line at the central bank down to 150, and assert_world green. The hold dies in the same breath, released as the real debit lands. Memo out, posting in. One simplification to be honest about: the exercise announces the pair total and then submits each cleared tap as its own interbank payment - on a one-tap day the two are the same 100. The netted version, one payment per pair at a cutoff, is lesson 7’s engine, and the module’s project runs it at scale.
One assert remains, and it is the lesson. The exercise’s Network carries an accounts dict that is deliberately present and deliberately empty, and the final check weighs it: len(network.accounts) == 0. Six messages carried; zero accounts kept. Module 0 asked you to believe that the name printed on the card holds none of the money it directs. You have now run the whole day yourself, and the claim is no longer testimony - it is an exit code.
Check yourself
1. The beep came back in a second, and carla’s balance still reads 250. What does the issuer now hold, and why is it not a posting?
A hold: a memo saying 100 of carla’s 250 is spoken for, kept beside the ledger - in the exercise, a dict - not on it. It cannot be a posting because a posting needs balanced legs, and nothing has been paid to anyone: no account anywhere has received what carla’s would lose. The hold changes what the issuer will say to the next question, not what anybody owns.
2. Carla holds 250, yet the 200 attempt is declined. What arithmetic said no, and where have you met it before?
available = balance - holds: 250 less the 100 already promised leaves 150, and 150 does not cover 200. It is the same promise-aware check as module 1’s submit, which refused a payment with “owes 120 from the queue”. Both count outstanding promises against the balance, so two affordable requests that are unaffordable together fail at the second ask, not at settlement.
3. The clearing file says {("Harbour", "Quay"): 100} and not one balance has changed. In lesson 2’s terms, what did the night produce?
The agreement, not the commit. Clearing is working out who owes whom, and the file is that answer in dict form: a bill. On this stylised day one tap makes the pair total trivially equal to the tap; on a real day taps flow through each pair in both directions and net down inside it, which is lesson 7’s whole economy. Settlement is a separate act, the next day, in reserves.
4. The settlement step calls world.submit and world.settle - the exact API from module 1, nothing card-specific. What does that tell you about the card rail?
That cards are not new settlement machinery; they are a message-and-memo layer on top of machinery you already own. Everything card-shaped - the second-fast authorisation, the hold, the nightly file - happens before and beside the ledger. When money finally moves, it moves as module 1’s three postings, deposits and reserves, which is exactly why the summary calls this rail deferred-net in a real-time costume.
5. The last assert is len(network.accounts) == 0. Why is that one line the whole lesson?
Because it is module 0’s claim made executable. The network appeared in every phase - carried the question, carried the yes, computed the file, announced the settlement - and finished the day with an empty accounts dict: party to every message, party to no money. The message layer and the money layer are different layers, and the assert pins the seam between them.
Do this
Ten minutes, from module-02-domestic-rails. Open code/four_party.py: the day is scripted - carla endowed 250 at Harbour, the cafe at Quay, one 100 tap, a 200 attempt that must decline, then clearing, settlement and the final weigh-in - and one method is yours: Network.clear, the night itself. Work the TODO(you): sum each tap’s amount into a dict keyed by the (issuer_bank, acquirer_bank) pair, and carry() one clearing message per tap as you go. The asserts are the spec: this day’s file is exactly {("Harbour", "Quay"): 100}, and clearing must move nothing - if you find yourself calling world.submit inside clear, you are settling a night early.
python3 code/four_party.py
Run it as shipped and day 0 already passes - the tap authorises, the 200 declines - before the run dies at NotImplementedError: the night’s arithmetic is the part that is yours. Green is six printed lines, the sixth being exactly
authorised in a second, settled a day later: a deferred-net rail wearing a real-time costume
with the line above it reporting that the network carried 6 messages and kept 0 accounts. Before moving on, add a temporary print(*network.log, sep="\n") at the end of main and read the day back as the network saw it: six messages, the whole story, none of the money. Delete it after. The completed version is solutions/four_party.py; compare when you are green, not before.
What you can now do. You can run a card tap through the four-party model end to end and name each phase’s relationship to money: authorisation a message and a memo, clearing an arithmetic file, settlement an ordinary interbank payment you built two modules ago. You can prove, not just repeat, module 0’s strangest claim: the party whose name is printed on the card held no money at any point - its ledger-line count is zero. And you can place the card rail correctly on this module’s map: a deferred-net rail wearing a real-time costume. What the model leaves out is the price of the costume. Your run paid the cafe the full 100, and reality does not: on every sale a fee flows between the two banks you just played, and the next lesson follows it.