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

FundPotWithEntries

func FundPotWithEntries(cur realm, amount int64, holders []address, units []int64) int64
Ask AI Claude ChatGPT

FundPotWithEntries is FundPot plus the earned entries that rake bought, in ONE call, and it is callable only by r/splash/arcade/duel. Returns the total entry units credited.

READ THE BLOCK ABOVE duelRealmPkgPath FIRST: it has the defect, the three rules, why this door is privileged and why the bound rather than the guard is what makes it safe.

WHY ONE CALL AND NOT TWO. The money and the entries must land in the SAME week and the same transaction, or the pot identity and the ticket index could disagree about which week the duel's rake belonged to. A caller cannot deliver one without the other, cannot deliver entries worth more than the money, and cannot choose the week — currentWeek() decides, exactly as FundPot's does.

THE MONEY PATH IS FundPot'S, UNCHANGED AND NOT COPIED. This function calls FundPot directly (a non-crossing call within this realm, so cur.Previous() is still the duel and the pull is still caller-pays out of the duel's own allowance). Every refusal FundPot makes — non-positive amount, a committed or abandoned week, a short allowance, a beacon too stale to pin a new week's round — therefore applies here identically, and no second version of the sponsorship bookkeeping exists to drift from the first.

WHAT AN EARNED ENTRY IS, EXACTLY: one unit appended to this week's ticket index with NO spendOf record.

Example
 1APPENDED, NEVER INSERTED OR ADJUSTED. appendTickets is the same function Buy
 2uses, so an earned entry is a new index whose prefix starts at the whole
 3total at the moment it was appended. That is the frozen-ownership property
 4Commit's argument rests on (finding 10) and the reason fenwick rejects
 5negative deltas by design. Nothing here touches an existing index.
 6
 7AND NO spendOf RECORD, WHICH IS CORRECT AND IS NOT AN OVERSIGHT. The holder
 8paid no ticket money to this realm; what they paid was the duel's rake, and
 9that money is in this week's pot as Sponsored. So:
10
11  - Pot == sum(spendOf) + Sponsored is UNAFFECTED. Entries carry no money of
12    their own; the money term is FundPot's, and it is counted once.
13  - Refund pays spendOf only, so a holder of nothing but earned entries
14    reclaims nothing, which is right: they have no ticket money here to
15    reclaim, and their rake is in Sponsored.
16  - On an abandoned week THE SPONSORSHIP GOES TO THE EARNED-ENTRY HOLDERS, pro
17    rata, as a claim on that same week, and buyers reclaim their own gross. The
18    sentence that stood here was PRECISELY INVERTED and is kept because a reader
19    who has read this file before will be carrying it: "On an abandoned week
20    Sponsored goes to opsPot and buyers reclaim their spend; EARNED-ENTRY HOLDERS
21    GET NOTHING. Also right, and it is the same money-belongs-to-no-buyer rule
22    Refund already applies." Earned-entry holders are the INTENDED BENEFICIARIES —
23    the rake is theirs and they can recover it by no other route — and it is
24    BUYERS who get nothing from it, having contributed nothing to it and having
25    their whole ticket price back. AUDIT-2026-09-28.md findings 16, 31 and 34; read
26    retireSponsorship and ClaimDeadWeek. Only a week with NO earned holder at all
27    sends this money to opsPot.
28  - A WEEK CAN NOW HAVE TICKETS AND ZERO spendOf — every entry earned, none
29    bought. Commit requires tickets > 0 and gets it, Resolve pays the winner
30    the sponsorship, and that is precisely the intent: the flippers draw for
31    the pot their own rake built. SWEEPING SUCH A WEEK IS ITS SHAPE 2 AND FIRES ON
32    EXACTLY IT, once committed and once the grace window has passed with no draw.
33    The retired claim, which was wrong from the day finding 17 was implemented:
34    "SweepDrawlessWeek requires ZERO tickets and therefore correctly will not fire
35    on such a week — its sponsorship is a real prize now, not stranded money." The
36    ZERO-TICKET requirement is shape 1 only. Finding 17 added shape 2 for this very
37    week, because Resolve was otherwise its ONLY exit and no clock could rescue it —
38    and shape 2 then retires the sponsorship to the earned holders rather than
39    stranding it. Read SweepDrawlessWeek's two shapes.
40  - Finding 3's one-way Abandoned flag still covers the fact that refunds
41    cannot retire tickets. An earned entry is as un-retirable as a bought
42    one and is safe for the same reason: no draw over an abandoned week's
43    ticket set will ever happen.

THE HOLDERS ARE THE DUEL'S TO NAME AND THIS REALM DOES NOT SECOND-GUESS THEM, with one exception it cannot check and one it can. It cannot check that a holder is an EOA — there is no such predicate for a bare address on this toolchain — so THE DUEL credits only parties whose own Post or Accept was a direct user call, which is where finding 8's reasoning has to live for earned entries. It can and does check that every holder is a valid non-empty address and every unit count is positive, so a malformed batch fails loudly instead of appending an index nobody owns.

Panics if the caller is not the duel realm, if the two slices disagree in length, if either is empty, if any unit count is not positive, if any holder is invalid, or if the batch claims more units than the delivered money could have bought.

Params

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/v3" -func "FundPotWithEntries" -args $'' -args $'' -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/v3" -func "FundPotWithEntries" -args $'' -args $'' -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