ClaimSponsorRefund
Ask AI
ClaimDeadWeek pays the caller their pro-rata share of the sponsorship that a DEAD week's draw never got to pay out, and returns the amount. PERMISSIONLESS, self-service only — a caller can only ever claim their own share — and it is the EXIT for the claim term retireSponsorship creates. AUDIT-2026-09-28.md finding 31.
WHY IT IS A NEW ENTRY POINT AT ALL, WHICH IS THE ONE QUESTION WORTH ANSWERING HERE. The cheaper design is to fold each holder's share into their spendOf and let the existing, already-audited Refund be the exit — no new function, no new term, no new tree. THAT DOES NOT WORK, AND THE ARITHMETIC IS WHY RATHER THAN A JUDGEMENT CALL:
Example
1Refund re-derives the house fee it must give back as
2 cutPart = floor(gross * HouseCutBps / bpsDenom)
3and decrements d.Cut by it. That derivation is EXACT — see cutPerTicket's build guard —
4only because a spendOf is always a whole number of ticket prices, so its floor is
5precisely the sum of the cuts those purchases booked.
6
7A sponsorship share booked NO CUT. Add a share s to a buyer whose gross was
8g = n * TicketPrice and Refund derives floor((g+s)/20) = n*500_000 + floor(s/20), while
9d.Cut only ever received n*500_000. d.Cut goes NEGATIVE by floor(s/20) and Refund's own
10"refund would overdraw the week's escrowed fee" panic fires — so the buyer cannot
11reclaim their own ticket money at all. Worse in the pure-earned case: a holder with no
12purchase gets gross s against a week whose Cut is 0, and floor(s/20) is stolen from
13nothing. THE FIX FOR A FUND-STRANDING DEFECT WOULD HAVE CREATED ONE.
14
15The identity itself survives — Refund removes `amount` from both sides whatever the
16rounding — which is exactly what makes this dangerous: checkInvariant and AuditFailure
17stay green right up to the panic. It is the Cut term, not the identity, that breaks.
SO THE CLAIM IS A SEPARATE TERM WITH ITS OWN EXIT, AND IT CARRIES NO FEE BY CONSTRUCTION: sponsorship never paid a house cut on the way in and this pays none on the way out. The term appears in checkInvariant (heldClaims) and in AuditFailure (per week, and in the aggregate, and re-derived against claimedOf by deadWeekClaimFailure), which is the price of not reusing Refund and is paid in full.
IT HAS NO CLOCK, NO BEACON READ AND NO REFUSABLE DEPENDENCY, deliberately. By the time a claim exists the week is already abandoned — every gate that had to be waited out (Refund's grace window, or SweepDrawlessWeek's) was waited out before retireSponsorship ran — so a second wait would only be a second thing that could go wrong on a payout path. Compare ReclaimStrandedCarry, which this replaces: that needed a beacon test AND a three-week long stop because it was trying to find a destination. There is nothing to find.
IT IS O(log N) AND ALL-OR-NOTHING PER HOLDER. The share is derived from the rate and the holder's unit weight at call time rather than stored per holder at abandonment, which is what keeps the abandonment transition constant-time (read retireSponsorship). The weight cannot have changed since: an abandoned week is always a past week, and Buy and FundPotWithEntries only ever touch currentWeek(). One claimedOf entry therefore settles a holder for ever, and a second call finds it and refuses.
A REFUNDED BUYER MAY STILL CLAIM, AND THAT IS CORRECT, NOT A LOOPHOLE. Refund returns the gross they paid for their tickets; this returns their share of money that was never theirs to pay and never anybody's to win. The two are different money with different owners, and the week's identity keeps them apart — spendOf on one side, Claims on the other. A holder with earned entries and no purchase has a claim here and nothing to refund, which is the whole point of finding 15's players being paid.
BOOKS FIRST, TRANSFER SECOND, as in Resolve, Refund and WithdrawOps.
Panics if there is no record for the week, if that week never distributed anything, if the caller holds no units in it, or if the caller has already claimed. ClaimSponsorRefund returns a sponsor's payment from a week that died without drawing. It pays the GROSS — the house's 5% included — because the fee was only ever earned by a draw actually running, and this draw never ran.
THE OWNER'S RULING, 2026-09-30: a sponsor's money is "either returned to sponsor or rolls over until a draw happens". Rolling forward is what AUDIT-2026-09-28.md findings 28 and 31 removed — the caller chose which week it landed in by choosing when to call, and could aim it at a draw they already dominated. So it is returned, and it is PULLED rather than pushed: paying up to maxSponsorsPerWeek addresses inside retireSponsorship would put those transfers on the critical path of a buyer's own Refund, where one failing recipient could block the week.
IT DOES NOT COMPETE WITH ClaimDeadWeek. That pays earned-entry holders out of the duel's OWNERLESS rake; this pays named sponsors out of money that never entered that pool, because retireSponsorship carves it out first. The two draw on different halves of the same pot and neither can reach the other's.
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 "ClaimSponsorRefund" -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 "ClaimSponsorRefund" -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