Skip to content

💰 Currency System

🎯 Purpose

Currency is a modular, service-driven wallet system for managing balances such as gold, gems, tickets, energy, etc.

It is:

  • Wallet-agnostic — you can replace the backing implementation (advanced use)
  • Network-agnostic — authority and idempotency are optional layers
  • UI-agnostic at its core — the runtime service has no UI dependency
  • Inventory-agnostic at its core — the Inventory adapter compiles only when REV_INVENTORY_PRESENT is defined
  • Decorator-composed — behaviour is layered explicitly via factories

Everything revolves around one contract:

ICurrencyService is the authority for ledger state and mutation. Everything else composes behaviour around it.


🧠 System Philosophy

Currency is:

  • Deterministic
  • Explicit
  • Composition-first
  • Service-driven
  • Honest about its guarantees

It is not:

  • A networking framework
  • A replication layer
  • A transactional ACID database
  • A hidden global system

🧩 What Lives Here

Concept What it is
ICurrencyService Core wallet operations (credit/debit/set/transfer + events)
CurrencyId Canonical id ("gold", "gems") normalized lowercase + trimmed
Money long minor units (no floats), overflow-checked
Decorators Optional layers via CurrencyFactories (Caps, Audit, Authority, Escrow, RequireEscrow, Idempotency, BatchEvents)
Bootstrap Scene helper that composes and publishes a stack
Persistence (optional) Snapshot + JSON helpers
Exchange (optional) Table-based conversion engine
UI (optional) UGUI / TMP / UITK bars
Policies (optional) Cap rules + RequireEscrow
Definitions (optional) Metadata + formatting
Public API Stable helper surfaces
UX Reason-text helpers

🧠 Usage Guidance

1) Add to your scene

Minimum setup:

  1. SceneCurrencyService
  2. (Recommended) CurrencyServiceBootstrap

2) Use from gameplay code

var svc = CurrencyResolve.ServiceFrom(this);
var gold = new CurrencyId("gold");

var result = svc.Debit(player, gold, 50);

if (!result.Success)
    Debug.LogWarning($"Debit failed: {result.Code} ({result.Message})");

3) Listen for changes

svc.OnWalletChanged += d =>
{
    Debug.Log($"{d.owner.name} {d.currency} changed: {d.before.amount} -> {d.after.amount}");
};

🧩 Core Contract: ICurrencyService

bool EnsureWallet(GameObject owner);
Money GetBalance(GameObject owner, CurrencyId currency);

CurOpResult Credit(GameObject owner, CurrencyId currency, Money amount);
CurOpResult Debit(GameObject owner, CurrencyId currency, Money amount);
CurOpResult SetBalance(GameObject owner, CurrencyId currency, Money newBalance);
CurOpResult Transfer(GameObject from, GameObject to, CurrencyId currency, Money amount);

event Action<CurrencyDelta> OnWalletChanged;

⚠️ Important Notes

  • Operations are synchronous
  • Successful mutations emit OnWalletChanged
  • Single operations are atomic
  • Multi-operation atomicity is not provided by default

🧱 Composition & Decorators

You do not instantiate decorator types directly. Use CurrencyFactories.

svc = CurrencyFactories.WithCapsThenAudit(svc, policy);
svc = CurrencyFactories.WithAuthority(svc, this);
svc = CurrencyFactories.WithIdempotency(svc);
svc = CurrencyFactories.WithBatchEvents(svc);
  1. Caps
  2. Audit
  3. Authority
  4. (Escrow / RequireEscrow when composed)
  5. Idempotency
  6. BatchEvents

⚠️ Important Notes

RequireEscrow vs Escrow

  • RequireEscrow = guard
  • Escrow = capability

Escrow must be explicitly composed. RequireEscrow only enforces it.


🧠 Usage Guidance

Service Resolution

var svc = CurrencyResolve.ServiceFrom(this);

Resolution order:

  1. Published override
  2. Cached scene result
  3. Scene search (SceneCurrencyService)

Bootstrap

CurrencyServiceBootstrap:

  • Finds inner service
  • Composes stack
  • Publishes via override
  • Cleans up on disable

🧩 What Lives Here (Optional Systems)

Persistence

var snap = CurrencyPersistence.Capture(svc, player, ids);
CurrencyPersistence.Restore(svc, player, snap, "LoadWallets");

Exchange

var ex = CurrencyFactories.BuildExchange(table);
ex.TryExchange(svc, player, src, dst, 100, "Exchange");

UI

  • CurrencyBar
  • CurrencyBarTMP
  • CurrencyBarUITK

Public Helpers

  • CurrencyBatching
  • TransferPolicyProviders

📦 Folder Overview

  • Abstractions
  • Core
  • Internal
  • Bootstrap
  • Authority
  • Policies
  • Definitions
  • Exchange
  • Persistence
  • UI
  • PublicAPI
  • EditorApi
  • Adapters
  • UX

⚠️ Important Notes

  • UI, Exchange, Persistence, and Definitions are optional layers
  • Adapters depend on external systems (e.g., Inventory)
  • Composition depends on installed modules and defines

🚫 Not for Production Use

  • Debug/UX helpers (e.g., reason-text utilities) are not intended as gameplay logic
  • Sample/UI components should be treated as reference implementations

  • Currency Public API
  • Currency Integration Surfaces
  • Currency Mental Model
  • Currency FAQ

🎯 Summary

Currency provides a stable ICurrencyService contract with optional composition:

  • Caps
  • Audit
  • Authority
  • Escrow
  • RequireEscrow
  • Idempotency
  • Batch events
  • Persistence
  • Exchange
  • UI

You choose the stack. The contract remains stable.