Appearance
Real vs Play Money Split — two coexisting economies, per-table
Accepted + shipped 2026-07-19 · plan real-play-money-split (amends agent-pmf-beta D4)
Goal
One deploy hosts both economies: every table is play (playSKPK) or real (SKPK/tSKPK), users always see both balances, and real money is a switch-flip away — not a redeploy.
The decision
In the context of the beta running playSKPK-only with one chip per deploy (CHIP_TOKEN), facing a launch that needs the real economy ready-to-flip (a backing mainnet can actually ship, and real tables with the same fund-safety liveness paths as play tables), we decided for:
tables.money_kind(play | real, typed column, defaultplay) as the fund-path discriminator — the cage's singlechipMint(kind)seam resolves the mint FROM THE ROW; the deploy-wideCHIP_TOKENsurvives only as the kind-less default, folded byeffectiveMoneyKind(a legacy single-economytskpkdeploy transacts tSKPK regardless of its rows).skpk_enabled— agame_settingsglobal (fail-closed OFF, per-environment) gating real-table create + fund. Withdraw/settle/escape are never gated. Staging ON; prod OFF until launch.- Canonical-USDC backing — a
numero-tokenupgrade:init_real_backing(usdc_mint)binds an EXTERNAL canonical USDC mint (Circle devnet USDC on staging; mainnet USDC later) + areal_reserveof that mint;wrap/unwrapre-bound to it (1:1, substitution + foreign destination revert). The self-minted SKUSDC is retired from the chip path. - One custody deployment per economy — a byte-identical
numero-custodyunder a fresh program id (9azX3QdAmwfqP4JEkvGVs5e4pnYxaGusyyg4XC8jkE93; cargo featurereal-economyswaps onlydeclare_id; built byonchain/scripts/build-real-custody.sh). The cage routes custody by the entry's stamped kind, resolved from the OFF-CHAIN row BEFORE any on-chain read (the entry PDA derives from the program id). - Two-balance UI — header shows playSKPK + the real chip (money green,
--money); lobby play/real filter + real-row styling; the cage teller is the USDC⇄tSKPK window when the switch is on (flag read via an UNGATED/api/economy/flags— production must be able to read it at launch).
And against: a second deploy per economy (splits the lobby/userbase), keeping SKUSDC backing (mainnet structurally cannot ship it), per-mint custody vault PDAs (a riskier change to the fund-safety-hardest program than the established fresh-ID pattern), money-kind in settings JSONB (fund discriminators get typed columns), and deferring real-table custody (escape is a fund-safety liveness path).
Load-bearing invariants
- The mint follows the TABLE, never the caller — no request field can redirect a deposit's economy; a mismatched funding attempt reverts whole (the table PDA never even inits).
- The switch gates money IN, never money OUT.
- A kind is validated, never defaulted — a typo'd
moneyKindrefuses (invalid_money_kind). - Custody kind resolves off-chain-first —
findCustodyEntryByOnchainId(newest solana row; entry ids reset per cage boot until the durable sequence) picks the deployment before the PDA read.
Verification
Onchain 34/34 (incl. adversarial backing conservation); dual-economy cage e2e 6/6 (both economies in one deploy with provably different vault mints; settle + withdraw exits in tSKPK; the fund gate; real escape → real custody → tSKPK claim); integration 5/5 (create gates); RTL (header/lobby/teller); poker 732 passing; typecheck 36/36. Devnet brought up + verified on-chain the same day.
Links
- Changelog: 2026-07-19 — real vs play money split
- Guide: smart contracts (wrap backing, dual custody)
- Full planning docs:
.indusk/planning/real-play-money-split/(workbench)