Dual-Currency Economy

The platform has first-class Gold and Diamond wallets alongside the virtual-chip balance and tournament tickets.

Persistence

  • CurrencyCode is the wallet currency enum: GOLD and DIAMOND.
  • Wallet stores spendable balance, reserved locked, and a per-row version for each (userId, currency).
  • Transaction.currency and TransactionArchive.currency identify the wallet touched by each ledger row.
  • CurrencyRate stores effective Diamond to Gold rate history.
  • CurrencyConversion stores one-way conversion attempts and links its two ledger rows by conversionId.
  • CurrencyPackage stores cashier package definitions for Gold and Diamond purchases.
  • PokerTable.currency declares the wallet used by real-money table buy-ins and cash-outs.

User.balance is still dual-written for Gold. Treat Wallet(GOLD) as the source of truth for new code, but keep reads of the legacy column compatible.

Service Ownership

  • src/services/wallet.service.ts is the only service that should mutate Gold or Diamond wallet balances directly.
  • src/services/currency-conversion.service.ts owns Diamond to Gold conversion, daily caps, idempotency, and rate snapshotting.
  • src/services/economy-settings.service.ts owns operator controls: dualCurrencyEnabled, walletCutover, conversionEnabled, conversionMin, conversionMax, conversionDailyCapPerUser, diamondToGoldRate, goldWithdrawable, goldPurchasesEnabled and diamondPurchasesEnabled.
  • src/services/economy-reconciliation.service.ts verifies per-currency wallet totals against signed ledger deltas and continuity.
  • scripts/migrate-dual-currency.ts backfills Gold wallets from User.balance and writes MIGRATION_GRANT ledger rows.

Financial mutations must run in Serializable transactions. Debits use conditional wallet updates so concurrent spend attempts cannot both succeed.

Default conversion settings are a rate of 100 Gold per Diamond, a minimum of 1 and a maximum of 10000 Diamond per conversion, and a cap of 100000 Diamond per user per UTC day.

API Surface

Player cashier:

  • GET /api/v1/cashier/balance returns legacy balances plus wallets.
  • GET /api/v1/cashier/wallets returns wallet totals, locked amounts, available spend, and conversion limits.
  • GET /api/v1/cashier/currency-packages lists active Gold and Diamond packages.
  • GET /api/v1/cashier/convert/preview previews Diamond to Gold conversion using the active rate.
  • POST /api/v1/cashier/convert converts Diamond to Gold with an idempotency key.

Admin economy:

  • GET/PUT /api/v1/admin/economy/settings
  • GET/POST /api/v1/admin/economy/rates
  • GET/POST /api/v1/admin/economy/packages
  • PATCH/DELETE /api/v1/admin/economy/packages/:packageId
  • GET /api/v1/admin/economy/summary
  • GET /api/v1/admin/economy/reconciliation

Settlement Rules

  • Real-money tables debit and credit the table currency.
  • Virtual-chip tables still use User.virtualBalance and virtual transaction types.
  • Tournament USD is treated as Gold for wallet settlement. TICKETS remains a ticket entitlement. GOLD and DIAMOND settle against the matching wallet.
  • Coupons and bonuses with GOLD or DIAMOND credit the matching wallet. Legacy USD continues to map to Gold.
  • Withdrawal balance checks run against the Gold balance. goldWithdrawable is an operator setting returned to clients by GET /api/v1/cashier/wallets; the withdrawal service does not read it.
  • Diamond to Gold conversion is one-way. Gold to Diamond is intentionally unsupported.

Reconciliation Invariants

  • Every completed Gold or Diamond wallet mutation must have a same-transaction ledger row.
  • Conversion writes exactly two completed ledger rows, one CURRENCY_CONVERSION_OUT in Diamond and one CURRENCY_CONVERSION_IN in Gold.
  • balanceBefore and balanceAfter refer to the wallet currency on that transaction row.
  • Reconciliation compares wallet supply by currency against signed completed ledger deltas, with a tolerance of 0.01, and checks per-user continuity.
  • Decimal amounts remain Decimal(18,2). Conversion rates remain Decimal(18,8), and conversion payouts truncate toward zero at two decimal places.

Migration Verification

Use the migration script before cutover:

npm run db:generate
npx prisma migrate status
npx tsx scripts/migrate-dual-currency.ts --dry-run
npx tsx scripts/migrate-dual-currency.ts

After backfill, run economy reconciliation from the admin endpoint or service and verify Gold wallet totals match existing User.balance totals.

Try the live demo Pricing

Have questions about Poker Engine? We reply fast.