Declare them
One currency or several: an in-app economy usually runs on soft currency alone, and the calls name the code on every movement.
ledger({
currencies: [
{ code: "chips", name: "Casino Chips" },
{ code: "gold", name: "Gold", decimals: 2 },
],
})decimals defaults to 0. An empty currency set is refused, and so are duplicate codes.
A code is lowercase letters, digits and dashes — US Dollars is not a currency code. The reference has the grammar.
Amounts are integers in the minor unit
Always. Never floats.
decimals says nothing about storage. It says how to display.
| Stored | decimals | Displays as |
|---|---|---|
150 | 0 | 150 |
150 | 2 | 1.50 |
The ledger stores 150 in both cases — an integer, which is what lets the three invariants be enforced by the database rather than by a handler. Every credit, debit, hold and transfer is that integer, and arithmetic on it is exact.
So the conversion happens once, at the edge
Your UI multiplies by 10 ** decimals on the way in and divides on the way out. Nothing between those two points ever sees a fraction.
If you find yourself dividing before a ledger call, the division belongs on the other side of it.
Currency is half of an account’s identity
An account is unique on (userId, currency) — one account per player per currency, and that pair is the conflict target when a first credit opens one.
So alice holding chips and alice holding gold are two accounts, two balances, two histories. They cannot fund each other, and there is no exchange operation, because an exchange rate is a business decision the ledger has no basis to make.
Building one is two operations under one ref prefix: debit the source, credit the destination, at whatever rate your own code decided.
Choosing what is a currency
A currency is something a player holds and spends.
| Chips, gems, gold, credits, tokens | Yes |
| Spendable reward points | Yes — you spend them |
| A score | No |
| XP or a level | No |
The test is one question: can a player spend it?
If they can never spend it, it is a score, and the ledger’s holds, overdraft protection and transfers apply to none of it. Using the ledger for XP is forcing a spend-and-settle ledger onto a counter.
Scores belong in leaderboard. XP belongs in a board or a small table of your own.