Realm
gno.land/r/g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems/gems/campaign
Overview
Realm Path
gno.land/r/g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems/gems/campaign
Exported Functions
9
State Entries
5
Source Files
2
Total Package Entries
23
Exported Functions
9 exported functions
State
5 state entries
Source Code
FILES
campaign.gno
go
1// Package campaign is a near-verbatim copy of the real gems6/campaign
2// realm, redeployed here under the test address so this whole
3// reveal-delay + gemsartfab prototype is self-contained and testable
4// without touching the live gems6 deployment or needing the vesting
5// wallet's own key. Logic is unchanged from the original -- see that
6// package's own doc comments for the full design rationale (why a
7// dedicated prunable realm instead of a Merkle root, the stacking rule
8// in grantOne, etc.).
9package campaign
10
11// Storage (2026-09-26): every grant lives in ONE map, packed as a single
12// int64 (remainingUses*packBase + bps), plus one slice of wallets for
13// EntryAt's stable order. The first version kept an avl.Tree of *entry
14// pointers: every wallet became its own tree node plus entry object, about
15// 2.9 KB each on a local chain (0.29 GNOT per wallet, ~85 GNOT for the
16// 284-wallet campaign). A map and a slice are stored as a few objects
17// whose size grows by roughly the key and value per wallet.
18
19import (
20 unsaferealm "chain/runtime/unsafe"
21 "strings"
22)
23
24const packBase = 10001 // bps is 0..10000
25
26var (
27 collectionOwner address = "g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems"
28 approvedConsumer string
29 grants = map[string]int64{} // wallet -> remainingUses*packBase + bps
30 order []string // wallets with a grant, for EntryAt
31)
32
33func pack(bps, uses int64) int64 { return uses*packBase + bps }
34func unpack(v int64) (bps, uses int64) { return v % packBase, v / packBase }
35
36func callerAddress() address {
37 return unsaferealm.PreviousRealm().Address()
38}
39
40func assertOwner() {
41 if callerAddress() != collectionOwner {
42 panic("unauthorized: caller is not the collection owner")
43 }
44}
45
46func SetApprovedConsumer(cur realm, pkgpath string) {
47 assertOwner()
48 approvedConsumer = pkgpath
49}
50
51func validateBps(bps int64) {
52 if bps < 0 || bps > 10000 {
53 panic("bps must be between 0 and 10000")
54 }
55}
56
57func validateMaxUses(maxUses int64) {
58 if maxUses <= 0 {
59 panic("maxUses must be positive")
60 }
61}
62
63// grantOne adds uses to a wallet; a second grant stacks its uses and keeps
64// the better (lower) bps.
65func grantOne(wallet string, bps int64, maxUses int64) {
66 if v, ok := grants[wallet]; ok {
67 oldBps, uses := unpack(v)
68 if oldBps < bps {
69 bps = oldBps
70 }
71 grants[wallet] = pack(bps, uses+maxUses)
72 return
73 }
74 grants[wallet] = pack(bps, maxUses)
75 order = append(order, wallet)
76}
77
78func removeOne(wallet string) {
79 if _, ok := grants[wallet]; !ok {
80 return
81 }
82 delete(grants, wallet)
83 for i, w := range order {
84 if w == wallet {
85 order[i] = order[len(order)-1]
86 order = order[:len(order)-1]
87 break
88 }
89 }
90}
91
92func AddWallet(cur realm, wallet address, bps int64, maxUses int64) {
93 assertOwner()
94 validateBps(bps)
95 validateMaxUses(maxUses)
96 grantOne(wallet.String(), bps, maxUses)
97}
98
99func AddWalletsBatch(cur realm, walletsCSV string, bps int64, maxUses int64) {
100 assertOwner()
101 validateBps(bps)
102 validateMaxUses(maxUses)
103 for _, w := range strings.Split(walletsCSV, ",") {
104 w = strings.TrimSpace(w)
105 if w == "" {
106 continue
107 }
108 grantOne(w, bps, maxUses)
109 }
110}
111
112func RemoveWallet(cur realm, wallet address) {
113 assertOwner()
114 removeOne(wallet.String())
115}
116
117func RemoveWalletsBatch(cur realm, walletsCSV string) {
118 assertOwner()
119 for _, w := range strings.Split(walletsCSV, ",") {
120 w = strings.TrimSpace(w)
121 if w == "" {
122 continue
123 }
124 removeOne(w)
125 }
126}
127
128func assertCallerIsApprovedConsumer(cur realm) {
129 caller := cur.Previous().PkgPath()
130 if approvedConsumer == "" || caller != approvedConsumer {
131 panic("unauthorized: caller is not the approved consumer realm")
132 }
133}
134
135func Peek(wallet address) (bps int64, remainingUses int64, ok bool) {
136 v, has := grants[wallet.String()]
137 if !has {
138 return 0, 0, false
139 }
140 bps, remainingUses = unpack(v)
141 return bps, remainingUses, true
142}
143
144func ConsumeDiscount(cur realm, wallet address) (bps int64, ok bool) {
145 assertCallerIsApprovedConsumer(cur)
146 w := wallet.String()
147 v, has := grants[w]
148 if !has {
149 return 0, false
150 }
151 bps, uses := unpack(v)
152 if uses <= 1 {
153 removeOne(w)
154 } else {
155 grants[w] = pack(bps, uses-1)
156 }
157 return bps, true
158}
159
160func Count() int64 { return int64(len(order)) }
161
162func EntryAt(index int) (wallet string, bps int64, remainingUses int64) {
163 wallet = order[index]
164 bps, remainingUses = unpack(grants[wallet])
165 return wallet, bps, remainingUses
166}
167Raw Package Data
Raw JSON data