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/v2 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

Refund

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

Refund returns the caller's own ticket money for a window nobody settled. PERMISSIONLESS, and self-service only: a caller can only ever reclaim their own record.

WHAT IT IS FOR IS LIVENESS, NOT LOSS. A window's pot must not be hostage to whether anybody spends ~17.0M gas relaying one drand round, and at an hourly cadence with pots of a few tenths of a GNOT that is not an exotic worry — IT IS THE EXPECTED OUTCOME OF A QUIET HOUR. That is the only failure left, it is unbounded in time, so the exit is A CLOCK: refunds open refundGraceBlocks (twelve hours) after Commit and not before.

THE PRECONDITION IS THEREFORE NOT THE COMPLEMENT OF Resolve's — the two OVERLAP for ever once the window opens, and that is fine and is handled by Abandoned. There is no state in which Resolve is impossible, so a refund gate that waited for one would never open at all.

AND THE EXIT IS ONE-WAY, WHICHEVER WAY IT GOES. Refund and Resolve are permanently mutually exclusive per window: the first refund that pays out sets Abandoned, after which Resolve and Capture refuse that window for ever and every later refund proceeds without consulting the beacon again. Read Draw.Abandoned for the theft this prevents — it is AUDIT-2026-09-28.md finding 3 and it carries more weight since the drand migration, not less, because a pinned round can be relayed by anyone at any time for ever.

EACH BUYER RECLAIMS EXACTLY THEIR OWN RECORDED SPEND, ONCE. The record is zeroed before the transfer, so a second call finds nothing and panics; and because Pot + Cut == sum(spendOf) for an unresolved window — WITH NO SPONSORSHIP TERM TO GET IN THE WAY, which is the whole simplification this realm buys by having no FundPot — the refunds can never total more than the realm holds against this window.

THE HOUSE FEE *IS* REFUNDED, SINCE 2026-09-29, AND THE PARAGRAPH THAT USED TO BE HERE SAID THE OPPOSITE. It read: "What a buyer gets back is their post-cut contribution — the part that was ever in the prize pot — not the gross ticket price. The 5% was taken at sale time and is already in the ops pot; clawing it back would mean the ops pot could go negative, or that this realm had to hold every cut in escrow against the chance of an unrelayed round." IT IS KEPT VERBATIM BECAUSE IT NAMES THE COST OF THE FIX AND THE FIX PAID IT: the realm now DOES hold every fee in escrow per window (Draw.Cut), which is the second option that sentence dismissed, and the ops pot cannot go negative because nothing credits it until a draw succeeds.

WHAT A BUYER GETS BACK IS THEREFORE THE WHOLE TICKET PRICE. A refunded window is a window that did not run, and the fee was a charge for a draw that never happened. This is a REDUCTION IN HOUSE REVENUE in the failure case, which needs no permission and is the safe direction. AUDIT-2026-09-28.md finding 25.

A WINDOW THAT WAS NEVER COMMITTED IS NOT REFUNDABLE HERE, and needs no separate path — but only because Commit TAKES THE WINDOW AS AN ARGUMENT and any window that is over can be committed by anyone at any time. So a buyer holding a stranded window's tickets names that window to Commit themselves, and then either Resolves it (the normal outcome, once somebody relays the round) or refunds it here twelve hours later. Before Commit took that argument the equivalent sentence in the raffle was simply FALSE, and that was AUDIT-2026-09-28.md finding 1.

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/v2" -func "Refund" -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/v2" -func "Refund" -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