25 min

The two tiers of money

Money differs by whose ledger it sits on: reserves are entries at the central bank that only banks can hold, while deposits are commercial-bank IOUs the rest of us use.

Where you are. Lesson 4 left you holding one bank as a balance sheet: typed accounts, balanced postings, and a sheet that re-asserts itself after every write. Module 0’s map lesson told you where that bank sits - tier 2 of a two-tier system, with the central bank’s ledger above it - and lesson 5 of that module gave you the word for the bank’s own balance up there: reserves. This lesson builds the upper tier in code. Three classes - World, Bank, CentralBank - stand the whole map up, and one new invariant ties the tiers together: a mirror the drawing could never enforce.

A drawing cannot raise

Take module 0’s pyramid - the narrow band on top for the central bank’s ledger, the wide band below for the commercial banks - and deface it. In the top band, write Alder reserves: 300. In the bottom band, inside Alder’s box, write reserves: 500. Step back and look. The drawing does not object. Ink never does: you can sketch a monetary system in which a bank believes it has 500 at the central bank while the central bank has only ever heard of 300, and the page holds both numbers without complaint. Module 0’s fifty-line toy was barely stricter; its ledgers were dict literals, and the author typed the opening balances in by hand, trusted. This lesson replaces the trust with a class that refuses. From here on the map is a program, and a world where the tiers disagree will not run.

The idea in one paragraph

Money differs by whose ledger it sits on. Reserves are entries on the central bank’s ledger, and only banks can hold them; deposits are entries on commercial banks’ ledgers, the IOUs the rest of us use. The miniledger makes both tiers concrete: CentralBank is tier 1, Bank is tier 2, and World holds one of the former and any number of the latter. What the code adds to the drawing is a checkable seam between the tiers: every bank’s reserves line is an asset in its own books and, at the same moment, a liability line named after that bank in the central bank’s books - and the two must agree, to the unit, at all times. assert_world() walks that mirror line by line after every change and raises on the first disagreement. The map is no longer a picture of the system; it is an invariant of it.

Three classes, two tiers

Here is the world this lesson and its exercise live in, built with the library’s real API:

from miniledger import World, Bank

world = World()
world.admit(Bank("Alder"))
world.admit(Bank("Birch"))
world.banks["Alder"].open_deposit("alice")
world.banks["Birch"].open_deposit("bob")

A Bank is lesson 4’s balance sheet given a job: it opens with a reserves asset line, a loans asset line that stays empty until lesson 9, and an equity line. open_deposit adds one liability line per customer, opened at zero - the bank’s IOU, awaiting money. A World starts with its central bank already standing; you supply the banks.

World.admit is the gate between the tiers. Admitting a bank does two things at once: the bank joins the world, and the central bank opens a liability line in its name - world.central_bank.line("Alder") returns "Alder reserves", and that string is a real account on the tier-1 ledger. There is no other door. Nothing in the API opens a tier-1 line for alice, and that absence is this lesson’s claim made structural: reserves are entries at the central bank that only banks can hold.

Module 0’s map lesson handed you three names, and the class list encodes the first two. Central-bank money is what lives on the CentralBank ledger - here, the reserve lines. Commercial-bank money is the deposit lines on each Bank. Private claims on either - the side box on the map - get no class at all yet: the miniledger models the two tiers, and later modules build the side box’s residents on top of it. One deliberate absence: this tier 1 carries only reserves. The other resident of the real tier 1, the banknote, is the next lesson’s business.

One balance, two ledgers

Now the seam. You already know the pattern from your own account: a deposit is your asset and the bank’s liability - one balance, two sets of books, opposite kinds. Reserves repeat it exactly one tier up. Alder’s 500 at the central bank is Alder’s asset, its claim on the central bank; and it is the central bank’s liability, recorded on the line kept in Alder’s name. Same balance, two ledgers. The drawing showed this as two bands; the code stores it as two entries that must never drift apart. That pairing is the mirror.

tier 1 central bank ledger Alder reserves · 500 Birch reserves · 200 its liabilities · one line per bank must agree must agree tier 2 Alder's ledger reserves · 500 the bank's asset · its claim on tier 1 alice · 500 a deposit · Alder's IOU tier 2 Birch's ledger reserves · 200 the bank's asset · its claim on tier 1 bob · 200 a deposit · Birch's IOU
The two-tier map as ledgers: the central bank's ledger holds one reserve liability line per bank, each bank's ledger holds the matching reserves asset line, and a double-headed arrow labelled must agree ties each pair together

Wider than the screen; scroll it sideways.

Opening balances, honestly

Module 0’s toy typed alice’s 500 straight into a dict literal: an opening balance by authorial fiat, with no record of where the money came from. The miniledger closes that door. Every account opens at zero, and money only ever moves by a balanced posting. So where does the first unit come from? World.endow:

world.endow("Alder", "alice", 500)
world.endow("Birch", "bob", 200)
world.assert_world()

endow is the honest version of “just give her a balance”: the central bank furnishes fresh reserves to the bank, and the bank credits the customer’s deposit against them. Two balanced postings, one per tier, four lines moving together.

The shape is not an invention of the toy. When a real central bank wants more reserves in the system, it buys assets, and the postings land exactly like this: the central bank’s asset side grows against a bigger reserve line, and a customer’s deposit grows against the bank’s bigger reserve balance. Lesson 10 names that operation and the levers around it; for now, take the honest rule: opening balances exist because the central bank furnished them, and the ledgers say so.

The alarm that walks the map

assert_world() runs three checks, in order. The central bank’s sheet must balance. Every bank’s sheet must balance. And then the check no single ledger could run for itself - straight from the library:

mirror = self.central_bank.balance(self.central_bank.line(bank.name))
if mirror != bank.balance("reserves"):
    raise LedgerError(
        f"tier mismatch: {bank.name} carries reserves "
        f"{bank.balance('reserves')} but the central bank's line says {mirror}"
    )

That is the defaced drawing from the top of this lesson, refused in five lines of Python.

Review

Money differs by whose ledger it sits on

Money differs by whose ledger it sits on. Reserves are entries on the central bank’s ledger, and only banks can hold them. Deposits are entries on commercial banks’ ledgers, the IOUs the rest of us use. That is the whole two-tier map, and the code makes both tiers concrete rather than drawn: one central bank, any number of commercial banks, and a world holding both. Which tier your money is on decides who owes it to you, and therefore what happens to it when that institution fails.

The seam between the tiers is checkable

What the code adds to the drawing is a checkable seam. Every bank’s reserves line is an asset in its own books and, at the same moment, a liability line named after that bank in the central bank’s books, and the two must agree to the unit at all times. The world walks that mirror line by line after every change and raises on the first disagreement. So the map stops being a picture of the system and becomes an invariant of it: not a description you could drift away from without noticing, but a condition the code refuses to run without. That same mirror shape returns across the border, where nothing enforces it and reconciliation has to do the job by hand.

Check yourself

1. Alder’s reserves line and the central bank’s “Alder reserves” line hold the same number, yet one is an asset and one is a liability. How are both right?

They are the same balance seen from opposite sides. The 500 is Alder’s claim on the central bank, so it sits on the asset side of Alder’s books; and it is what the central bank owes Alder, so it sits on the liability side of the central bank’s. This is the deposit relationship you already live with - your asset, the bank’s liability - repeated one tier up. The mirror check exists because two ledgers recording one relationship can drift, and nothing inside either ledger alone would notice.

2. Why does the miniledger force opening balances through endow() instead of letting you assign alice a 500 directly, the way module 0’s toy did?

Because a bare assignment creates money with no issuer and no paired leg - a number with no account of where it came from. endow makes the origin explicit and balanced: the central bank furnishes fresh reserves (its liability line up, its assets up), and the bank credits the deposit against its new reserves. Four entries, two balanced postings, mirror intact. The library’s docstring calls endowment the central bank’s act, and lesson 10 names the real operation it mirrors.

3. You reach into Alder’s books and add 100 to its reserves balance directly. Which check catches it, and why is it not the tier-mismatch alarm?

The per-ledger check. Alder’s assets grew while its liabilities and equity did not, so its own sheet no longer balances and assert_world() raises balance sheet broken before the mirror comparison ever runs. The tier-mismatch alarm only gets its turn when every individual sheet balances; it exists for the more careful forgery - reserves overstated and a deposit overstated to cover it - which every within-ledger check would wave through.

4. Where do module 0’s three kinds of money land in the code, and what is missing?

Central-bank money is the entries on the CentralBank ledger; in this world, the per-bank reserve lines. Commercial-bank money is the deposit liability lines on each Bank. Private claims on either have no class at all yet; the miniledger models the two tiers, and the side box waits for later modules to build on it. Also missing is tier 1’s other resident, the banknote: this world’s central-bank money is reserves only, and physical cash is the next lesson’s subject.

Do this

Fifteen minutes, nothing beyond the standard library and the course’s own miniledger. Work from module-01-money-at-rest and open code/two_tiers.py. Its build_world assembles exactly this lesson’s world - Alder and Birch admitted, alice endowed 500, bob endowed 200 - and the one TODO(you) is tier_report: return the three totals that must agree. Sum the central bank’s per-bank reserve lines (world.central_bank.line(name) gives you each line’s name), sum the banks’ own reserves balances, and sum the deposit lines across banks - the accounts whose kind is "liability".

python3 code/two_tiers.py

Run as shipped, the starter stops at NotImplementedError. Completed correctly, the assert passes, assert_world() walks the mirror one last time, and the script ends with the line

both tiers agree line by line: {'tier1_reserve_liabilities': 700, 'tier2_reserve_assets': 700, 'tier2_deposits': 700}

All three totals are 700, which is 500 plus 200, and the third equality deserves a stare: deposits equal reserves here only because every unit arrived by endowment. The moment a bank lends - lesson 9’s subject - deposits grow while reserves do not, and tier2_deposits parts company with the other two while the mirror between the first two holds. If the assert fires instead, print your report and find which total drifted; the usual slip is counting equity among the liabilities. The completed version is in solutions/two_tiers.py.

What you can now do. You can build the two-tier world in code: admit banks through the only gate that opens a tier-1 line, endow opening balances honestly instead of typing them into a literal, and read the same balance twice - an asset in the bank’s books, a liability on the central bank’s matching line. You can state the seam between the tiers as an invariant that assert_world() checks line by line, and you know the order the alarms fire when someone lies to it: a crude forgery breaks the bank’s own sheet first, and only the balanced lie needs the mirror. The next lesson adds the one resident of tier 1 this world leaves out - physical cash, the central-bank money the public can actually hold - and shows what a withdrawal does to both balance sheets at once.

What you can now do

You can build the two-tier world in code and verify the mirror that ties the tiers together.