Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

garden/v7 package

Overview

Package garden is Gnotchi's pet realm, v7: the free, allowlisted, drand-rolled, hold-many realm MINT.md designs end to end, now carrying Batch One's personality, the kinship web and a batch of sixty. It inherits garden/v4's machinery -- the SaveDay shape, meters, garden plots, levels, and the List/Buy/Cancel trading surface -- but NOT v4's invariants (MINT.md section 6). garden/v4 is live, immutable and untouched by this file; its two gnomes stay exactly where they are forever (MINT.md section 6, BATCH-ONE.md section 0), and so does garden/v5's one.

============================================================================================ WHAT v7 IS: v6 PLUS EXACTLY TEN CHANGES. READ THIS, THEN "WHAT v6 IS" BELOW IT. ============================================================================================

v7's specification is realms/garden/v7/DESIGN.md (1,974 lines, written 2026-10-17 and reviewed before any of this was typed). This block is the summary; that document is the argument, and where this file disagreed with it the disagreement is marked in the source at the place it happens.

THE RULE THIS FILE IS WRITTEN UNDER, which is v6's own rule quoted back at its successor: "Every line of v5 that is not one of the two fixes below is carried forward byte-for-byte... v5 survived a security audit; every unrelated change made here would be a change that has not been audited, so none was made." v7 is the version that was reserved for changing things, so the door is open -- but THE AUDIT ARGUMENT DOES NOT EVAPORATE BECAUSE THE DOOR IS OPEN. Everything v6 carries forward is carried forward here too, byte-for-byte, unless it appears in the list below. The list is complete. If a change is not in it, it is not authorised, and the three defects mutation testing found in v5, along with every comment recording why something is shaped as it is, are all still here on purpose.

CHANGE 1 -- batchCap 24 -> 60. DECISIONS.md 2026-10-17, "Batch One becomes 60", superseding "Batch One stays 24" from earlier the same day. ONE CONSTANT.

CHANGE 2 -- FIVE ROLLED TRAIT AXES plus born-under, stored on the gnome. TRAITS.md sections 1 and 7.

CHANGE 3 -- THE DOMINANCE ORDER, stored as five explicit slots rather than a 7-bit Lehmer index. The reason is in the gnome struct's own comment: a factorial-base index needs a codec on both sides of the wire, which is two things that must agree forever over a convention nobody has written down, in the one function MINT.md section 9 names as needing its own audit pass above all others.

CHANGE 4 -- THE FOUR STATS on a shared budget, drawn from a 546-row legal set ENUMERATED AT init() FROM THE RULE rather than transcribed, because a transcription error in a 546-row table is undetectable by eye and the rule it transcribes is four comparisons. TRAITS.md section 4.2.

CHANGE 5 -- A SECRET ID, with the first-seventeen-distinct constraint. TRAITS.md section 3.2.2: without it, 36.9% of batches could not spell the acrostic of section 3.2.1.

CHANGE 6 -- A ROLLED CARE THRESHOLD, stored, WITH NO GETTER. The one declared exception to GAMES.md:38, and the only write-only field in the realm.

CHANGE 7 -- THE 84 KINSHIP EDGES on chain, STRUCTURE ONLY, keyed by batch index. LINEAGE.md section 6's OVERRULED box: "store the structure, keep the wording off chain where it can still be improved."

CHANGE 8 -- seedFor MOVES FROM FNV-1a TO SHA-256. NOT A FEATURE -- a defect DESIGN.md found while working out change 3. FNV-1a's low bits are provably a parity of the input bytes' low bits; v5 and v6 take % 100, % 60 and % 4 and mostly get away with it, and v7 takes % 2, % 3, % 5 and % 16 for the one field TRAITS.md's whole duplicate-vector argument rests on. THIS IS THE ONE TO READ FIRST IN REVIEW: it rewrites a line an audited realm carries. Proof at seedFor.

CHANGE 9 -- THE NAMED-EDITION ODDS ARE DECOUPLED FROM THE BATCH SIZE AND RAISED TEN-FOLD. 2026-10-18, the owner: "make the last few have them. The rates have to come out during a mint." ONE CONSTANT AND ONE LINE: namedPick's modulus was uint64(batchCap), so each of Hob, Thistle and Wick was a 1-in-60 shot per claimant and there was a 74.37% chance at least one of the three never existed. It is now uint64(namedPickRange), its own constant at 10 -- a 1-in-10 shot each, leaving 0.54% of full batches missing one. NOTHING ELSE ABOUT THE NAMED-EDITION PATH MOVES: the `namedPick < numNamedEditions` branch, the characterOwner.Has check and the transaction-ordering-race comment inside it are v6's byte-for-byte. THIS IS NOT A GUARANTEE and is not a replacement for the removed one; it is a probability, it fires nowhere new, and it reads nothing new. The arithmetic for all three rates, why the modulus is NOT batchCap (that coupling was accidental -- it made the rarest-asset odds a side effect of a batch size changed for unrelated reasons), why raising a rate is safe where the removed guarantee was not, and the honest cost (collisions become common; first settle wins) are at namedPickRange's declaration.

CHANGE 10 -- THE NAMED ONE-OF-ONES GO FROM THREE TO EIGHT. 2026-10-18, the owner: "If we are doing 60 there should be more specials than 3." He is right, and the reason is arithmetic that change 1 produced silently: three of 24 was 12.5% of a batch, and moving the batch to 60 without touching the roster quietly cut it to 5%. EIGHT OF 60 IS 13.3%, which restores the proportion the collection was designed at. ONE CONSTANT (numNamedEditions 3 -> 8) AND THREE TABLES IN STEP (namedEditionOrder, namedEditionDisplayNames, reservedNameKeys) -- plus List's lock message, which says the count out loud and is therefore a FOURTH place the roster's size appears; see numNamedEditions for the full list of what must move together.

THE FIVE NEW NAMES ARE BATCH-ONE.md section 7's, EXTENDED IN THAT SECTION'S OWN REGISTER AND JUSTIFIED THERE, NOT HERE: Tally, Glim, Vigil, Spruce, Yonder. They are EXISTING, ALREADY VALIDATED ART, renamed exactly the way Hob (the gilded gnome) and Thistle (the Viking) were -- trowel-hand, emberwing, nightwatch, gentleman and pirate from pack/04-pixel-art/characters.json, every one of them "works"/"confirmed at true 2x" in ARCHETYPES.md section 3 and section 6. NO NEW ART WAS DRAWN OR MODIFIED FOR THIS CHANGE and this realm does not know what any of them look like: `character` is a string, the look lives in the art pack, and that separation is unchanged.

namedPickRange STAYS AT 10, DELIBERATELY, AT THE OWNER'S APPROVED ODDS -- a specific edition is still a 1-in-10 shot per claimant and nothing about the pick, the branch or the race moves. What DOES move is what the odds compose to, and the new figures replace the old ones everywhere they are written down: (9/10)^60 = 0.18% per edition is unchanged, but P(at least one of EIGHT missing) = 1 - (1-0.0017971)^8 = 1.43%, up from 0.54% at three. A half-filled batch of 30 is 29.3%. The cost side moves much further: 48 of 60 claimants now hold a named-pointing pick (60 * 8/10) for eight slots, so about 40 of them lose the race and fall through to an ordinary draw. Full arithmetic, all four rows, at namedPickRange. NOTE THE HEADROOM: 10 >= 8 still holds, but only just -- at eight editions the modulus is two residues from the point where an edition becomes unreachable, so numNamedEditions may not grow again without raising namedPickRange, and TestNamedEditionOddsAreOneInTen is the test that fails if it does.

NOT A GUARANTEE, AGAIN AND FOR THE SAME REASONS: this adds five entries to three tables and no mechanism. No forcing, no namedEditionToForce, nothing keyed on a shrinking observable (AUDIT-01 RED-1). "TALLY WAS NEVER BORN" is now a 0.18% permanent fact per edition exactly as "HOB WAS NEVER BORN" is, and the chance that SOME edition is missing went UP, not down, because there are more editions to miss. That is the accepted price of the proportion the owner asked for.

NOT A CHANGE -- THE NAMED-EDITION GUARANTEE, WRITTEN 2026-10-17 AND REMOVED 2026-10-18. It was briefly a change of its own: "when the number of unsettled slots remaining equals the number of named editions still unclaimed, the remaining settles take them." AUDIT-01 RED-1 found that rule positionable -- its trigger was computable from two public readers and a wallet that never claimed until 57 were settled took an edition with certainty -- and the owner chose REMOVAL over any replacement, so the named-edition path here is v6's byte-for-byte apart from change 9's one modulus, and NO END-OF-BATCH FORCING AND NO namedEditionToForce EXISTS OR MAY BE RE-ADDED. The residual chance that at least one edition is never minted is KNOWN AND ACCEPTED: 74.4% when it was accepted, 0.54% after change 9, 1.43% after change 10 raised the roster from three to eight (more editions, more chances for one of them to be missed), never zero. The full record of the decision is in settleCore, next to the pick it would have overridden; do not re-add a guarantee without reading it, and note that changes 9 and 10 are deliberately NOT one -- between them they move two numbers and five table entries, not a mechanism.

WHAT v7 MUST NEVER DO, and these are named so that leaving them out is a decision on the record rather than an omission. From TRAITS.md section 7: no rarityScore(), no TraitCensus(), no traitCount(axis, id), no sort-by-stat, no trait-gated entrypoint anywhere in any realm, nothing that returns a distribution. From LINEAGE.md section 6.5: no KinOf(tokenID), no IsKin(a, b), no LineOf(tokenID), no LineCount(line), no KinCount and nothing that returns a degree, no sort/filter/gate of any kind on a line, a relation or a relative, and NO ENTRYPOINT ANYWHERE IN ANY REALM THAT BEHAVES DIFFERENTLY FOR TWO RELATED GNOMES THAN FOR TWO STRANGERS. Added by v7, because it is the first realm where they become reachable: no CareThreshold / CareScore / Band / PointsToThreshold or any fraction of them; no SecretCount / SecretCensus / HoldersOfSecret / IsFirstSeventeen and nothing exposing secretsDrawnMask; no Label / Archetype; no TraitsMatching or any filter or predicate; no unique-in-batch marker; AND NO READER, ANYWHERE, THAT TAKES TWO TOKEN IDS -- that last one is the structural form of all of it, because a function of two gnomes is the shape every kin-bonus, related-pair and shared-quest feature needs. See the shape rule stated once above the trait readers.

THE INVARIANT THOSE PROTECT, STATED ONCE: NO FUNCTION IN v7 BRANCHES ON A TRAIT, A STAT, A LINEAGE EDGE OR A SECRET. Not in a guard, not in a loop condition, not in a fee calculation, not in an eligibility check. Trait data is read by this realm's own code in exactly two places, and both are inside the draw writing itself down: drawRoll/assertRoll, and secretWeightsFor's one comparison of boast/fear against the secret target table, which produces a secret id and nothing else. TestTwoGnomesWithOppositeTraitsAreTreatedIdentically is that invariant as a test a CI run can check rather than a promise a reviewer has to take.

THE MIGRATION QUESTION, ANSWERED ONCE SO NOBODY RE-OPENS IT: v5's one gnome STAYS ON v5 and v4's two stay on v4. No migration, ever -- the identical answer BATCH-ONE.md section 0 gave one version earlier, for the identical reason: v5 is immutable, has no transfer-in path, and nothing in v7 can reach it. BATCH ONE IS THE SIXTY GNOMES ON THIS REALM AND NOTHING ELSE; the 60 is sixty, not fifty-nine plus the one already minted. v5's gnome holds no axis, no dominance order, no stat line, no secret, no sign, no threshold, no batch index and no kinship edge -- not because anything was withheld but because V5 HAS NO SUCH FIELDS, so it cannot be a member of a batch whose defining properties did not exist when it was minted. Nothing in this file knows v5 exists: not a field, not an import. No cross-realm per-wallet cap (MINT.md section 6 already settles it -- "a single wallet may hold a v4 gnome and any number of v5 gnomes at once" -- and importing v5 would make v7 undeployable anywhere v5 is not live), no batch-index offset, no legacy-holder benefit (if the owner wants the v5 holder on the v7 allowlist, that is one AdminAllow call and a curation decision), no cross-realm name uniqueness (impossible, and already true between v4 and v5), no cumulative TotalSupply, and no deprecation pointer in v5 (it is immutable). THE ONLY THING v7 INHERITS THAT IS NOT SOURCE CODE IS THE NAMESPACE.

AND ONE FACT THAT CHANGES THE SHAPE OF THIS VERSION: v7 SUPERSEDES A REALM THAT NEVER CAME UP. garden/v6 is written, audited, passing its tests and NOT ON CHAIN -- vm/qpkgmeta_json answers "status":"inert", three paid addpkg attempts produced nothing retrievable, and a five-line probe realm with no imports failed identically, which is what proves it is the chain and not the code (TESTNET.md:530-582). So v6's two fixes have never been live for anybody: SetName and SettledRound arrive for the first time here, and section 1.1's "byte-for-byte" is a claim about SOURCE, not about continuity of service. Before attempting to deploy this larger realm, re-run the five-line probe first -- and remember the lesson that file draws: "OK! from gnokey, success:true in the block, and a debited balance are together still not evidence that a deploy happened. Only reading the package back is."

============================================================================================ WHAT v6 IS: v5 PLUS EXACTLY TWO FIXES, AND NOTHING ELSE. READ THIS SECOND, AND READ IT WHOLE. IT IS THE RECORD OF THE AUDITED BASE THIS FILE IS A COPY OF, AND EVERY WORD OF IT STILL APPLIES EXCEPT WHERE THE TEN CHANGES ABOVE SAY OTHERWISE. ============================================================================================

garden/v5 is DEPLOYED AND LIVE on the onyx testnet. v6 exists because a review on 2026-10-16 found a hole that the original design, the test suite and the adversarial security review (ATTACK-REVIEW.md, eight attack goals) all missed -- not a vulnerability, a MISSING FEATURE that contradicts something the owner asked for in his own words. This file is a DELIBERATELY NARROW, SURGICAL COPY of v5. Every line of v5 that is not one of the two fixes below is carried forward byte-for-byte, including every comment recording why something is shaped as it is and the three real defects mutation testing found and fixed. v5 survived a security audit; every unrelated change made here would be a change that has not been audited, so none was made. If you are about to "also improve" something while reading this file: don't. Write a v7.

FIX 1 -- A v5 GNOME CANNOT HAVE A NAME, AND THE GAME NEEDS ONE.

Example
 1v5's gnome struct has no name field, no setter, and `character` -- the only identity string
 2it carries -- is "" for every gnome outside the three capped one-of-ones. Confirmed on the
 3LIVE CHAIN, not inferred: `GnomeByToken("0000002")` on the deployed v5 returns
 4character "". So 21 of Batch One's 24 gnomes would have been permanently anonymous, with no
 5way ever to fix it, because the realm is immutable.
 6
 7That directly contradicts the owner's own instruction that in-game characters "should
 8refer to you as your gnome name" -- the adventure's whole NPC dialogue surface is built on
 9a name existing. v5's own package doc argued the opposite ("NO PLAYER-CHOSEN NAME... the
10app is free to let an owner caption their own gnome off chain"), and that argument was
11WRONG in one specific, load-bearing way worth recording rather than quietly deleting: an
12off-chain caption is not an identity. It cannot travel with the token through a sale, it
13cannot be read by another realm, it cannot be reserved against impersonation, and the
14adventure realm cannot greet you by it. The reasoning that was right -- that nothing
15should be CHOSEN at mint time -- is preserved exactly: see SetName below.
16
17THE SHAPE, AND WHY. Naming is a SEPARATE ACT, AFTER the roll, never part of it. `Claim`
18still takes no arguments and `Settle` still takes no name: the entire point of the mint is
19that you do not choose what you get (MINT.md section 1a/section 2), and nothing here
20weakens that. Naming what you already got is a different act entirely, performed by a
21different function, at a different time, by the token's current owner only.

FIX 2 -- v5 NEVER STORED THE DRAND ROUND THAT PRODUCED EACH GNOME.

Example
1v5 emits the settling round in its `Settle` event and then forgets it. "Which round made
2this gnome" was therefore a transaction-log archaeology problem rather than a state read:
3`Provenance` returns adoptedAt/acquiredAt/saleCount and there was no getter for the round
4at all. Since "provably unrigged" is this project's headline claim (ECONOMY.md section 1),
5a holder being unable to verify their own gnome with one query was a gap between what the
6project says and what the chain can answer. v6 stores the round on the gnome itself
7(gnome.settledRound) and exposes it (SettledRound). See settleCore.

============================================================================================

WHAT CHANGES FROM v4, IN ONE TABLE, EACH CITED TO THE DOCUMENT THAT DECIDED IT:

  • Adopt(name, hat, coat, eyes, nose) is GONE. Nothing is chosen by a caller any more. DECISIONS.md 2026-10-15 deprecates the DIY picker as a product direction; MINT.md section 1a/section 2 replace it with a roll.
  • An ALLOWLIST gates who may ever claim (MINT.md section 11: "gate issuance, never the secondary market"). Admin-managed, additive and removable, never touching the mint itself once a claim has spent its roll.
  • ONE ROLL PER WALLET, FOREVER, enforced independently of whether the resulting gnome is still held (MINT.md section 1a/section 2.3). hasRolled is permanent; nothing in this file clears it, and selling the gnome does not refund the roll.
  • FREE. Claim refuses coins exactly as v4's Adopt refuses them (MINT.md section 1a/2.5, GAMES.md rule 6: "random when earned, exact when bought... free: random is allowed").
  • THE ROLL IS TWO-PHASE, SETTLED BY DRAND (MINT.md section 10, the load-bearing finding of this whole file). A single-transaction roll is peekable two ways, verified against gno.land's own source and binary rather than assumed from Ethereum's model: vm/qeval can call a crossing function for free with no signature, and `gnokey maketx call -simulate only` previews a real signed call against the last committed block with no gas and no broadcast. Neither tool is the cause; the cause is that gno.land exposes NO on-chain entropy at all -- chain/runtime has ChainHeight and nothing else, math/rand is PCG seeded (0,0) on every node forever, and there is no shipped VRF -- so any single-transaction formula, however it is dressed up, is something a caller can evaluate offline for every block it might land in before ever deciding to broadcast. The fix MINT.md section 10.5 settles on, and this file builds, is a commit/reveal split across this project's own deployed drand verifier: Claim spends the one-time roll and pins a ROUND THAT DOES NOT EXIST YET; Settle, permissionless, computes the result once that round is actually published. One player signature, not two -- the player signs Claim only.
  • PARTS FROM A WEIGHTED TABLE (ECONOMY.md section 3), not a player's choice, with three named one-of-ones (Hob, Thistle, Wick, Tally, Glim, Vigil, Spruce, Yonder -- BATCH-ONE.md section 7; three until v7 change 10 made it eight) capped at one each.
  • COUNTED RARITY. Every part's population is tracked on chain and readable by anyone -- MINT.md section 13's "nine gnomes in the world have this nose" has to be a chain fact, and EyesCount/NoseCount/HatCount/CoatCount below are that fact, not an app claim layered on top.
  • MINT ONE, HOLD MANY, PLAY ONE (MINT.md section 2a). Buy's one-gnome-per-address refusal is GONE -- deliberately, and only from Buy, never from Claim (see Claim's hasRolled gate, which is the cap that still matters). An address may hold any number of gnomes; exactly one is ever AWAKE, tracked by activeToken, and only the awake one can ever gain a cared day, a streak, a level or XP. SaveDay and ChooseType resolve through activeToken, never through "the" gnome for an address, because there no longer is one.
  • SWITCHING (Wake) is free, at most once per UTC day (MINT.md section 2a), the same day-boundary discipline SaveDay's own dailyCareDay already uses.
  • THE SECONDARY MARKET IS OPEN: Buy/List/Cancel carry over v4's shape and fee (MINT.md section 11), with no allowlist on buying and no hold cap.
  • THE LOCK: the eight named one-of-ones cannot be listed until they have lockCaredDays cared days; every ordinary gnome trades from day one (MINT.md section 12/13, "Option A" -- lock only the named one-of-ones). SETTLED 2026-10-19 AT 14, down from that section's unconfirmed recommendation of 30; reasoning at lockCaredDays itself. It is still one named constant and nothing else in this file assumes its value -- verified by AUDIT-03, which also confirms maxListingDays' own 30 was never coupled to it. A gnome's own caredDayCount never decreases and is not reset by Buy, so once a named gnome unlocks it stays unlocked through every future sale.
  • A GENESIS GNOME, minted to this realm's own address at deploy, non-transferable, never listable, separate from the 60-gnome batch (DECISIONS.md 2026-10-15: "the genesis gnome belongs to nobody, permanently, on purpose"). ITS NAME IS SPRIG, settled 2026-10-19 -- see genesisGnomeName below for why, and note it is load-bearing in FOUR places, not one: the constant, reservedNameKeys, the gnome's own `character` slug, and characterOwner.

WHAT IS DELIBERATELY OUT OF SCOPE FOR THIS FILE, EACH A SCOPING DECISION MADE BY THIS PASS, NOT A GAP IN THE BRIEF THAT ASKED FOR IT. Recorded here in the open the same way MINT.md records its own open questions, so a future reader finds the reasoning in one hop:

  • NO SetRule/FreezeRules TUNABLE ADMIN SURFACE. v4's ruleBand/SetRule/FreezeRules machinery is real and proven, but every care-mechanic number below is a fixed constant at v4's own deploy defaults instead. The brief this file was written against lists eleven concrete requirements and none of them is "tunable rules"; adding v4's whole admin-ratchet surface on top of everything already new here would have roughly doubled this file's size and its audit surface for a feature nobody asked for in this pass. A FUTURE VERSION can add it back following v4's own pattern exactly if the owner wants it. (v5 said "a v6" here; v6 exists now and deliberately did not, for the same reason it added nothing else.)
  • NO NAME AT MINT TIME. This entry REPLACES v5's own "NO PLAYER-CHOSEN NAME" entry, which is the thing v6 exists to correct -- see the "WHAT v6 IS" section at the top of this file for the full account of what that argument got right and what it got wrong. What survives from it, unchanged and non-negotiable: NOTHING IS CHOSEN BY A CALLER AT MINT TIME, INCLUDING A NAME. `Claim` takes no arguments; `Settle` takes no name. v4's validNameRune/validNameCharset/hasVisibleNameContent machinery (three audit rounds' worth, per v4's own comment) is carried back into this file WHOLE, not re-derived, because the naming act moved rather than disappeared -- it is now SetName, callable only by the token's current owner, only after the roll has already irrevocably decided what they got.
  • NO OpenBatch ADMIN SURFACE. MINT.md section 6 floats an owner-callable OpenBatch for adding future batches' rosters; the task this file was built against names a single 60-gnome batch (BATCH-ONE.md, at DECISIONS.md 2026-10-17's size) and does not ask for a mechanism to open a second one. The batch here is sized once, at deploy, by the batchCap/numNamedEditions constants below. A FUTURE VERSION is the natural place for OpenBatch if a second batch is ever wanted, the same way every other irreversible-shape decision in this project has gotten its own version rather than a retrofit. (v5 said "a garden/v6 is the natural place"; v6 exists now and deliberately did NOT take that opportunity -- it is two fixes and nothing else.)

A REAL FLAW FOUND AND FIXED WHILE DESIGNING Settle, RECORDED HERE RATHER THAN QUIETLY FIXED AND FORGOTTEN -- the same discipline MINT.md section 10 models for its own single-transaction finding. An earlier version of this design picked the named one-of-ones by drawing "without replacement" from a shrinking pool: remainingTotal = batchCap - settledCount, remainingNamed = 3 - namedAssignedCount, and a claimant's pick = seed % remainingTotal. THAT IS PEEKABLE AND GAMEABLE, even though the seed itself is honestly drand-derived and unforgeable: because remainingTotal only ever shrinks as OTHER claimants settle, and Settle is permissionless and callable at any later time, a claimant whose seed is already fixed (the moment their pinned round publishes) can simply precompute, for every possible remainingTotal from 1 to batchCap, whether that value would win them a named edition -- and then wait, costing nothing, until the rest of the batch happens to settle into a state that does. The fix below removes every dependency on shrinking, observable, time-varying state from the decision: a claimant's own named-edition pick is `seed % namedPickRange` (`seed % batchCap` until v7 change 9 moved the modulus off the batch size on 2026-10-18 -- a different constant, the same and only load-bearing property), against a modulus that is a COMPILE-TIME CONSTANT, not a moving count, so there is nothing left to wait for -- the only residual order-dependence is which of possibly several colliding claimants (bad luck, not strategy: nobody can change their own pick by choosing when to settle) gets a named edition that more than one of them rolled, and that is resolved by whichever Settle call lands on chain first.

A SECOND AUDIT PASS NAMED THIS PRECISELY: "settling promptly", said above and in an earlier version of Settle's own comment, OVERSTATES what the residual actually rewards. It is not "whoever claimed first" or "whoever cared most" -- pendingClaim.claimedAt is never consulted by the tie-break, and Settle is permissionless, so a bystander (a searcher, a bribed relayer, a claimant's own bot) can win a collision for whichever side pays for transaction-ordering priority, regardless of who rolled or claimed first. Considered and rejected: tie-breaking on claimedAt instead. It sounds like the honest fix, but it is not reachable without a worse trade-off -- Settle finalizes irrevocably (it mints a real token to a real owner, who may already have acted on it), so honoring an earlier claimedAt would mean either refusing to finalize ANY collision candidate until every possible earlier claimant's round has published and settled (reintroducing exactly the wait-and-see window section 10 and this fix exist to close, now around a deadline instead of a pool size) or reaching back to UNDO an already- settled mint (which nothing in this realm, or any grc721-based realm in this project, has ever claimed to be able to do, and which would be a strictly worse property than the one being fixed). What actually holds, stated precisely rather than aspirationally: nobody can improve THEIR OWN odds by choosing when to settle (the property section 10 is about, and the property this fix actually guarantees); the residual is an ordinary transaction-ordering race over a LOW-PROBABILITY, LOW-STAKES event (three slots in a batch of 24, decided among whichever handful of claimants happen to collide -- WRITTEN BEFORE CHANGES 1, 9 AND 10, and "low probability" and "a handful" are the two words that stopped being true; see the paragraph below), the same shape of race every permissionless action on every chain is already subject to, not a new hole this design opened. See Settle's own comment for the arithmetic. v7 CHANGES 9 AND 10 (both 2026-10-18) MAKE THAT RACE MORE FREQUENT AND SAY SO: at namedPickRange = 10 roughly 18 of 60 claimants held a named-pointing pick for three slots, and at change 10's eight slots it is 48 of 60 (60 * 8/10) chasing eight, so collisions are now the NORMAL CASE rather than an unlucky one -- about 40 claimants draw a named pick and fall through to an ordinary draw. The race is unchanged in SHAPE -- first settle takes it, nobody can move his own pick -- and it rewards promptness, not lateness. The honest statement of that cost, and the reason the rate was raised anyway, are at namedPickRange.

SETTLE CAN NOW PAY FOR ITS OWN BEACON RELAY, OWNER-APPROVED 2026-10-15, THE DAY AFTER THIS FILE WAS FIRST WRITTEN. The two-phase roll above depends on beacon/v2 actually having the pinned round's drand signature by the time Settle runs, and until this change that was somebody else's problem: a relay has to happen, by a human or a bot, running outside this realm and outside this project's control, or a claimant who has already spent their one-and-only roll is stuck mid-mint through no fault of their own -- found live, not hypothetically, while building this file: the project's own beacon was observed ~2 days stale. The fix the owner asked for ("can we have the mint transaction pay the beacon though") is `SettleWithSignature` below: the player's own app fetches the pinned round's signature from drand's public API (the same `https://api.drand.sh/<chain>/public/<round>` this file's own test fixtures are fetched from) and passes it straight through; Settle relays it itself, in the same transaction, before computing the result. One transaction, and the player pays its gas -- TO THE NETWORK, same as any other transaction's gas, NOT to this project, so it never touches the free-mint promise (MINT.md section 14: "gas is still a payment, just not to us... the operator takes no stake"). Three things this buys, in the owner's own order of importance:

  • IT COSTS THE PROJECT NOTHING. The alternative to a player-paid relay is an operator-run one -- a cron job or a bot calling beacon.Publish on a schedule, which is real recurring GNOT spend with no revenue to offset it (MINT.md section 14 forecloses an unlock fee, batch one is free end to end). This change deletes that cost rather than budgeting for it.
  • A PLAYER CAN NEVER BE BLOCKED BY THIS PROJECT'S OWN HOUSEKEEPING. The stuck-mid-mint state above is now unreachable for anyone willing to fetch their own round's signature: their own Settle call makes the round available, with no dependency on this project, a third-party relay, or anyone else's goodwill.
  • THE BEACON GETS FRESHER AS A SIDE EFFECT, FOR EVERYONE ELSE USING IT. beacon/v2 is a shared, permissionless, on-demand record (see its own package doc) -- a round this realm's own claimants cause to be published is published for every other consumer of that beacon too, not just for this realm.

THREE THINGS WORKED OUT BY QUERYING THE LIVE CHAIN, NOT ASSUMED, because this is new cross-realm write surface and this project's own history (the SPLASH beacon migration, MEMORY.md) is built from exactly the kind of interrealm detail that gets guessed wrong:

  1. WHAT beacon.Publish DOES ON AN ALREADY-PUBLISHED ROUND. Fetched byte-for-byte from the live deployed realm itself (gno.land/r/g1r4aw43kagaaquhegxw6xr346h9f2qu2ezf0jar/beacon/v2) via the public onyx-1 RPC's `vm/qfile` query, 2026-10-15, and diffed identical against this project's own vendored copy at realms/_vendor/beacon/v2/beacon.gno -- it PANICS, it does not no-op: `func Publish(cur realm, round int64, sigHex string) { if Published(round) { panic ("beacon: round already published") } ... }`. SettleWithSignature therefore checks `beacon.Published` itself BEFORE ever calling Publish, exactly once, and never calls Publish at all if the round is already there -- calling it unconditionally would make this function fail every time a relay (another claimant's own SettleWithSignature, or anyone else) beat it to the punch, which given Settle's own permissionless, callable-by-anyone design is an ordinary and expected race, not an edge case.
  2. THAT Publish IS REACHABLE CROSS-REALM AS `beacon.Publish(cross(cur), round, sigHex)`, and that it carries no caller-identity check at all for this function to trip over. Confirmed two ways: the live source pulled in point 1 shows Publish's body never reads `cur` or calls `cur.Previous()` -- it is permissionless by whoever relays, not merely by whichever address -- and this exact call shape is already proven working, not merely plausible, by this file's OWN test harness: garden_test.gno's `publishAs` helper has called `beacon.Publish(cross(cur), round, sigHex)` from this package since the day this file was written, and every test that calls it passes. SettleWithSignature's own call is the identical shape with `cur` being Settle's own realm parameter instead of a test helper's. (`cur.Previous()` inside beacon.Publish would resolve to THIS realm, garden/v5, when called this way rather than to the human wallet that called Settle -- a real interrealm fact worth recording even though Publish does not currently branch on it: if beacon/v2 ever grows a caller-identity check, re-read this paragraph before assuming Settle's relay still works.)
  3. GAS. Measured on this toolchain with r/splash/beacon/zz_gas_test.gno's own method (`gno test -run TestZZGas -print-runtime-metrics=true -v`, net of this package's own fixed per-test-call overhead): a bare Settle today costs ~6.2M gas; beacon.Publish alone costs ~18.5M gas on THIS realm's measurement (the same order of magnitude as beacon/v2's own documented 16,970,375, re-measured rather than quoted because toolchain and realm storage both shift it). SettleWithSignature's worst case -- the round not yet published at all -- is therefore on the order of ~25M gas total, call it 1% of the 3e9 block gas limit and a few hundredths of a GNOT at this chain's gas price. Reasonable: it is roughly four times a bare Settle, not an order of magnitude more, it still leaves room for well over a hundred such settlements in a single block, and it replaces a cost this project would otherwise have paid itself, recurringly, forever, to keep the beacon fresh by some other means.

THE SHAPE: TWO ENTRY POINTS, ONE SHARED CORE, NOT ONE FUNCTION WITH AN OPTIONAL SIGNATURE. `Settle(cur realm, claimant address)` is UNCHANGED in behaviour and UNCHANGED in signature -- every existing test and every existing caller that already knows (or does not care) whether the round is published keeps working exactly as before, at exactly its old gas cost, with no signature to fetch or supply. `SettleWithSignature(cur realm, claimant address, sigHex string)` is new, and is for a caller that HAS a signature and wants its transaction to publish the round if nobody else already has. A Gno realm's exported functions take a fixed argument list -- there is no overloading, and an optional/variadic last argument would still force every caller, including every test and the app's own simpler "it's already published" path, to pass an empty string they have no use for, for a case (publishing) that is the EXCEPTION, not the rule, since most settlements land after some earlier claimant or relay has already published the shared round. Two named entry points make the exception visible in the function name instead of buried in an argument, and cost nothing extra: both share one unexported `settleCore`, so there is exactly one copy of the draw-and-mint logic, not two drifting in parallel. And it is SAFE for either one to be called in either actual on-chain state: Settle still refuses cleanly, with its original message, if the round genuinely is not published yet; SettleWithSignature still succeeds, with its supplied signature simply going unused, if the round turns out to already be published by the time it runs (point 1's race) -- neither caller has to know in advance which state they are in, they only have to know whether they personally HAVE a signature to offer.

Function

Acorns

func Acorns(addr address) (acorns int64, found bool)
Ask AI Claude ChatGPT

Param

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/r/g1nq99m3h9ed7vqve6ajpr3qwy2ls252m8jnundm/garden/v7.Acorns()"

Result