🧠 Currency System — Mental Model¶
This page explains how to think about Currency in RevFramework.
It is not an API reference.
It is a conceptual map that helps you decide:
- Where does this behaviour belong?
- Which extension point should I use?
- What should I never touch?
If you understand this page, the Currency API will feel obvious.
🎯 The Core Idea¶
Currency is a deterministic ledger with guarded mutation, not a UI counter.
A currency value is not “just a number you change”.
It is a ledger-backed state that must:
- validate intent
- enforce policy
- respect authority
- emit truthful events
- support rollback and replay
Everything in the system exists to protect that ledger.
🧱 The Single Source of Truth¶
ICurrencyService¶
The currency ledger is owned and mutated through ICurrencyService
ICurrencyService
It owns:
- balances
- mutation rules
- event emission
- orchestration guarantees (rollback, batching when supported)
Nothing else is allowed to:
- mutate balances
- fake deltas
- bypass policy or authority
- emit currency events
If something changes currency without going through the service, it’s wrong.
🔄 The Currency Mutation Model¶
Every currency change conceptually flows through the same logical stages:
Intent
↓
Validation
↓
Policy
↓
Authority
↓
Mutation
↓
Events / Audit
However:
These stages are conceptual, not a fixed runtime pipeline.
In practice the order is defined by decorator composition (via CurrencyFactories).
Example stack:
Caps → Audit → Authority → Idempotency → BatchEvents
Each decorator guards part of the mutation process.
🔍 Stage 1: Intent (The Request)¶
Question: “What is being requested?”
At this stage:
- inputs are validated
- amounts must be valid
- currency IDs must be valid
This is where programmer errors are rejected.
What belongs here¶
- argument validation
- request metadata (
reason,sourceId)
What does NOT belong here¶
- balance checks
- policy enforcement
- authority decisions
Intent is about shape, not permission.
📏 Stage 2: Policy (Rules & Caps)¶
Question: “Is this value allowed?”
Policy is defined by CurrencyPolicy and evaluated via CurrencyCapRule.
Policy can:
- clamp values
- reject mutations
- require escrow globally
Design rule¶
Policy decides legality — it does not perform mutations.
🔐 Stage 3: Authority (Who May Act)¶
Question: “Is this caller allowed to do this?”
Authority is enforced via ICurrencyAuthority.
Authority:
- may block operations
- never changes amounts
- never emits events
This design keeps Currency multiplayer-ready.
💰 Stage 4: Mutation (The Ledger Write)¶
Question: “Write the new balance.”
This is the only place where balances actually change.
Rules:
- mutations are deterministic within a given composed service stack
- overflows are detected
- insufficient funds fail cleanly
For a single operation:
If mutation fails, the ledger remains unchanged.
For orchestrations:
- earlier successful steps may require rollback
- rollback is best-effort unless escrow is used
📊 Stage 5: Events & Audit (Observation)¶
After a successful mutation:
CurrencyDeltais emitted- optional audit entries are recorded
- batch events may be emitted
Design rule¶
Events reflect what actually happened — never intent.
🧾 Audit (Optional, Observational)¶
Audit:
- records successful mutations
- stores before/after/delta
- is read-only to consumers
Audit is a ledger of history, not a source of truth.
📦 Transactions¶
CurrencyTxn¶
- ordered execution
- best-effort rollback on failure
- optional batch emission
Rollback may be blocked by policy, authority, escrow, or idempotency depending on stack composition.
CurrencyHoldTxn¶
Uses escrow holds for stronger guarantees.
Process:
Acquire holds → Preflight → Apply credits → Commit tokens
🔁 Exchange¶
Exchange performs:
Debit source → Credit destination
Quoting is non-mutating and rule-based.
Execution uses best-effort rollback on credit failure.
🔐 Escrow¶
Escrow supports reservations and delayed confirmation.
TryHold → debit immediately
Commit → finalize (logical no-op; funds already debited at hold time)
Release → refund
Expired holds are processed via ExpireStale.
🧩 Batching¶
Batching affects event emission only, not mutation behavior.
Batch events require the service stack to include:
CurrencyFactories.WithBatchEvents()
🧰 Adapters & External Systems¶
Currency does not directly depend on:
- UI
- Inventory
- Save systems
- Networking
External systems adapt to Currency, not the other way around.
🚫 What Never Belongs Where¶
- UI must not mutate currency directly
- Events must not imply success before mutation
- Policy must not write balances
- Authority must not change amounts
- Audit must not become a dependency
- External code must not emit currency deltas
🧠 The One‑Sentence Model¶
Currency is a guarded ledger where intent, permission, legality, execution, and observation are strictly separated.
TL;DR¶
- One service owns truth
- Policy decides legality
- Authority decides permission
- Ledger writes are deterministic
- Events reflect reality
- Everything else adapts around the service