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

Resolve

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

Resolve picks the window's winner and PAYS THEIR WALLET. PERMISSIONLESS.

The whole pot leaves this realm in a single wugnot.Transfer to the winner's address. No internal balance is credited, there is nothing left to claim afterwards, and no buyer's money is held past the window they bought into.

winner = ownerOfUnit(randomness mod FrozenTotal). FrozenTotal, NOT the live ticket total: recomputing it here would reopen the buy-the-winning-unit attack described on Commit.

The pot pays out UNTAXED. The house cut was already taken on the way in, at sale time; taxing the payout as well would be charging twice for the same ticket.

RANDOMNESS SOURCE: a captured value if there is one, otherwise the beacon directly — so the happy path needs no extra transaction.

THERE IS ONE FAILURE MESSAGE AND THERE CAN ONLY BE ONE. "Not relayed yet" is always a wait-and-retry, never "this draw is dead": no round is ever unobtainable, so anybody who sees that message can make it stop being true by relaying the round themselves for ~17.0M gas. A realm that offered a second, terminal message would be steering buyers into refunds they do not need.

THERE IS NO HEIGHT GATE HERE AND THERE MUST NEVER BE ONE. refundGraceBlocks does not appear in this function and CommittedAt is not read: the grace window is a FLOOR ON BUYER ACCESS, NOT A DEADLINE ON THE DRAW. It only ever opens Refund; it never closes Resolve. An honest relay that arrives a day after Commit, before anybody has refunded, should still pay the winner it always would have, over exactly the FrozenTotal it was frozen with. Adding a height deadline here would manufacture a second class of dead window — past the grace, relayed, and resolvable by nobody — which is the very shape of defect the grace window exists to remove.

WHAT DOES CLOSE THIS FUNCTION IS Abandoned, and that is enough. Once the grace window has opened, Resolve and Refund are both available and the first one called wins: a refund sets Abandoned and this function refuses for ever, a resolve pays the winner and Refund's Resolved check refuses for ever. NEITHER ORDERING CAN PAY THE SAME UGNOT TWICE, which is the property that matters. The fairness cost of that race — a buyer who can see they did not win refunds instead, annulling the draw — is AUDIT-2026-09-28.md finding 7's recorded residual, and it is CHEAPER HERE THAN THERE because the window that produces it is twelve hours rather than three weeks. It is derived and discussed at refundGraceBlocks rather than buried here.

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 "Resolve" -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 "Resolve" -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