Internal engineering report · TotoChain · 2026-08-31

TotoChain 30-Month Economic Simulation

What run 20260829-224013 did, what it found, and the causal chain behind the dead referral economy — every number below is read from the run's own telemetry, state exports, and source code, not estimated.

30 months · 840 days · 24,192,000 blocks 2 cycle boundaries (end m13, end m26) 10,000 agents · Base preset (75% passive) 11 genesis validators scenario realistic · production runtime, real dispatch total wall-clock ≈ 9.2 h
Fee-blocked intents
524,819
≈ 20× all successful actions
Zero-spendable accounts
78.4%
7,853 of 10,011 at day 840
Referral extrinsics
0
17,012 intents emitted, all skipped
Single-piece unlocks
97.5%
42,935 of 44,046 unlocks
Referenda rejected
110 / 112
98.2% of decided polls
Top-10 share of free TOTO
88.9%
all 11 validators rank above every user
Context

What this simulation is

It does not model TotoChain — it runs TotoChain. The production runtime executes under sp_io::TestExternalities: every agent action is a real signed extrinsic through RuntimeCall::dispatch, every fee is charged by the production PotentialCurrencyAdapter, and every on_initialize / on_finalize / on_idle hook runs in block order — 28,800 one-second blocks per simulated day.

What is modelled is the population: who acts, when, and how often. Ten thousand agents with research-calibrated personas, heavy-tailed activity weights, constant-hazard churn with resurrection, Bass-diffusion adoption, and a friendship overlay carrying an embedded paid-referral forest. The question the whole apparatus answers: does a chain launched from empty genesis ever reach a state where its own economy and governance are usable?

Two documented fidelity gaps — keep them in mind when reading results

No transaction pool / CheckWeight. A day's activity lands in one block, so the run can say nothing about congestion or block capacity.

on_idle gets Weight::MAX. The deferred-reward sweeper runs at its full 1,000-edge/block cap instead of a real node's leftover weight. Queue behaviour (filling at boundaries, draining after) is real; per-block cost is a harness artifact. No economic quantity is affected.

Method

What this run did

30 months means two full 13-month supply cycles — no previous run had crossed a second boundary. New in this run: weekly per-account state exports from month 3 (4× the resolution of earlier runs), which is what finally separated composition effects from real balance decay, and localized the famous "month-6 collapse" to a 4-day window.

Result 1 · the root cause

Why the referral economy never forms — and why almost every fNFT has one fraction

One insolvency check plus one skipped intent path kill the entire assembly economy. Everything downstream — single-piece collections, the dead trade market, floor-rate unlock payouts, and ultimately the fee death spiral — traces to this chain:

The three causes, pinned to code

1 · Sponsor insolvency, off by exactly one ED. Sponsoring a referral stakes 32×ED and requires free ≥ stake + ED = 33×ED (can_fund_referral, simulation/tests/economic.rs:1557). A freshly onboarded user is seeded with exactly 32×ED. The graph model picks a social inviter at the configured paid-regime rate (R ≈ 0.95), but the affordability filter rejects ~81% of them, and onboarding falls back to a genesis validator as sponsor, round-robin (economic.rs:1637). Net realized user-referral rate: 1,635 / 10,000 = 0.16.

2 · Mid-life invitations never dispatch. Agents emitted 17,012 Intent::Refer over 30 months; the driver at economic.rs:4275 skips every one (sentinel invitee == u32::MAX, commented "until an explicit invite driver exists" — never written). exec_refer = 0 for the whole run, with zero failures: the extrinsic is never attempted, not attempted-and-failing.

3 · A sim gate keys on the wrong counter. market_possible checks refer_ok > 0 (economic.rs:3118, :3997) — but the piece multiplier reads DescendantCount (full active subtree, pallets/referral/src/lib.rs:619pallets/fractional-nft/src/lib.rs:563), which is non-zero for validators and 340 users. So the assembly-market sweep never ran even for the multi-piece collections that do exist — the run log shows "skipped (no referral yet)" on all 840 days.

Collections minted vs unlocked, by size
Log scale. 47,172 of 51,408 collections (91.8%) are minted single-piece. Nothing above 5 pieces was ever unlocked — the 1,219 collections of 6+ pieces (up to 603) are permanently stranded: their pieces scatter through the block-reward channel and the dead market means they can never be reassembled.
mintedunlocked
Table view
The question that started this report: "all NFTs split into 1–3 fractions — users have no active referrals. Why?"

The premise needs one correction: the distribution isn't capped at 3 — the report chart is visually crushed by the 1-piece bar. Of the 8,297 collections still live at day 840: 62.3% have 1 piece, 13.5% have 2, 3.2% have 3, and a real tail runs to 603 pieces. The 41 collections of 100+ pieces are all validator-minted (each validator sponsors ~760 users, and mint count = its active descendant count + 1) and hold 39.3% of all live pieces.

But the instinct was right where it matters: ~97% of users have zero active referrals — not because activation fails (all 10,000 onboarding referrals activated successfully) but because the referral edges nearly all point at validators (cause 1) and no new edges are ever created mid-life (cause 2). The 2–3-piece collections belong to the 340 users who managed to refer one or a few people.

Referral funnel, whole run

stagecount
users onboarded10,000
on-chain referral edges created & activated10,000
… referred by another user1,635 (16.4%)
… sponsored by a validator (fallback)8,365 (83.6%)
mid-life Refer intents emitted17,012
… dispatched on-chain0
forest mean depth / max depth0.37 / 9

Piece accounting at day 840 (reconciles exactly)

quantitypieces
minted via submissions84,294
wildcard fills during unlock935
burned by unlocks45,191
live (users 40,016 + pool 22)40,038
check: 45,191 + 40,038= 85,229 ✓

Wildcards first appear at month 17 and stay marginal (2.1% of claims) — with 1-piece collections, no gap ever needs filling.

Result 2 · the consequence

The fee death spiral

Fees are paid from locked potential; potential decays by 1/28 daily; emission halves monthly and the cycle budget runs dry early. With the only conversion path paying the 0.0100% floor, users cannot rebuild spendable balance — so the chain progressively locks its own users out.

Share of intents fee-blocked, per month
Blocked ÷ (blocked + landed) among that month's intents. The dip at month 14 is the cycle-boundary refill — relief lasts one month, then the climb resumes. Vertical hairlines mark the cycle boundaries.
Table view
Intents landed vs fee-blocked, per month
Log scale. By the final months, ~35,000 intents/month are blocked against a few hundred landed — a standing 20:1 ratio of intended-to-realized activity. Every economic series in this report therefore understates demand.
landedfee-blocked
Table view
Pool inflow, 30 months
5.53B
decay + fees into reward pool
Pool outflow (payouts)
320M
5.8% of inflow ever comes back
Every unlock paid
0.0100%
the Hill-curve floor, all 44,046 times
Total fees charged
184.6
TOTO across the whole run
Result 3 · tokenomics

Supply and the cycle boundaries

Emission halves monthly inside a 13-month cycle, so the back half of every cycle mints almost nothing while the daily 1/28 decay keeps draining accounts into the pool. At each boundary the cycle burn destroys the reward pool's burnable surplus — and sets exactly that amount as the next cycle's mint budget (CycleSupply := burned). It is a closed re-mint loop over the pool account; user balances are never touched by the boundary — they are already at dust from the decay.

Total issuance, reward pool, and unlock fund
The two cliffs are the cycle burns: 1.936B destroyed at the end of month 13, 1.794B at the end of month 26 (3.73B cumulative). The pool collapses 1.94B → 350M and 1.80B → 324M; the unlock fund — the only source of user spendable — stays under 10% of the pool at all times.
total issuancereward poolunlock fund
Table view
Potential created vs recycled, per month
Log scale. Creation is a clean halving staircase (1.05B → 525M → 262M …) that reaches sub-million levels by month 11 and restarts at each cycle; in months 12–13 and 25–26 the block-reward mint is effectively zero. Recycling (daily 1/28 decay + fees into the pool) declines only as balances drain — from the second month of every cycle (m2, m15, m28) it exceeds creation, and from that point the population as a whole can only lose potential.
created (minted)recycled (decay + fees)
Table view
The design finding: creation and destruction of potential are unbalanced — in rate and in shape

Rate. Creation halves every month and is effectively zero by month 10–13 of each cycle; destruction never slows — every account loses potential/28 daily (~3.6%/day, burn.rs:119) plus fees on top. The chart above shows the crossover: from the second month of every cycle, more potential is recycled than created. From then on the population drains regardless of behaviour — 62.7% of accounts were at zero by day 364, before any boundary ran.

Shape. Creation is concentrated: the author mint goes to block producers and the descendant sweep flows down referral subtrees, so inflow depends on authorship and tree position. Destruction is uniform: every holder bleeds the same proportional 1/28 share. An ordinary user — no authorship, thin tree — has inflow ≈ 0 against outflow 3.6%/day: their equilibrium balance is zero by construction. This survives every sim-harness fix; it is rate math, not behaviour.

The aggregate loop is already symmetric (burned = next cycle's budget, below). The imbalance lives at the individual-account level and in time — so the fix is balancing the two flows (tie the decay rate to the emission schedule, give emission a per-account floor, or widen the 90/10 RecycleShare split), not making them literally one flow.

What cycle_preserved = 0 actually means (clarified)

The event field preserved is the pool's core (non-potential-sourced) surplus left above the 1,000-TOTO genesis floor after the burn (pallets/potential/src/lib.rs:829) — not user value. It reads 0 because everything above the floor was potential-sourced: the expected value for this run. The destroyed amount is not lost either — cycle-2's mint budget is exactly boundary-1's burn (1,936,381,861) and cycle-3's exactly boundary-2's (1,793,638,861). What empties users is the daily potential/28 decay plus the halving emission — 62.7% of accounts were at zero the day before the boundary ran.

I2b: a real accounting event at both boundaries

The burn clamps to physical surplus with margin exactly 0, then zeroes the whole burnable tracker — silently consuming tracked-but-unbacked units (7.94×10²⁶ at boundary 1, 9.59×10²⁵ at boundary 2; the accumulated RP-2 drift). Deterministic, shrinking as the chain matures, and currently unreported by anything except this invariant.

fNFT block reward: dead on 99.7% of blocks

BlockRewardShortfall fired 24,128,464 times across 24.19M blocks; distributions succeeded on 0.35% of blocks over the run and 0.08% in the final month. The author-reward channel that is supposed to seed collections contributes almost nothing — and because a shortfall reports through events rather than dispatch errors, every invariant stays green while the mechanism is dead. Root cause is the same early budget exhaustion shown above: schedule calibration, not tokenomics.

Result 4 · behaviour

The economy winds down instead of reaching steady state

Adoption saturates at the 10,000 carrying capacity by month 22 — but activity peaks at months 7–10 and then declines monotonically, because the population that arrived can progressively no longer afford to act.

New adopters per month
Bass diffusion: peak 1,336/month at month 9, zero from month 23.
Table view
Cumulative population
Saturates at the 10,000 carrying capacity at month 22.
Table view
Executed actions per month, by path
Log scale; months with zero activity leave gaps. Every path peaks by month 9–12 and declines through month 30 — the winding-down is the fee spiral seen from the activity side. Trades never exceed 116/month; referral activations track adoption and end with it.
NFT submits handshakes sells votes unlocks trades
Table view

Whole-run totals: 51,408 NFT submissions (21,175 validator + 30,233 user) · 44,046 unlocks (41,624 validator + 2,422 user) · 15,650 handshakes initiated, 14,326 completed · 13,973 sells · 10,000 activations · 7,346 votes · 2,432 attestations · 818 trades — and exactly 1 DID created in 30 months.

Result 5 · power

Governance runs, but decides nothing; wealth never leaves the validators

Governance

  • 113 referenda spawned (≈4/month from m3); 111 closed; 110 of 112 decided were rejected (98.2%) — zero passed.
  • Deposits (100,100 spendable) affordable from month 3 — but effectively only to validators; the circulating float (17.4M TOTO) is 173× one deposit.
  • Vote-reputation degenerates: with every poll rejected, alignment is only achievable by voting no. Outcome: 310 positive / 8,434 zero / 1,267 negative — 96.9% of accounts scored zero or worse.

Concentration

  • The top 11 holders are exactly the 11 genesis validators (28.4M–43.4M free each). The richest user holds 16,041 — a 1,770× gap to the poorest validator.
  • Top-10 share of free TOTO: 88.9%; validators hold 92.7% of all potential.
  • Validator set is closed: 189 candidates registered and ground reputation for 28 months; the best reached 28% of the displacement threshold. Zero displacements in 2.5 years.
  • Technical committee caps at 100 seats from month 8 (253 candidates by m30, zero registration failures).
Result 6 · the harness itself

Simulation engineering: the cost mechanism, found after eight refuted hypotheses

Wall-clock cost tracks exactly one quantity: potential-lock writes from the deferred-reward sweeper in on_idle — at a near-constant 27.5–30.6 ns per lock across five regime transitions. No population statistic ever predicted it; the event registry had been counting it all along.

Wall-clock per simulated month
Log scale. The two spikes are the post-boundary months: 5.0 h (m14) and 3.3 h (m27) — recovery is complete within one month, both times.
Table view
Non-mint lock writes per month
Log scale; zero-months leave gaps. The same shape as cost: 597M locks at m14, 437M at m27 — the sweeper saturating ~74% of its 1,000-edge/block cap.
Table view

What the pairing establishes

  • m14: 597,344,735 excess locks · 17,948 s → 30.6 ns/lock. m27: 437,287,335 · 12,043 s → 27.5 ns/lock. Within 10% across spikes ten months apart.
  • The months-3–5 expense (7.7–19.4M locks) and the famous "day 136–140 collapse" (47×) are the same phenomenon: the sweep queue filling during population growth and draining in a 4-day event — misread as a month-boundary effect for two runs because of monthly sampling.
  • The post-boundary queue scales with the preceding cycle's activity, so it shrinks as the chain matures (cycle-2 spike 27% smaller on a saturated population). Same direction for the I2b drift (7.9×10²⁶ → 9.6×10²⁵).
  • Standing caveat: with Weight::MAX the harness concentrates this work; a real node spreads the identical queue across many more blocks. Read it as sustained on_idle saturation after each boundary, not as a 5-hour block.
Open sim defects (Serki's backlog, confirmed this run)

Super-linear late-run cost: per-day wall-clock rose 22.8 → 93.0 s across the final 21 days at a fixed population — every phase scaling together points at per-day iteration over grow-only structures (51,409 collections; 160,156 overlay edges). Caps feasible run length well before 60 months.

Blind telemetry: trade_sites/trade_pairs only count the bootstrap sweep (818 real trades invisible to them); wildcards_live never catches the intra-block lifecycle. Wrong gate: market_possible keyed on refer_ok (see Result 1).

Register

All findings, both chapters

RULE-CHECKED findings are deterministic rules over the run JSON (cannot hallucinate); OPEN-ENDED findings come from an independent model reading the telemetry blind. Where they agree, two methods agree.

severityfindingchapterfirst fired
CRITICALReferral graph never forms — exec_refer = 0 all 30 months; root cause of the findings belowopen-endedm06
CRITICALEvery unlock is (near-)single-piece; assembly economy structurally unreachablerule-checkedm01
CRITICALTrade market never executes a single market trade in 840 days (candidate set empty by construction)open-endedm30 framing
CRITICALfNFT block reward shortfall on ~99.7% of blocks; reward channel dead while invariants stay greenbothm01
CRITICALCreation/destruction mismatch: emission halves monthly and concentrates at authors/subtrees, while decay runs constant (1/28-daily) and uniform — recycling exceeds creation from month 2 of every cycle, so ordinary users' equilibrium potential is zero (see Supply section)synthesis (this report)m02
CRITICALSpendable TOTO zero for 78.4% of accounts; top-10 hold 88.9% of free balanceopen-endedm01
CRITICALReward-sweep queue saturates on_idle after each boundary (741 lock-writes/block vs cap 1,000)rule-checkedm14
CRITICALSim: behaviour-phase cost grows 4× in 21 days at fixed population (grow-only structure iteration)open-endedm30
WARNINGFee blocking: 524,819 attempts, 20× successful actions, monotonically growingbothm12
WARNINGThe cycle burn preserved nothing — cycle_preserved = 0 at both boundaries (clarified: preserved = pool core surplus, not user value — see Supply section)rule-checkedm14
WARNINGVote reputation degenerates: 96.9% zero/negative; 98.2% of referenda rejectedbothm06
WARNINGForest never branches: 84% cold signups, mean depth 0.37 — tier-2 lineage multiplier removedrule-checkedm03
WARNINGReferral-activation reward shortfall on 538 of 10,000 activations (frozen once adoption ended)rule-checkedm03
WARNINGPotentialOperationFailed fired once (session-boundary op; events-only reporting)rule-checkedm03
WARNINGSim: trade_sites counter blind to per-agent trades (telemetry defect, not economics)rule-checkedm03
INFORP-2 burnable-counter drift: peaks 78,355 (m13) / 8,589 (m26), reset by each cycle burn; clamp keeps supply saferule-checkedm01 (downgraded m30)
INFOUnlock fund is 9.1% of pool; conversions run at the floor; outflow 5.8% of inflowopen-endedm30
INFOSim throughput spread 392.9× (45 blk/s at m14 vs 17,652 at m1) — describes the harness, not the chainrule-checkedm03
What to do with this

Open questions & recommended next steps, ranked

Fix the sponsor-insolvency off-by-ED and write the invite driver — then rerun.

Seed onboarded users with 33×ED+ (or lower the sponsor requirement) and resolve Refer intents to concrete overlay neighbours instead of skipping them (economic.rs:1637, :4275). This is the one change that makes the design's core loop — referrals → multi-piece collections → assembly market → Hill payouts — observable for the first time. Until then, the simulation has never actually tested it.

Fix the market_possible gate.

Replace refer_ok > 0 with a DescendantCount-based check (economic.rs:3118, :3997). Cheap, and provably wrong today — multi-piece collections existed from month 3 while the market sweep skipped all 840 days.

Decide how creation and destruction of potential should relate — today the two flows are unbalanced in rate and in distribution.

This is the central protocol-level finding (the referral failures above are harness defects). Rate: emission halves monthly to ~zero by month 10–13 while the 1/28-daily decay never slows — recycling exceeds creation from the second month of every cycle. Distribution: creation flows to block authors and their referral subtrees; destruction hits every holder uniformly — so a non-authoring, tree-poor user's equilibrium potential is zero by construction. Fixing the sim harness improves spendable income but does not touch this. Options: tie the decay rate to the emission schedule (halve them together), give emission a per-account floor that survives the halving tail, or widen the RecycleShare 90/10 split toward the unlock fund. The aggregate cycle loop needs no change — it is already closed.

Cycle boundary — resolved mechanically; two narrower design questions remain.

Resolved at code level: the boundary touches only the pool account and re-mints the burned amount as the next cycle's budget (CycleSupply := burned, lib.rs:822) — the aggregate loop is intended and closed. The remaining design questions are narrower: (a) individual balances are not preserved — a user's share of the next cycle depends on block authorship and referral-tree position, not prior holdings; is that reset intended? (b) the loop shrinks ~7.5% per cycle (2.10B → 1.936B → 1.794B) — exactly the value converted to permanent spendable; confirm that deflation profile is the plan. What zeroes ~63% of users by cycle end is not the boundary but the creation/destruction imbalance — the previous question.

Recalibrate the emission schedule (or accept dead reward months).

The cycle budget exhausts by month 10–12, leaving the block-author reward channel dead on 99.7%+ of blocks and firing 24.1M shortfall events that nothing surfaces. If the halving cadence is fixed, the per-month fractions need rebalancing against the cycle cap.

Decide whether RP-2 drift should be reconciled before consume_burnable.

Both boundaries silently consumed unbacked burnable (margin exactly 0). Bounded and shrinking, but unbounded in code — only the I2b advisory reports it.

Re-examine the unlock Hill parameters at realistic magnitudes.

Half-saturation at 350 pieces / 7,000 referrals against an observed reality of 1–5-piece unlocks means every conversion pays the floor and holder timing behaviour is unobservable. Either the curve or the mint economics needs to move.

Investigate governance's 98.2% rejection rate.

Agents vote 65% aye by configuration, yet 110 of 112 referenda failed — likely a support/approval-threshold interaction. Until referenda can pass, vote-reputation cannot differentiate voters and the penalty branch is unreachable.

Check the KILT path: exactly 1 DID in 30 months.

2,432 attestations ran on a fixed schedule, but DID creation happened once. Either a sim gap or a dead feature path.

Correct PAPER.md §3.5 / open issue #7.

The paper states "no referral is ever activated" and "every collection is single-piece" — both false (10,000 activations; a multi-piece tail to 603). It conflates the exec_refer extrinsic counter with on-chain activation state. The corrected causal story is Result 1 above, fully evidenced.

Fix the sim's late-run super-linear cost before attempting longer runs.

Per-day cost quadrupled over the final three weeks at a fixed population. Profile the per-day iteration over collections/overlay edges; 60-month runs are infeasible until this is bounded.