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

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

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

The one-sentence model

Currency is a guarded ledger where intent, permission, legality, execution, and observation are strictly separated.


TL;DR

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