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.

raffle/v4 package

Overview

Package raffle is the SPLASH weekly ticket raffle.

WHAT THIS REPLACES, AND WHY IT IS A SEPARATE REALM.

r/splash/game is a permanent-deposit yield product: you deposit GNOT, you can never withdraw, your position size IS your ticket count, and winning the weekly draw ADDS the pot to your locked position rather than paying your wallet. Its raffle had to live inside that realm because the draw mutated three pieces of that realm's state atomically (see the long comment at the top of r/splash/game/raffle.gno).

This realm sells TICKETS, not positions. A ticket costs TicketPrice and buys exactly unitsPerTicket units of one week's draw — one ticket was one unit until 2026-09-29, and EntryUnitPrice says why the two numbers had to come apart; the pot pays out to the winner's WALLET; and nothing a buyer paid is held beyond the week they bought into. Because there is no position, no yield index and no permanent deposit, the draw needs nothing from r/splash/game and this realm holds no authority over it, over r/splash/beacon, or over r/gnoland/wugnot.

THERE IS EXACTLY ONE PACKAGE-PATH GUARD IN THIS FILE, AND UNTIL 2026-09-29 THERE WERE NONE. The sentence that used to be here — "there is no package-path guard anywhere in this file and nothing for one to protect" — is no longer true and is not quietly deleted. FundPotWithEntries admits r/splash/arcade/duel and nobody else, because duel play now EARNS entries in the live week's draw (AUDIT-2026-09-28.md finding 15). Read that function in full before touching it: it is the only privileged door in this product, it is the shape r/splash/game declined to add five times, and what keeps it small is that everything it can do is bounded by the money delivered through it in the same call. Every OTHER entry point here is still permissionless and can still only do the one thing the week's state machine allows next.

THE MECHANICS ARE r/splash/game/raffle.gno's, DELIBERATELY UNCHANGED: the week number derived from the global clock, the Commit/Capture/Resolve three-phase cycle, and above all the FROZEN-TOTAL property. Read Commit below before changing anything: freezing the ticket total is what stops a buyer from waiting for the randomness to publish and then buying exactly the winning ticket index, and that attack was live in an earlier version of the old realm.

ONE MECHANIC IS NOT THE OLD REALM'S ANY MORE: WHICH ROUND DECIDES A WEEK. The old realm — and this one until 2026-09-28 — bound Round = beacon.Latest() + 1 inside Commit, which handed the round choice to whoever called Commit, AFTER the week's ticket set was already frozen and public. The publisher's key can sign any round at will and beacon.Publish accepts rounds in any order, so that was a deterministic, risk-free win for the key holder. The round is now PINNED WHEN THE WEEK'S RECORD IS CREATED — before the first ticket of that week exists — and is never written again. See getOrCreateDraw and pinOffsetRounds.

THAT DOES NOT MAKE THE DRAW TRUSTLESS, and nothing in this file should be read as claiming it does. The beacon's key holder still knows every round's randomness in advance, so they can still rig a week by choosing the MODULUS — buying last, just before the week closes, to move FrozenTotal and the unit ranges. Pinning removes the round-selection freedom, which was the cheap, deniable and exact version of the attack; the residual one leaves an on-chain trace as a suspicious late purchase. THE ARCHITECTURAL PROBLEM REMAINS OPEN and is the owner's decision: closing it needs an externally-controlled beacon or a VRF. See AUDIT-2026-09-28.md findings 4 and 10.

THE ARCADE FEEDS THIS POT, AND SINCE 2026-09-29 IT ALSO FILLS THE DRAW. r/splash/arcade/duel sends its rake here, so every coin flip makes this week's prize bigger — that is what makes the duel and the raffle one product instead of two contracts. The rake itself still buys no tickets and no winning chance: it is tracked separately as Draw.Sponsored, because the refund safety property is the identity Pot == sum(spends) + Sponsored and house money belongs to no buyer. Read Draw.Sponsored and FundPot together before changing either.

WHAT CHANGED IS THAT THE PLAYERS WHO PAID THAT RAKE NOW ARRIVE WITH IT. Until 2026-09-29 the money came and the entrants did not, so a week of busy coin flipping and no ticket sales could be won outright by one cheap ticket bought in its final block — the whole pot, to somebody who had contributed none of it. FundPotWithEntries is the fix: the duel delivers the same rake and, in the same call, the two players of each settled duel are credited entries worth EXACTLY what that rake would have bought at EntryUnitPrice. The money and the entrants now arrive together. Earned entries carry ticket weight and NO spendOf record, which is consistent with every identity in this file and is spelled out at FundPotWithEntries. AUDIT-2026-09-28.md finding 15.

WHAT IS NEW, and could not have existed in the old design: Refund. There, the pot was accrued protocol fees and nobody's stake was ever at risk, so an unresolvable draw could simply wait forever. Here the pot is buyers' own money, so a week nobody settles must have a way back out. SINCE THE DRAND MIGRATION THAT IS A LIVENESS EXIT AND NOT A LOSS EXIT — no round is ever unobtainable, so what Refund guards against is nobody bothering to relay one. See Refund.

Function

SweepDrawlessWeek

func SweepDrawlessWeek(cur realm, week int64)
Ask AI Claude ChatGPT

SweepDrawlessWeek rolls a FINISHED week's sponsorship forward into the live week's pot, when nobody ever bought a ticket in it. PERMISSIONLESS, like Commit.

THERE ARE TWO SWEEPABLE SHAPES SINCE 2026-09-29 AND THE SECOND HAS A CLOCK ON IT: a week with NO TICKETS AT ALL (the original drawless week, sweepable as soon as it is over) and a week with TICKETS BUT NO SPEND — every entry earned by duel play — which is sweepable only once it is committed and refundGraceElapsed has gone true, and which is marked Abandoned when it is. The full reasoning, including why "zero spend" alone would have been a fund-destroying gate, is in the body above the shape test. AUDIT-2026-09-28.md finding 17.

FINDING 15 MADE THIS FUNCTION MUCH HARDER TO REACH AND IT IS DELIBERATELY KEPT. A week of duel rake used to end with sponsorship and a zero ticket total, which is exactly the state this function exists for. Now the duel's deliveries bring ENTRIES with them (FundPotWithEntries), so such a week normally has tickets, is a real draw, and this function correctly refuses it — its sponsorship is a prize somebody can win rather than money nobody can reach. The states that still land here are real and small: a rake too small to buy one whole entry (rake < EntryUnitPrice), a duel both of whose parties played from realm code and so earned nothing, and any plain FundPot sponsorship from a caller that is not the duel. DO NOT DELETE IT ON THE GROUNDS THAT THE COMMON CASE IS GONE: every one of those states still strands money without it, and that was the sixth of this product's "money in, no way out" findings.

THE DEFECT IT CLOSES. FundPot credits the live week and creates that week's record if it does not exist yet, so a week in which duels were collected but nobody ever bought a raffle ticket ends the week holding sponsorship in a Draw with a zero ticket total. Commit rightly refuses that week (no tickets, no unit to draw, no owner to pay), Resolve and Refund both require Committed, and FundPot only ever funds the live week — so before this function that sponsorship was unreachable by EVERY path in this realm, permanently, while heldPots went on counting it and checkInvariant and AuditFailure stayed green over money that was gone. That is the same class of defect as the 2026-09-28 audit's first and most serious finding, and it is not an exotic state: a quiet raffle week with an active coin flip is an ordinary Tuesday.

WHERE THE MONEY GOES, AND WHY NOT opsPot, AND WHY IT DOES NOT GO ANYWHERE AT ALL. It goes BACK TO THE PEOPLE WHO HOLD UNITS IN THIS WEEK, pro rata — and for shape 1, which has no unit holders whatsoever, it goes to opsPot because there is provably nobody else. Read retireSponsorship; the branch this function takes is exactly `pureEarned` and is stated again at the call site. This money is the PLAYERS' side of the duel rake (see r/splash/arcade/duel's Collect: the ops share went to r/splash/ops already, and what arrives here is deliberately the rest), so on shape 2 it lands back on the duel players who generated it.

TWO SENTENCES THIS REPLACES, IN ORDER, BECAUSE THE SEQUENCE IS THE LESSON. First: "So it rolls forward to the live week's pot" — the live week being whichever week the caller chose to fire in (finding 28). Then: "It marks the week and registers it (markCarryPending); the next week record to be created absorbs it at its own birth... to a week nobody in this transaction can name" — except that the caller who opens that week is the caller who calls Buy, so they could name it after all, and be its only entrant (finding 31). Both sentences described a destination. There is none now.

AND REFUND NOW AGREES, WHICH IT DID NOT UNTIL 2026-09-29. This paragraph used to end "Contrast Refund, which moves an abandoned week's sponsorship to opsPot: there the week HAD entrants and they are being paid back individually, so there is no prize left for the sponsorship to enlarge." THE CONTRAST WAS THE DEFECT. Two functions in one file moved the same field's money to opposite destinations, and since earned entries exist an abandoned week's "entrants" are mostly duel players with no spendOf to be paid back at all — so the rationale did not hold either. One ticket plus the grace window turned the whole argument into a cheap transfer of a week of rake to the house. AUDIT-2026-09-28.md finding 16. Refund's transition now retires the sponsorship exactly as this function does, and retireSponsorship is the shared step — a step with no destination in it.

THIS FUNCTION STILL DIFFERS FROM REFUND IN ONE WAY, AND MUST, BUT THE DIFFERENCE IS NOW A CLOCK RATHER THAN A DESTINATION. It may REFUSE — the live-week rule, the shape tests, and the grace window each shape must wait out — because a dead week's sponsorship has no claimant and no deadline, so a refusal costs nobody anything and any caller may retry for ever. Refund may not refuse, because a buyer is waiting; and since neither function looks for a destination any more, there is no longer any state in which Refund COULD be refused for want of one. That is strictly stronger than both fallbacks it replaced — the immediate opsPot bank of finding 16 and ReclaimStrandedCarry's three-week long stop of finding 28 — and both of them are now deleted rather than relocated, because a claim on the week the money is already in cannot fail to find anything.

THE DESIGN TENSION, RESOLVED EXPLICITLY SO IT IS NOT "FIXED" BACK. This realm's stated principle is that THERE IS NO ROLLOVER, so a drawless week must not exist — and Commit still enforces exactly that, unchanged. That principle is about TICKET HOLDERS' pots: it stops a week's prize being deferred away from the people who entered that week and paid for it. Rolling SPONSORSHIP forward from a week that had no entrants whatsoever does not violate it, because there was nobody to defer anything away from — no ticket was sold, so no buyer's expectation of this week's prize exists to be broken. Do not "restore consistency" by deleting this function or by letting Commit accept a zero total; the second would reopen the drawless-draw hole, and the first would reopen the stranded-money hole.

NO WUGNOT MOVES HERE, and a reader looking for the missing Transfer should stop looking. The tokens are already inside this realm — FundPot pulled them in when the dead week was sponsored — so this is PURE BOOKKEEPING between two weeks of the same realm. The realm's wugnot balance is identical before and after; heldPots and heldSponsored each lose and regain exactly the same amount, which is why checkInvariant's equality still holds at the end.

Panics if the week is not over yet (a live week may still sell a ticket, which would make it a real draw), if there is no record for it, if anybody BOUGHT a ticket in it, if it holds no sponsorship, if it is resolved or already abandoned, if its entries are all earned and its draw can still happen (uncommitted, or inside the grace window), or — defensively — if a ticketless week is somehow committed.

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/raffle/v4" -func "SweepDrawlessWeek" -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/raffle/v4" -func "SweepDrawlessWeek" -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