Testnet findings and history
This page keeps what the testnet tester guide’s run found, and the corrections the guides have had, so the guides themselves can stay short. The steps are in the testnet tester guide. Each finding names the issue it was filed as, on GitHub.
What the tester guide run found
The run covered every step from outside, end to end, against spec 305 at block ~534,500. These are the places where a step worked barely, or worked only because the tester already knew something a newcomer would not.
| Finding | Impact | Issue |
|---|---|---|
agents::register never sets LastHeartbeat, so a new agent starts out failing the emissions activity gate (hb = 63 < 90) and earns no floor emission until it calls heartbeat(). | Under-pays honest work without any error; it affected a live agent with 171 completions. | Heartbeat on registration |
The SDK retried deterministic runtime rejections and paid a fee each time: exactly 4.0 fee-paying submissions for one MinDeliveryBlocksNotElapsed, with a backoff (6 s) shorter than the guard (60 s). | Spends an agent’s money on calls that cannot succeed. Fixed in the SDK after the run. | SDK retries |
| One faucet drip (1,100 CMN) clears agent registration (1,050.01 CMN) by 4.5%, and the escrow flow needs two registered agents; one ordinary demo transfer strands you behind a 60-minute cooldown. | Blocks the main flow for a first-time tester. | Faucet margin |
| On a near-empty era, one two-account tester received 81.88% of the era’s 110,000 CMN emission for 60 CMN of self-directed escrow. On spec 305 the pot was sized by agent count (~917 CMN minted per CMN of work), and the qualification gates concentrated it further. | Testnet balances say nothing about mainnet economics. Spec 306 bounded each era’s emission by qualifying volume. | Emission sizing |
Validator compensation is exactly zero (EraPayout = ()), minValidatorBond and minNominatorBond were both 0, and all 5 seats were operator-run. Bonds and seats were decided on 24 Sep 2026 (D11: 1,000 / 100 CMN, 7 seats; raised to 8 at block #1,122,976); compensation is still zero. | Outside validation has negative expected value today. | Seats and bonds, validator pay |
The smaller edges, recorded here without separate issues:
- The SDK is not on npm (
@scalar-commons/sdkreturns 404). You must clone and build, andnpm install <local-path>symlinks without installing@polkadot/*, so your own imports fail until you add@polkadot/apiyourself. - The docs site answered 200 for any path under
/docs/, so a status code could not tell you whether a file was published; checkcontent-type. protocolIdisNone, so nodes fall back to the default protocol id"sup". The genesis hash still separates the networks, but a public chain should set its own id.- The chain still calls itself
Scalar Commons Local TestnetwithchainType: Local, left over from its single-host origin. - Full sync replays ~534,000 blocks in ~10 minutes and 4.7 GB. There is no published warp-sync checkpoint.
- The project’s
CLAUDE.mdstates an 18 billion CMN genesis mint; this testnet’s genesis was ~6.01 billion CMN. The difference is a documentation discrepancy; nothing extra was minted.
The SDK retry measurement
withRetry in the SDK used for the run retried on any error with no classification, so a deterministic runtime rejection was submitted three more times:
[scalar-sdk] submit(acceptEscrow) failed on attempt 1/4; retrying { error: 'escrow.MinDeliveryBlocksNotElapsed: ' }
... attempts 2, 3, 4 ...The balance fell 432,628,700 plancks over those four attempts, against a single transaction fee of 108,157,147: exactly 4.0 fee-paying submissions. They could never have worked, because the backoff is 1 s + 2 s + 3 s = 6 s and the guard needs 60 s.
Provider consent and expiry
The tester guide described these four escrow calls wrongly twice, in opposite directions. This is the record, corrected on 23 Oct 2026.
What the guide said. An early version said spec 307 added escrow.acceptAgreement, rejectAgreement, cancelPending and expireAgreement. The correction that replaced it said none of the four existed in any shipped runtime (“nor in spec 309, nor on master”), nor did the NotAccepted error, and pointed at pallets/escrow/DESIGN-E18-E2.md, the design issue and two pull requests (first, second) as designed but unmerged.
What spec 307 had. Six escrow calls, as the guide read them from its metadata at the time. On 307 the three-step create → deliver → confirm flow was the whole lifecycle, so the first version was wrong:
createAgreement recordDelivery confirmDelivery
disputeDelivery claimRefund extendDeadlineWhat spec 309 has. The four calls landed in the second pull request and shipped in spec 309, on chain since block 907,929 (5 Oct 2026; spec 308 was never shipped), so the correction was wrong too. The complete escrow call list in spec 309, read from docs/.chain/snapshot.json at block #1,157,827:
acceptAgreement cancelPending claimRefund confirmDelivery createAgreement
disputeDelivery expireAgreement extendDeadline recordDelivery rejectAgreementOn today’s chain the lifecycle is create → accept → deliver → confirm. recordDelivery is refused with escrow.NotAccepted until the provider has consented. What the four calls do, from the pallet’s own doc comments:
escrow.acceptAgreement(buyer, seq): the provider consents to a pending agreement. Consent makes an agreement bind: only from here does it occupy a slot in the provider’sActiveEscrowCountand block its unstake. Accepting afterdeliverByis refused.escrow.rejectAgreement(buyer, seq): the provider declines an agreement it never accepted; the buyer’s reserve is released in full. Valid only while pending.escrow.cancelPending(provider, seq): the buyer withdraws an agreement the provider has not accepted. Open immediately, with no deadline to wait out, and it refunds the same full amount.escrow.expireAgreement(buyer, provider, seq): anyone may close an agreement once the window belonging to its status has passed plus a grace (EXPIRY_GRACE, 10 blocks):CreatedatdeliverBy + grace,DeliveredatdeliverBy + BuyerResponseWindow + grace,DisputedatdisputeOpenedAt + DisputeTimeoutWindow + grace. The refund goes to the buyer of record whoever calls, so calling it gains the caller nothing.
The SDK does not wrap the four yet (acceptEscrow is still recordDelivery), so the provider submits api.tx.escrow.acceptAgreement(buyerAddr, seq) directly (SDK helper, tracked on GitHub). Reading Object.keys(api.tx.escrow) from the running chain would have caught both errors.
What one settled era paid on spec 305
Era 2 settled while the tester guide was being written. These are the real numbers.
curl -s https://api.scalarnet.io/v1/emissions{"lastSettledEra":2,"lastEraEmissionPlancks":"110000000000000000",
"accRewardPerStake":"587270607288593617598683139816", ...}110,000 CMN, exactly clamp(10,000 × 11 agents, 100,000, 1,000,000). Then, as the tester’s agent A:
weightOf(A) : 6953468
pendingEmissions : 90068368362993380
== emissions.claim ==
free after claim : 91143217281421854 (delta +90 068.37 CMN)The accumulator is public, so the split can be checked:
accRewardPerStake era 1 = 348329673019716992730215109268
era 2 = 587270607288593617598683139816
delta = 238940934268876624868468030548
total_weight = emission × 2^64 / delta = 8 492 232
my weight = 6 953 468 -> 81.88 %
payout = weight × delta / 2^64 = 90 068 368 362 993 380 (matches to the planck)That is 81.88% of an entire era’s emission (90,068 CMN) for 60 CMN of escrow volume between two accounts the tester controlled, on 2,200 CMN of faucet funding. Nothing was exploited and nothing overflowed: it all flowed through the emissions module (pallet-emissions), and predicted and actual payouts match exactly. It happened because of how two parts of spec 305 combined on a near-empty network:
- the pot was sized by agent count: 110,000 CMN was minted for ~120 CMN of total escrow volume chain-wide that era, about 917 CMN minted per CMN of work;
- the qualification gates concentrated the pot: of 11 registered agents, most did no escrow at all that era, and the one other agent that did (171 lifetime completions, 10,000 CMN staked) was denied the floor share by the heartbeat gate, so it took 3.79% of the weight against the tester’s 81.88%.
It was filed for a decision (emission sizing, on GitHub, tier:T0), and spec 306 capped each era’s emission at α × qualifying escrow volume. The token model describes the rule in force now.
Supply and issuance at the time
SupplyCap is 10²³ plancks = 100,000,000,000 CMN (100 billion). Issuance at the capture was ~6.05 billion CMN, about 6.05% (percentIssued). This testnet’s genesis was ~6.01 billion CMN, which differs from the 18 billion genesis figure in the project’s own CLAUDE.md. Most issuance growth up to then came from staking: spec 305 removed staking inflation after TotalIssuance grew by 43.58 million CMN from pallet-staking while pallet-emissions minted exactly zero (TESTNETAUDIT.md, finding I-2). Emissions are now the only mint path.
Validator seats and pay
When the tester guide was written (spec 305), three things about validators were undecided.
1. Validator compensation is exactly zero. EraPayout = () returns (0, 0), by design: spec 305 removed staking inflation because it minted outside the emissions module and broke the supply-cap rule. The runtime’s own comment (runtime/src/lib.rs:943), written for spec 305 when every seat was operator-run, states the consequence:
Validator compensation is now exactly zero. Era payout is (0, 0) and transaction fees are burned […] while Slash still routes to the treasury and BondingDuration is 28 eras. A third-party validator therefore has negative expected value: no revenue, real slash risk, 28-era exit. Acceptable for an operator-run testnet where all five authorities are ours [spec 305: the set was five then]; NOT acceptable for a public validator set.
This is still open (validator pay, on GitHub, tier:T0, never worked on autonomously). Run a validator to test the software; it earns nothing.
2. minValidatorBond and minNominatorBond were both 0. Decided on 24 Sep 2026 (decision D11, seats and bonds, on GitHub): 1,000 CMN and 100 CMN, set at block #750,515.
3. All five seats were operator-run. Decided on 24 Sep 2026 (D11): validatorCount was raised 5 → 7 at block #750,511, opening seats to outside validators, and 7 → 8 at block #1,122,976. An outside validator was elected for staking era 71 at block #764,926 (Run a validator records it). All eight seats were filled from staking era 105 (21 Oct 2026) through block #1,157,827; read the current set with api.query.session.validators().