Mawu · Architecture

Abundantia: Distribution, Payout, the Economy Firewall & Anti-Fraud

A focused page within the Mawu Architecture documentation. The full map and every sibling page live in the Architecture hub.

6sections11 minread1diagram

On this page

Mawu is a creator republic, so the question that decides whether it survives is not "can a creator build a realm?" but "does a creator get paid correctly, and is the money they earn protected from the people trying to steal or launder it?" Abundantia is the answer: the platform-owned plane that distributes content-addressed realm and mod artifacts over a CDN, computes every creator's payout to the cent across three revenue rails, and then settles those payouts through the shared Aje payment substrate. Around that plane sit two non-negotiable defenses. The economy firewall keeps the real-money platform economy one-directional — money flows in to entitlements, and in-realm play-currency can never cash out to real money through a community operator. The anti-fraud layer makes the payout pool expensive to farm: only accounts that have actually spent real money count toward it, retention is weighted over raw concurrency, and a graph over account/realm/device/payment relationships flags the gold-farming and self-dealing topologies that every creator-payout program attracts.

This page is the engineering companion to the "Abundantia — Distribution and Payout Architecture" and "The Economy Firewall and Anti-Fraud" summaries in the orientation hub ../V7_ARCHITECTURE.md. Because Abundantia is where real money meets community-hosted realms, it sits exactly on the trust boundary: the catalog, the payout formula, and both firewalls are platform-controlled and never delegated to a realm process, while the realm keeps only its own non-fungible play economy. The settlement leg deliberately stops at a typed contract and hands execution to the shared Aje plane — the same seam V3's commerce service uses — so this document is careful to say which side of that seam each piece of code lives on.

What ships, honestly#

The Abundantia market service is real, tested TypeScript: apps/v7/abundantia-market-service/src/service.ts is a ~4.5 K-line module whose service descriptor (port 47301, service.ts:1030) advertises ~50 capabilities, and its service.test.ts suite runs 21 tests green (vitest runTests 21 passed, verified on box). The tests are not truthiness checks — they assert exact computed amounts: a 100-cent engagement pool splits to 33/17/16/34 across three creators and a declared dependency; a direct sale of 10_000 gross with 800 tax and 1_200 refund yields 13_000 net, 3_400 platform revenue, and a 4_200-cent creator route; a 6_999-bps direct split (one basis point under the 70% floor) is rejected outright. The payout formula, the verified-spend eligibility weighting, the currency firewall's graph-reachability check, the fraud-graph topology detector, the commerce-ledger isolation, and the content-addressed catalog are all domain-specific and exercised against known-correct values.

Three honest qualifications. First, like V3's commerce crate, Abundantia's job is the ledger, not the wire. evaluateAbundantiaPayoutFormula (service.ts:1741) computes splits, holds, and compliance gates and emits a typed AbundantiaAjeSettlementReceipt carrying deterministic aje:v7:abundantia:<hash> transfer IDs (createAjeTransfer, service.ts:3589) — it does not import @oshun/payments-bridge or broadcast a transaction. The real Aje plane (Ed25519 receipt signing, the cross-rail entitlement bus, the cold-spend co-signing queue) lives in libs/oshun/payments-bridge and is the substrate that actually executes settlement; Abundantia produces the contract it settles. Second, the Nàná economy crate (libs/v7/nana/src/lib.rs) is deliberately minimal today — it defines NanaCharacterRecord, NanaBalances (cash/bank/society in minor units), and validate() (which rejects negative balances), and its own doc-comment says it "intentionally starts small." The realm-scoped double-entry ledger, faucet/sink primitives, and auto-balancer described in the hub are architecture, not yet code in that crate. Third, the Year-1 catalog — which realms, which Collections, which featured drops — is a schedule the service executes, not an artifact in the repo. The machinery ships; the merchandise is operations.

Distribution — catalog, CDN, and one-click install#

Every Abundantia artifact is content-addressed, which is the single primitive the whole distribution layer is built on. computeAbundantiaContentHash (service.ts:1136) hashes the schema tag, the content bytes, and the sorted dependency-closure hashes into a sha256: id, so an artifact's identity changes the instant any byte of it or any of its dependencies changes. buildContentAddressedCdnObject (service.ts:1153) turns that hash into a CDN URL with an integrity field equal to the hash itself, so a swapped dependency is tamper-evident at fetch time. This is the same Nix-model content addressing the Ixchel resolver uses for its lock file — distribution and dependency resolution agree on one id.

publishCrossPlatformCatalogArtifact (service.ts:1174) is the publish gate. It requires a sha256:-addressed content hash, an attested rights claim, and a Sekhmet review of status approved before a record is produced, and it computes per-platform availability separately: PC publishes immediately for a trusted creator, while console targets (xbox/playstation/switch) stay requires_console_approval until a matching AbundantiaConsoleAuthorization with a passed cert checklist is supplied. A single account's installs then sync across platforms (installCatalogRecordForAccount, service.ts:1243) — the mod.io "one account, every device" model — and the in-game browser (queryInGameCatalogBrowser, service.ts:1370) refuses to surface anything not Sekhmet-cleared and visibly flags degraded realms rather than presenting them as healthy, consuming the gateway's per-realm health signals.

The headline distribution flow is atomic Collection install. installCollectionAtomically (service.ts:1574) resolves a root listing's full dependency closure, verifies every artifact in the closure is Sekhmet-cleared, computes a lock-file hash, and only then fetches — and the service.test.ts "aborts Collection install atomically before fetching an uncleared dependency" case proves the all-or-nothing property: one uncleared dependency anywhere in the closure fails the whole install with zero artifacts fetched, so a player never ends up with a half-installed Collection straddling a quarantined mod.

Payout — three rails, weighted to the cent#

Abundantia pays creators across three rails, all reconciled by one formula: the engagement pool (a platform-funded pot, default 40% of eligible net revenue, distributed by weighted engagement), direct sale/subscription (with a hard ≥70% creator floor), and dependency-revenue chains (a configured share routed to the creators of an artifact's declared dependencies). The pool size and the dependency-share percentage are governance-set inputs to the formula (engagementPoolCents, dependencyShareBasisPoints), amendable via Eunomia; the 70% floor is the one rate hard-coded as an invariant.

Who counts: verified-spend, retention-weighted eligibility#

Before a single cent enters the engagement pool, runPayoutEligibilityEval (service.ts:1951) decides which play counts. It is the anti-bot lever made concrete: a session is excluded if the account has never spent real money (verified_spend_required) or is a flagged CCU-inflation bot (ccu_inflation_bot). Surviving sessions are weighted by buildPayoutEligibilityWeightedSession (service.ts:3022): engagementMinutes × (retentionMultiplier + cohortBonus) / 10_000, where the retention multiplier adds 1_000 bps per retained active day (capped at 7 days) and new/reactivated payers get a 2_500-bps bonus. The test pins the math: a 40-minute session from a 4-day-retained new payer weights to exactly floor(40 × (14_000 + 2_500) / 10_000) = 66, while a 200-minute never-spent session and a 999-minute bot session both weight to zero — raw concurrency buys nothing.

The split formula#

evaluateAbundantiaPayoutFormula (service.ts:1741) is the heart of the page. For each direct-revenue line it computes net = gross − tax − refund, takes the platform cut, and carves the creator pool at creatorShareBasisPoints — rejecting any line below 7_000 (validatePayoutFormulaInput, service.ts:3311). It then allocates the engagement pool across eligible signals by weight (allocateCentsByWeight, a largest-remainder method so the cents always sum exactly), and runs splitCreatorAndDependencyRevenue (service.ts:3378) to carve each primary creator's amount into the creator's own route plus pro-rata dependency-revenue routes to declared dependencies. The worked test case exercises all of it at once: Ada's 10_000-cent sale (net 8_000, 70% share, 25% dependency carve) yields a 4_200 creator route and a 1_400 dependency route to Cy; Bea's subscription, still inside its refund window, is computed but fully held (holdReason: refund_window_open). Totals reconcile to creatorEarningsCents: 9_700, platformRevenueCents: 3_400, heldCents: 4_551.

Compliance gates and the held-vs-cashable distinction#

A computed route is not a cashable payout. buildCreatorPayoutSettlements (service.ts:3448) walks each creator's routes through complianceGateReasons (service.ts:3560): missing KYC, an incomplete tax interview, or an unverified payout method each hold the entire payable amount, as do a reserve hold and an unmet minimum-payout threshold. In the worked case Ada clears (cashout 3_733 after a 500 reserve hold), Cy clears (1_416), but Bea is gated payout_method_required and Dia is gated kyc_required, tax_interview_required — both cash out zero. The settlement status reflects this honestly: payoutSettlementStatus (service.ts:3662) returns partially_held whenever anything is held, rejected when validation fails (the sub-floor case produces rejected with no transfers at all), and settled only when every cent is clear.

The dependency lock-file path and the Aje seam#

computeDependencyRevenueSharesFromLockFile (service.ts:1805) derives the dependency set straight from a realm's resolved lock file — the same content-addressed closure the Ixchel resolver pins — and allocates the configured dependency share across it by weight, so the people whose mods a realm depends on are paid by construction from the dependency graph, not by manual declaration. Every clear settlement becomes an AbundantiaAjeTransfer with a deterministic aje:v7:abundantia:<24-hex> id and a per-rail breakdown, wrapped in an AbundantiaAjeSettlementReceipt carrying a SHA-256 ledger hash. That receipt is the typed contract handed to the shared Aje plane — @oshun/payments-bridge — whose entitlement bus normalizes four fiat processors and fourteen crypto rails into one payment.invoice.settled envelope (entitlement-bus/topics.ts) and whose receipt-signer signs each payout with Ed25519. Abundantia decides who gets how much; the bridge decides how the money moves.

The economy firewall#

flowchart TD subgraph Realm[REALM PLANE — non-fungible play economy] PLAY[realm_play_currency / inventory] OPW[realm_operator_wallet] end subgraph Plat[PLATFORM PLANE — real money, one-directional] BUY[Purchase: Stripe / Adyen / PayPal / 14 crypto rails] ENT[platform_entitlement] POOL[Engagement pool · 40% net] DIRECT[Direct sale / subscription · ≥70% floor] end BUY -->|real_money_purchase| ENT ENT -->|realm_currency_grant| PLAY PLAY -. operator_redemption_request .-> OPW OPW -. cash-out .-x PAYOUT ELIG[runPayoutEligibilityEval<br/>verified-spend + retention] --> POOL FRAUD[runFraudGraphEval<br/>self-dealing + RMT topology] -. blocks .-> ELIG POOL --> FORMULA DIRECT --> FORMULA FORMULA[evaluateAbundantiaPayoutFormula<br/>split · dependency carve · gates] --> RCPT RCPT[AjeSettlementReceipt<br/>aje:v7:abundantia:… transfers] -->|typed seam| AJE[shared @oshun/payments-bridge<br/>Ed25519 · entitlement bus] AJE --> PAYOUT[Creator payout account]

The firewall has two halves, both enforced in code.

The currency firewall — realm value cannot reach real money#

runCurrencyFirewallEval (service.ts:1843) classifies every economic node as realm-value (realm_play_currency, realm_inventory_item, realm_operator_wallet) or real-money (platform_real_money_account, platform_entitlement, creator_payout_account) and proves no value can flow from the first class to the second. It does this two ways. First, a per-transfer guard (blockedCurrencyTransfer, service.ts:3967) blocks any realm-value → real-money edge and any operator_redemption_request originating from realm value. Second — and this is the part a naive check misses — findCurrencyFirewallLeaks (service.ts:4005) runs a depth-first reachability search from every realm-value node over the surviving (non-blocked) transfers, and fails the eval if any path reaches a real-money node, catching laundering that hops through intermediaries. The test proves the intent: an operator may sweep play-currency within their own realm (allowed), but the moment they try to operator_redemption_request from the operator wallet to an Aje payout account, that transfer is blocked, no leaking path survives, and the eval is clean. This is what keeps casinos and loot mechanics out of gambling regulation — there is no real-money cash-out, by construction.

Commerce-ledger isolation and the no-paywall-on-free rule#

runRealMoneyCommerceSurfaceEval (service.ts:1872) keeps the platform real-money ledger and the realm play-currency ledger physically separate, hashing each independently and refusing to mix entries, and it composes the currency firewall so isolation and non-convertibility are checked together. It also enforces a creator-protection rule the test names directly: a release that shipped free cannot later be paywalled (freeReleasePaywallRejected), so a creator can't bait-and-switch a community that already adopted their free content.

Anti-fraud#

Payout fraud is the predictable consequence of paying for engagement, and Abundantia attacks it at two layers. The eligibility layer above is the first and strongest lever — farms can't cheaply fake verified real-money spend, and weighting retention over concurrency kills CCU-inflation bots before they reach the pool. The second layer is the fraud graph.

The fraud graph — self-dealing and gold-farming topology#

runFraudGraphEval (service.ts:2000) builds indexes over account↔realm↔device↔payment edges, computes a degree-centrality score per account, and runs two detectors. findSelfDealingFraudGraphFlags (service.ts:2275) catches a creator farming their own realm: a realm whose owner and a cluster of never-spent players share a device or payment instrument is flagged block. findRmtFraudGraphFlags (service.ts:2330) catches the gold-farming topology — a recipient account receiving one-directional "free money" inflows from ≥3 source accounts, ≥2 of them never-spent, exceeding a 10_000-unit threshold, with anomalous centrality (degree ≥5). The thresholds are named constants (service.ts:1024-1028), and the test drives a synthetic 15-node graph to exactly two flags: the self-deal ring (creator + two never-spent alts on a shared console and card, neverSpentAccountCount: 2) and the RMT broker (degree 6, four never-spent farm accounts wiring 4_000 units each for 16_000 total inflow over four transfer edges). The detector reports the central account, the implicated devices and payment instruments, and the specific transfer edges — actionable evidence, not a bare score.

Adjacent to fraud, runRightsProvenanceEval (service.ts:1912) blocks the re-upload of a known-infringing content hash, walks the asset attribution chain, preserves fork-revenue links, and routes any DMCA claim to Themis — so a creator who forks another's work carries the revenue link forward and a rights-holder's takedown lands in the adjudication path rather than a support inbox.

What's real vs. spec#

The graph-topology detectors above are real, tested code — degree centrality, one-directional-flow detection, never-spent and shared-device clustering. What the hub additionally describes — an ML classifier scoring engagement quality from retention-predictive actions (the random-forest / graph-model literature) — is spec, not yet implemented: the current eligibility weighting is the deterministic retention/cohort formula, and the graph detectors are deterministic heuristics, not a trained model. The honest boundary is that the structural anti-fraud (verified-spend gating, topology flags) ships and is exercised; the learned engagement-quality model is future work.

  • Nàná Data Model and Pheme Voice — the realm-scoped character and play-economy aggregate whose balances the currency firewall keeps non-fungible with real money
  • Eunomia Governance and the Ori Bridge — the governance process that amends the 40% pool, dependency-share, and floor parameters, and the passport that carries creator earnings across realms
  • Oshun Domain Libraries — where the shared Aje plane @oshun/payments-bridge (Ed25519 receipts, the cross-rail entitlement bus, cold-spend settlement) that executes Abundantia's payouts lives
  • The orientation hub ../V7_ARCHITECTURE.md