Platform adapter

One interface — PlatformAdapter — covers everything upstream of the RGS: session lifecycle, money movement, free-round promo free-rounds, and platform events. How it talks to the operator (one WS, three microservices, REST + polling) is the integrator's call.

Interface

interface PlatformAdapter {
  connect(): Promise<void>;
  disconnect(): void;
  readonly isHealthy: boolean;
  readonly diagnostics: Record<string, unknown>;

  openSession(sessionId, connectionId): Promise<SessionInfo>;

  settleSimple(req):    Promise<RoundReceipt>;   // simple round = 1 RPC
  openComplex(req):     Promise<RoundReceipt>;   // complex = 2 RPCs
  updateComplex?(req):  Promise<void>;           // optional audit, no money
  closeComplex(req):    Promise<RoundReceipt>;

  onEvent(handler: (e: PlatformEvent) => void): void;
}

Money flow

Round shapeWallet callsWhere
simple (slot spin)settleSimple(bet, win)once per SPIN_REQUEST
complex (Mines, Road)openComplex(bet)at OPEN_REQUEST
complex (Mines, Road)closeComplex(win)at CLOSE_REQUEST
complex — STEPnoneSTEP is pure in-process

Two transactions per round, no exceptions. Mid-round step calls never touch the wallet. If you need an audit trail for in-flight state, implement the optional updateComplex — the orchestrator calls it fire-and-forget after each step; failures are logged, never surfaced to the player.

SessionInfo

{
  sessionId, currency,
  currencyDecimals,               // 2 = EUR/USD, 0 = JPY, 8 = BTC. Adapter sources it.
  balance,                        // integer minor units (× 10^currencyDecimals)
  allowedBets: number[],          // integer minor units
  defaultBetIndex,
  promo?:    PromoFreeRounds,
  openRound?: OpenRoundResume,    // round in flight from a prior connection
  carry?:     string,             // math's cross-round opaque blob (you stored it)
  nextMode?:  string,             // math's hint, you stored it
  mathVersion?: string,           // version that produced carry — discard on mismatch
}

The adapter is the source of truth for cross-round state. RGS stores nothing durable: it asks openSession for carry / nextMode / mathVersion and threads them into the next round's math.play or math.open.

Settle requests

SettleSimple = {
  sessionId, bet, betIndex, priceMultiplier, win, multiplier, type,
  roundState,            // this round's final math state, also the carry
  nextMode?, mathVersion?, promoId?, idempotencyKey?,
}

OpenComplex = {
  sessionId, bet, betIndex, priceMultiplier,
  initialState,          // math's opening state blob
  mathVersion?, promoId?, idempotencyKey?,
}

CloseComplex = {
  sessionId, roundId,
  finalState,            // THIS round's final math state
  carry?,                // NEXT round's seed — adapter stores for return on openSession
  nextMode?, mathVersion?,
  win, multiplier, type, idempotencyKey?,
}

RoundReceipt = { roundId, balance, promo?: { remaining } }

Event stream

The adapter pushes platform-originating events into the orchestrator via the handler installed by onEvent.

type PlatformEvent =
  | { type: "balanceChanged",    sessionId, balance, reason }
  | { type: "sessionClosed",     sessionId, reason }
  | { type: "promoGranted",      sessionId, promo }
  | { type: "autocloseRequested", sessionId, roundId?, reason };

autocloseRequested is the wallet's way to tell RGS to settle an in-flight complex round (e.g. session-end policy, compliance trigger). RGS-side autoclose is never timer-driven — always external.

Numerics & idempotency

All currency amounts are integers in the currency's minimal unit. The unit is scaled by SessionInfo.currencyDecimals: USD/EUR 1.00 → 100 when decimals = 2, BTC 0.00000001 → 1 when decimals = 8, JPY 100 → 100 when decimals = 0. Adapters facing platforms that expect decimal strings or floats convert at the boundary with toWireAmount / fromWireAmount from @open-rgs/adapter-kit — pick an explicit RoundingMode (default half_even, banker's rounding).

Every state-changing wallet RPC carries an idempotencyKey (default: uuid-v4, 5 min TTL). Forward it to the wallet if upstream supports dedupe; otherwise ignore it. Configure via createServer({ idempotency }).

Authoring resources