💰 Currency — System Guarantees Matrix
This page defines the behavioural contract of the Currency system.
No marketing. No implication. Just guarantees — and explicit
non-guarantees.
🔎 Quick Navigation
- Core Service
- Caps
- Authority
- Escrow
- RequireEscrow Guard
- Idempotency
- Batch Events
- Transactions
- Hold Transactions
- Persistence
- Exchange
- Awaiters
- UI
- Definitions
- Public API
- Non-Guarantees
- Final Summary
1️⃣ Core Service
ICurrencyService
| Aspect |
Guarantee |
| Storage |
Per-owner (GameObject) balances keyed by CurrencyId |
| Units |
Integer minor units (long) |
| Threading |
Synchronous, expected to run on Unity main thread |
| Mutation result |
Always returns CurOpResult |
| Events |
Emits OnWalletChanged after successful mutation |
| Atomicity |
✔ Single-operation atomic |
| Cross-operation atomicity |
❌ Not guaranteed |
2️⃣ Caps
CappedCurrencyService
| Aspect |
Guarantee |
| Min/Max enforcement |
✔ Per-currency |
| Clamp mode |
Adjusts to bounds |
| Fail mode |
Returns BelowMinimum / AboveMaximum |
| Applies to |
Set, Credit, Debit, Transfer |
| Transfer semantics |
Uses transfer preview helper |
| Cross-operation atomicity |
❌ Not guaranteed |
3️⃣ Authority
AuthorityCurrencyService
| Aspect |
Guarantee |
| Gated ops |
Credit, Debit, SetBalance, Transfer |
| Read ops gated |
❌ No (GetBalance, EnsureWallet allowed) |
| Missing binder |
Returns Unauthorized |
| Denied mutation |
Returns Unauthorized |
| Multiplayer logic |
❌ Not provided |
| Replication |
❌ Not provided |
4️⃣ Escrow
EscrowCurrencyService
| Aspect |
Guarantee |
| TryHold |
Immediate debit + token |
| Commit |
Logical finalize (no additional debit) |
| Release |
Refund credit |
| TTL |
Optional expiry |
| ExpireStale |
Refund or drop destroyed owners |
| Hard atomic multi-op |
❌ Not by itself (single-hold atomic only) |
5️⃣ RequireEscrow Guard
| Aspect |
Guarantee |
When RequireEscrow == true |
Debit/Transfer must use escrow |
| Escrow missing |
Deterministic ServiceMissing |
| Affects Credit |
❌ No |
| Adds escrow capability |
❌ No (guard only) |
6️⃣ Idempotency
IdempotencyCurrencyService
| Aspect |
Guarantee |
| Scope |
Audit-aware overloads only |
| Key format |
"|rid:<token>" suffix in sourceId |
| Duplicate request |
Short-circuits to Ok("Idempotent replay") |
| Non-audit calls |
Pass through unchanged |
| Storage |
Per-owner fixed-capacity ring buffer |
| Cross-process dedupe |
❌ No |
Note:
Replayed requests may not emit wallet events or audit entries, depending on decorator order.
7️⃣ Batch Events
| Aspect |
Guarantee |
| Requires |
WithBatchEvents |
| Batch capture |
Via CurrencyBatch |
| Emit control |
Caller decides Emit() |
Per-op OnWalletChanged |
✔ Always fires normally |
| Auto-emit on dispose |
❌ No |
| Multi-op collapse |
✔ Single aggregated batch event (when emitted) |
Batching affects event emission only, not mutation behaviour.
8️⃣ Transactions
CurrencyTxn
| Aspect |
Guarantee |
| Multi-op staging |
✔ Yes |
| Pre-sanity check |
Funds + overflow only |
| Rollback on failure |
✔ Best-effort reverse ops |
| Uses composed stack |
✔ Yes |
| Strong atomicity |
❌ Not guaranteed |
| Escrow-aware |
❌ No |
9️⃣ Hold Transactions
CurrencyHoldTxn
| Aspect |
Guarantee |
| Requires escrow |
✔ Yes |
| Phase A |
Acquire holds first |
| Phase B |
Preflight credit effects |
| Phase C |
Apply credits |
| Phase D |
Commit tokens |
| Failure before credit phase |
✔ Net-zero guarantee |
| Failure after credit phase |
✔ Best-effort unwind |
| True atomicity |
❌ Not guaranteed |
🔟 Persistence
| Aspect |
Guarantee |
| Snapshot type |
Absolute balances |
| Restore behaviour |
Sequential SetBalance |
| Rollback |
✔ Best-effort per owner |
| Batch emit |
✔ Single event when supported |
| Authority applies |
✔ Yes |
| RequireEscrow applies |
✔ Yes |
| Escrow holds used |
❌ No |
| Cross-owner atomicity |
❌ No |
11️⃣ Exchange
| Aspect |
Guarantee |
| Quote |
Non-mutating calculation with rule constraints (rate, min/max, fee, rounding) |
| Execution |
Debit then Credit |
| Credit failure rollback |
✔ Best-effort |
| Uses composed stack |
✔ Yes |
| True atomic swap |
❌ No |
| Cap/Authority enforced |
✔ Yes |
12️⃣ Awaiters
CurrencyAwaiters
| Aspect |
Guarantee |
| Waiting model |
Event-driven with lightweight fallback polling for invalidation |
| Async versions |
May resume off main thread |
| Coroutine versions |
Always resume on main thread |
| Service absence |
Await returns failure / default |
13️⃣ UI
| Aspect |
Guarantee |
| Event-driven updates |
✔ Yes |
| Service resolution |
Via CurrencyResolve |
| Auto-rebind |
✔ Yes (service swap detection) |
| Modifies state |
❌ Never |
| Multiplayer-safe default |
❌ Owner fallback is SP convenience |
14️⃣ Definitions
| Aspect |
Guarantee |
| Formatting only |
✔ Yes |
| Affects storage |
❌ No |
CurrencyId contract |
Stable primitive |
| Required for runtime |
❌ No |
15️⃣ Public API
| Aspect |
Guarantee |
| Stable supported helpers |
✔ Yes |
| Exposes internal types |
❌ No |
| Batch wrapper safe |
✔ Yes |
| TransferPolicyProviders |
Preview logic only |
🚨 Non-Guarantees
Currency does not guarantee:
- ❌ True multi-operation atomicity across decorators
- ❌ Network replication
- ❌ Server authority enforcement
- ❌ Deterministic rollback across policy + authority + idempotency
- ❌ Automatic escrow integration (must compose explicitly)
- ❌ Cross-scene ownership safety without
StableId
- ❌ Strong transactional guarantees under arbitrary decorator reorder
🎯 Final Summary
| Layer |
Strong Guarantee |
Best Effort |
Not Guaranteed |
| Single operation |
✔ |
|
|
Multi-op rollback (CurrencyTxn) |
|
✔ |
|
| Escrow holds |
✔ |
|
|
| Persistence atomicity (per owner) |
|
✔ |
|
| Cross-stack atomicity |
|
|
❌ |
| Multiplayer replication |
|
|
❌ |
System Philosophy
Currency is:
- Deterministic
- Explicit
- Decorator-composed
- Service-driven
- Honest about guarantees
It is not:
- A database
- A networking framework
- A financial-grade ACID system