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.

speed package

Overview

Package speed is the SPLASH HOURLY ticket draw: a draw every hour instead of every week.

WHY IT IS A SEPARATE REALM AND NOT A PARAMETER ON r/splash/arcade/raffle. The weekly raffle's cadence is woven into eight constants, four build guards and the refund grace window, and every one of them had to be re-derived from first principles here (see "the two clocks" below). A shared realm with a cadence dial would have had to satisfy BOTH sets of constraints simultaneously, which is how a margin gets shaved: the hourly window's pin offset is 2400 rounds and the weekly one's is 230400, and no single guard can assert both. Two realms, each with its own derivation and its own asserted margins, is the honest shape. The mechanics below are the raffle's, deliberately: the ticket index, the Commit/Capture/Resolve three-phase cycle, the FROZEN-TOTAL property, the one-way refund exit, and the pin-at-record-creation rule. Read r/splash/arcade/raffle/AUDIT-2026-09-28.md before changing any of them; this file's job was to re-scale the timing without re-introducing one of its fourteen findings.

========================================================================== V1 IS SELF-CONTAINED: THERE IS NO SPONSORSHIP AND THERE IS NO FundPot. ==========================================================================

The pot is ENTRY MONEY ONLY. Nothing outside this realm can put a coin into a window's pot, and this realm calls no other realm's code except r/gnoland/wugnot (to move money) and r/splash/beacon (to read a round). There is no cross-realm feed in either direction, on purpose, in both directions:

  • NOTHING FEEDS THIS POT. r/splash/arcade/duel's rake goes to the weekly raffle and stays there. Adding a second consumer of that rake would make the duel's settlement depend on two realms' bookkeeping instead of one, and the duel's Collect depending on the raffle's bookkeeping was AUDIT-2026-09-28.md finding 14 — the worst liveness defect of the day, in which any raffle panic trapped a resolved duel's whole pool in both realms. It was severed that evening (Collect books, FlushPot delivers) and this realm does not reinstate the shape.
  • THIS REALM FEEDS NOTHING. No rake, no share, no callback. Its only outbound transfers are wugnot to a winner's wallet, wugnot to a refunding buyer's wallet, and wugnot to opsVaultAddr.

AND THE PROPERTY THAT MAKES THE ABSENCE OF SPONSORSHIP A SECURITY FEATURE RATHER THAN A MISSING ONE — THE SOLE-ENTRANT PROPERTY:

Example
 1WITH NO HOUSE MONEY IN THE POT, A SOLE ENTRANT MERELY WINS BACK THEIR OWN STAKE
 2MINUS THE HOUSE CUT. The weekly raffle still has the sole-entrant attack, and it
 3is a real one there: the duel's rake is sponsored into the live week's pot, so
 4somebody who buys one ticket into a quiet week with a large sponsored balance
 5collects the sponsorship for the price of the fee on that ticket. Here Pot is
 6exactly sum(spendOf) by construction, so a sole entrant's pot IS their own
 7post-cut spend: they pay 100,000 ugnot, and if they are the only entrant they win
 895,000 back. THERE IS NO POSITIVE-EXPECTATION SOLO PLAY, so the attack cannot
 9exist — not mitigated, not made expensive, ABSENT, as an arithmetic consequence
10of the pot's definition.
11
12(A SOLE ENTRANT IS NO LONGER INDIFFERENT BETWEEN Resolve AND Refund, AND THIS
13PARAGRAPH SAID THEY WERE. It read: "The same arithmetic makes a sole entrant
14indifferent between Resolve and Refund: both return exactly their own 95,000."
15Since 2026-09-29 the fee is ESCROWED per window and earned only by a draw
16(AUDIT-2026-09-28.md finding 25), so a refund returns the whole 100,000 and a
17resolve returns 95,000: THE SOLE ENTRANT STRICTLY PREFERS THE DRAW TO FAIL, by
18exactly the fee. The conclusion this paragraph was supporting is unaffected —
19solo play is still never positive-expectation, which is what kills the
20sole-entrant attack — but the incentive it describes is now the general one the
21owner is weighing: with a refund at 100% of stake, every non-winner prefers a
22failed window to a drawn one. That is a product decision and is deliberately NOT
23fixed here; it is recorded in AUDIT-2026-09-28.md so that the next reader of this
24paragraph does not mistake it for a property of this realm's design. The refund
25grace window below is therefore not only a multi-entrant concern any more.)

SO DO NOT ADD FundPot, A SPONSORED FIELD, A ROLLOVER OR A JACKPOT WITHOUT RE-AUDITING THIS PARAGRAPH. Every one of them puts money in the pot that belongs to no entrant, which (a) resurrects the sole-entrant attack, (b) turns the invariant Pot == sum(spendOf) into Pot == sum(spendOf) + Sponsored with everything that follows from it (see the raffle's Draw.Sponsored, Refund's sponsorship transition and SweepDrawlessWeek — three separate mechanisms that exist only because house money belongs to nobody), and (c) makes this realm's liveness someone else's problem again.

========================================================================== THE ONE-HOUR CADENCE, AND THE TWO CLOCKS ==========================================================================

Every duration in the weekly raffle shrinks by roughly 168x here, and THE FAILURE MODE OF GETTING ONE WRONG IS SILENT — it is not a panic, it is a draw whose winning randomness was public on api.drand.sh before entries closed (finding 10). So each constant below is derived in place, and each is asserted by a build guard that obeys the doctrine that finding 11 cost three attempts to learn:

Example
1A GUARD THAT PROTECTS A REAL-WORLD DURATION MUST HAVE SECONDS ON BOTH SIDES,
2SOURCED FROM TWO DIFFERENT CLOCKS, OR IT CANNOT FAIL.

The two clocks available to a realm on this toolchain, neither of which it can measure at runtime (there is no block-timestamp getter):

  • DRAND'S PERIOD: beacon.PeriodSeconds = 3 s, EXACT. A round count multiplied by it is real seconds by definition of the beacon.
  • THE CHAIN'S BLOCK TIME: assumed 86400/blocksPerDay = 3.29998 s, written as the integer 33/10 in guards because 3.3 is not an integer constant. MEASURED ON LIVE gno.land MAINNET ON 2026-09-28: 3.306 s/block over 200,000 blocks and 3.311 s over 20,000. So reality is very slightly SLOWER than this file assumes, by about 0.2%. That is the benign direction for every margin here (a slower chain means the beacon's estimate LAGS drand rather than outrunning it, and lagging is correctable by any relay of drand's head — see speedMaxStaleBlocks), but the guards below are stated against a ±10% band rather than against the measurement, because the measurement is of the past.

Nothing in this realm is a timestamp. A window is a sequence number derived from chain height; no ticket, spend or draw is dated.

Function

SweepDrawlessWindow

func SweepDrawlessWindow(cur realm, window int64) int64
Ask AI Claude ChatGPT

SweepDrawlessWindow moves a FINISHED, TICKETLESS window's pot to the ops pot, so that money in such a window can leave. PERMISSIONLESS, like Commit. Returns the amount moved.

READ THIS WHOLE COMMENT BEFORE DELETING THE FUNCTION AS DEAD CODE, because in v1 IT IS UNREACHABLE THROUGH EVERY ENTRY POINT and that is deliberate.

WHY IT IS UNREACHABLE HERE. A Draw record comes into existence in exactly one place (getOrCreateDraw) with exactly one caller (Buy), and Buy appends a positive ticket count in the same transaction; a panic anywhere after the record is created reverts the whole transaction, record included. So there is NO SEQUENCE OF CALLS that leaves a window with a record, a zero ticket total and a positive pot. That argument is asserted by the suite (a ticketless window cannot be produced through Buy) and the function's own preconditions are exercised by installing the state directly.

WHY IT EXISTS ANYWAY. It is the shape of AUDIT-2026-09-28.md's fifth fix, and the defect it exists against is the single most common one in that document — SIX of the fourteen findings are "money is in the realm and no path can ever get it out", and four of those six were found by an engineer implementing something else, not by an audit. In the raffle the state is REACHABLE: FundPot credits the live week and creates its record, so a week in which the duel's rake arrived but nobody bought a ticket ends holding money in a Draw that Commit rightly refuses (no tickets), that Resolve and Refund cannot touch (both require Committed) and that FundPot will never revisit (it only funds the live week). ADDING ANY OF THE THINGS THE PACKAGE COMMENT WARNS AGAINST — sponsorship, a rake feed, a jackpot, a rollover — MAKES IT REACHABLE HERE TOO, and the day that happens this exit should already be in place and tested rather than being the thing somebody forgot.

WHERE THE MONEY GOES, AND WHY IT IS NOT "ROLLED FORWARD" INTO THE LIVE WINDOW'S POT.

THE PARAGRAPH THAT STOOD HERE RECOMMENDED A DESIGN TWO AUDITS FOUND EXPLOITABLE, AND THAT IS WHY IT IS QUOTED RATHER THAN QUIETLY REPLACED — AUDIT-2026-09-28.md finding 36. It read:

Example
1"The raffle rolls a drawless week's sponsorship into the live week's pot, AND IT IS
2RIGHT TO: that money is the PLAYERS' side of the duel rake, so handing it to the house
3would take the players' share away from players, and the raffle has a Sponsored field
4that lets the destination week's pot exceed its spend records without breaking
5anything."

EVERY CLAUSE OF IT IS NOW FALSE, AND THE "AND IT IS RIGHT TO" WAS THE DEFECT. The raffle does not roll a dead week's sponsorship anywhere: rolling it into the live week was AUDIT-2026-09-28.md finding 28 (a destination evaluated at call time is a destination the permissionless caller chooses, by choosing when to fire), and the register that replaced it for one day was finding 31 (the caller who opens the destination week is the caller who calls Buy, so they could be its only entrant and capture the whole amount for the price of the house cut). A dead week's sponsorship now becomes a PER-UNIT CLAIM ON THE WEEK IT IS ALREADY IN, payable to the holders of that week's EARNED entries — read the raffle's retireSponsorship and ClaimDeadWeek. There is no destination at all.

SO A READER WHO PORTS SPONSORSHIP INTO THIS FILE MUST NOT PORT THE SENTENCE ABOVE. The half of it that was sound — this money is the PLAYERS' share and must not end up with the house — is satisfied by giving it back to the players who earned it, in place, and never by moving it to a week somebody can pick. This comment is where an engineer adding a rake feed here would look first, which is exactly why a recommendation for a broken design sitting in it is a defect and not untidiness.

NEITHER HALF OF THE ORIGINAL APPLIED HERE IN ANY CASE.

  • This realm has no sponsorship and no rake, so there is no players' share to protect: any money in a ticketless window is unattributable by construction — exactly like a stranger's unsolicited transfer, which sweepDonations also sends to opsPot.
  • Crediting the live window's Pot would BREAK Pot == sum(spendOf) for that window, which is the identity that makes Refund unable to overdraw and makes a sole entrant's prize exactly their own stake. AuditFailure would then report the live window as short, correctly, and the only way to "fix" it would be to add the Sponsored field and the three mechanisms that come with it — porting the complexity the package comment says not to port, to launder money that cannot exist.

SO opsPot — AND SINCE 2026-09-29 THE TWO REALMS AGREE ON THIS SHAPE RATHER THAN DIFFERING, which is the opposite of what this paragraph used to say. It read: "SO opsPot, WHICH IS A DELIBERATE DEVIATION FROM THE WEEKLY REALM'S CHOICE AND IS NAMED AS ONE ... the routing question ('where does it belong') is answered differently because the money has a different origin." The raffle's own no-claimant branch now sends a dead week's sponsorship to opsPot for exactly the reason given here — there is provably no address holding a unit of that week, so there is nobody to wrong — and it only pays holders when holders exist. Read the raffle's retireSponsorship: the branch is on its EARNED unit total and nothing else. The essential property of the fifth fix is preserved in full either way: money in the realm has a path out, here through WithdrawOps.

Panics if the window is not over yet (a live window may still sell its first ticket, which would make it a real draw whose buyers are entitled to everything in its pot), if there is no record for it, if it sold ANY tickets, if it holds nothing, or — defensively — if it is committed, resolved or abandoned.

Param

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/g1r4aw43kagaaquhegxw6xr346h9f2qu2ezf0jar/arcade/speed" -func "SweepDrawlessWindow" -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "onyx-1" -remote "https://rpc.onyx.testnets.gno.land" ADDRESSgnokey query -remote "https://rpc.onyx.testnets.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/g1r4aw43kagaaquhegxw6xr346h9f2qu2ezf0jar/arcade/speed" -func "SweepDrawlessWindow" -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "onyx-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.onyx.testnets.gno.land" call.tx