← Back to Realms

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}
167

Raw Package Data

Raw JSON data