Skip to content

🧠 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:

  • CurrencyDelta is 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