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 run → Tests 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#
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.
Related#
- 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