Skip to content

💰 Currency – Exchange

Namespace: RevGaming.RevFramework.Currency.Exchange

The Exchange layer provides a simple, table-driven mechanism to convert one currency into another.

It is entirely optional — use it only if your economy needs designer-defined conversion rates (e.g. gold → gems, credits → tokens, scrap → fuel).

The exchange system is stateless, lightweight, and works with any composed ICurrencyService stack.


Purpose

  • Provide designer-friendly, data-driven exchange rates.
  • Keep conversion logic out of the currency service itself.
  • Support both quoting (preview) and execution (debit + credit).
  • Remain independent of composition layers (Caps, Audit, Authority, Escrow, Idempotency).

Exchange never owns balances — it delegates all mutation to the provided ICurrencyService.


Core assets

CurrencyExchangeTable

A ScriptableObject defining conversion rules between currencies.

Each rule contains:

  • fromId — source currency id
  • toId — destination currency id
  • rate — multiplier applied to the source amount
  • feePct — optional fee percentage (0 = no fee)
  • minSrc — minimum allowable source amount
  • maxSrc — maximum allowable source amount (0 = no upper limit)
  • roundDown — when true, uses floor; otherwise uses rounding

Example entry:

fromId: gold
toId: gems
rate: 0.05
feePct: 10
minSrc: 10
maxSrc: 1000
roundDown: true

Runtime behaviour

  • Currency ids are normalised (trimmed, lowercased) for lookup.
  • Lookup cache is rebuilt on enable/validate.
  • Missing rules cause TryQuote to return false.

TableCurrencyExchange

Concrete implementation of ICurrencyExchange backed by a CurrencyExchangeTable.

API

bool TryQuote(CurrencyId src, CurrencyId dst, long srcAmount, out long dstAmount);

CurOpResult TryExchange(
    ICurrencyService svc,
    GameObject owner,
    CurrencyId src,
    CurrencyId dst,
    long srcAmount,
    string reason = "Exchange",
    string sourceId = null);

Behaviour

  • Validates minSrc and maxSrc.
  • Applies conversion as:
    dst = srcAmount * rate * (1 - feePct / 100)
    
  • Rounds using floor or Math.Round depending on roundDown.
  • Returns failed results when:
  • No matching rule exists (CurOpCode.NotFound)
  • Source amount is invalid or below minSrc
  • Computed destination amount is <= 0
  • TryQuote performs math only and does not check wallet balance or policy/authority. Execution may still fail at debit/credit time.

⚠ Important details

  • TryQuote clamps srcAmount when maxSrc > 0.
  • TryExchange calls TryQuote, but still debits the original srcAmount.
  • Callers must always quote first to validate amounts.
  • TryExchange performs a debit followed by a credit.
  • If the credit fails after a successful debit, a best-effort rollback credit is attempted.
  • Full transactional atomicity is not guaranteed.
  • On credit failure, rollback uses the audit reason label "RollbackExchange" when auditing is enabled.

Factories

Create an exchange engine via CurrencyFactories:

var ex = CurrencyFactories.BuildExchange(exchangeTable);

This returns an ICurrencyExchange without exposing concrete implementation types.


Usage example

var ex = CurrencyFactories.BuildExchange(table);

if (ex.TryQuote(new CurrencyId("gold"), new CurrencyId("gems"), 200, out var gems))
    Debug.Log($"200 gold = {gems} gems");

var result = ex.TryExchange(
    svc, player,
    new CurrencyId("gold"),
    new CurrencyId("gems"),
    200,
    "ShopExchange");

if (!result.Success)
    Debug.LogWarning(result);

Gotchas

  • feePct is applied after the rate conversion.
  • maxSrc == 0 means no upper limit.
  • Quotes are always recomputed — no caching.
  • TryQuote ignores caps and policy — it is pure math.
  • TryExchange performs real debit/credit via the composed service, so:
  • Caps
  • Audit
  • Authority
  • RequireEscrow
  • Idempotency

may all apply at execution time.


Best practices

  • Keep exchange tables in dedicated assets.
  • Use feePct instead of baking fees into rates.
  • Always call TryQuote before TryExchange.
  • If maxSrc is used, clamp your input amount before calling TryExchange (the exchange does not automatically debit the clamped amount).
  • Group exchange tables by purpose (global, shop, crafting, tiered economy).
  • Store tables in a consistent location (e.g. Assets/RevFramework/Economy/Exchange/).

Safe to delete

If your game does not need currency conversion, you can delete this entire folder safely.

No other part of the Currency system depends on Exchange.


  • Core — transactions, purchases, awaiters
  • Definitions — currency metadata for UI
  • Policies — caps and escrow enforcement
  • UI — currency bars and debug tooling