Realm
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol2
Overview
Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol2
Exported Functions
17
State Entries
27
Source Files
2
Total Package Entries
54
Exported Functions
17 exported functions
State
27 state entries
Source Code
FILES
gcol2.gno
go
1package gcol2
2
3import (
4 "chain"
5 "errors"
6 "strconv"
7 "strings"
8
9 "gno.land/p/nt/avl/v0"
10 "gno.land/p/nt/grc721/metadata/v0"
11 "gno.land/p/nt/grc721/v0"
12 "gno.land/p/nt/seqid/v0"
13 "gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gpush"
14)
15
16// Holder perk for Bazaar: col.Buy subtracts HolderDiscount(trader) from
17// factory.ProtocolBps() (highest FeeDiscount in that wallet). Every Genesis NFT
18// also has ShareWeight = FeeDiscount for the 11% protocol-fee pool (claim later).
19
20const (
21 maxImageLen = 200
22 maxDiscountBps = int64(150)
23 maxSupply = 1111
24 supplyCommon = 666
25 supplyUncommon = 222
26 supplyRare = 111
27 supplyEpic = 66
28 supplyLegend = 33
29 supplyMythic = 13
30 defaultImage = "https://bazaar-gno.vercel.app/gnomies.png"
31 shareBps = int64(1100) // 11% of protocol take on Buy
32)
33
34type item struct {
35 id int
36 owner address
37 name string
38 image string
39 rarity string
40 discountBps int64
41}
42
43var (
44 token *grc721.Token
45 ledger *grc721.PrivateLedger
46 metaView *metadata.Metadata
47 metaLed *metadata.Ledger
48 tokenSeq seqid.ID
49 items avl.Tree
50 inited bool
51 creator address
52 nextID int
53 mintedCommon int
54 mintedUncommon int
55 mintedRare int
56 mintedEpic int
57 mintedLegend int
58 mintedMythic int
59)
60
61func Init(cur realm, name, symbol string) {
62 if !cur.IsCurrent() {
63 panic("genesis: spoofed realm")
64 }
65 if !cur.Previous().IsUserCall() {
66 panic("genesis: EOA only")
67 }
68 if inited {
69 panic("genesis: already initialized")
70 }
71 tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
72 token = tok
73 ledger = led
74 metaView, metaLed = metadata.NewMetadata(led)
75 creator = cur.Previous().Address()
76 inited = true
77 chain.Emit("Init", "creator", creator.String())
78}
79
80func Mint(cur realm, name, image, rarity string, discountBps int64) int {
81 caller := requireUser(cur)
82 if caller != creator {
83 panic("genesis: not creator")
84 }
85 if name == "" || len(name) > grc721.MaxNameLen {
86 panic("genesis: name")
87 }
88 if image == "" {
89 image = defaultImage
90 }
91 if len(image) > maxImageLen {
92 panic("genesis: image")
93 }
94 d := discountBps
95 if d < 0 {
96 d = rarityDiscount(rarity)
97 }
98 if d > maxDiscountBps {
99 panic("genesis: discount")
100 }
101 if nextID >= maxSupply {
102 panic("genesis: sold out")
103 }
104 takeRarity(rarity)
105 nextID++
106 id := nextID
107 tid := grc721.TokenID(strconv.Itoa(id))
108 if err := ledger.Mint(caller, tid); err != nil {
109 panic("genesis: mint")
110 }
111 if err := metaLed.SetTokenURI(tid, tokenJSON(name, image, rarity, d)); err != nil {
112 panic("genesis: tokenURI")
113 }
114 if err := metaLed.SetTokenMetadata(tid, metadata.Data{
115 Name: name,
116 Description: name,
117 Image: image,
118 Attributes: []metadata.Trait{
119 {TraitType: "Rarity", Value: rarity},
120 {TraitType: "FeeDiscount", Value: strconv.FormatInt(d, 10)},
121 {TraitType: "ShareWeight", Value: strconv.FormatInt(d, 10)},
122 },
123 }); err != nil {
124 panic("genesis: metadata")
125 }
126 items.Set(strconv.Itoa(id), &item{id: id, owner: caller, name: name, image: image, rarity: rarity, discountBps: d})
127 gpush.Sync(cross(cur), caller.String(), HolderDiscount(caller.String()))
128 chain.Emit("Mint", "id", strconv.Itoa(id), "discount", strconv.FormatInt(d, 10))
129 return id
130}
131
132// HolderDiscount is the best FeeDiscount among Genesis NFTs currently in `owner`'s wallet (not listed/escrowed).
133func HolderDiscount(owner string) int64 {
134 if !inited {
135 return 0
136 }
137 who := address(owner)
138 if !who.IsValid() {
139 return 0
140 }
141 var best int64
142 items.Iterate("", "", func(_ string, value any) bool {
143 it := value.(*item)
144 if it.owner == who && it.discountBps > best {
145 best = it.discountBps
146 }
147 return false
148 })
149 return best
150}
151
152// ShareWeight sums FeeDiscount of every Genesis NFT in `owner`'s wallet (not listed/escrowed).
153func ShareWeight(owner string) int64 {
154 if !inited {
155 return 0
156 }
157 who := address(owner)
158 if !who.IsValid() {
159 return 0
160 }
161 var sum int64
162 items.Iterate("", "", func(_ string, value any) bool {
163 it := value.(*item)
164 if it.owner == who {
165 sum += it.discountBps
166 }
167 return false
168 })
169 return sum
170}
171
172func DiscountOf(tid string) int64 {
173 return mustItem(parseTid(tid)).discountBps
174}
175
176func ShareBps() int64 {
177 return shareBps
178}
179
180func MaxSupply() int {
181 return maxSupply
182}
183
184func TotalSupply() int {
185 requireInit()
186 return nextID
187}
188
189func SupplyOf(rarity string) int {
190 switch canonRarity(rarity) {
191 case "mythic":
192 return supplyMythic
193 case "legendary":
194 return supplyLegend
195 case "epic":
196 return supplyEpic
197 case "rare":
198 return supplyRare
199 case "uncommon":
200 return supplyUncommon
201 default:
202 return supplyCommon
203 }
204}
205
206func MintedOf(rarity string) int {
207 requireInit()
208 switch canonRarity(rarity) {
209 case "mythic":
210 return mintedMythic
211 case "legendary":
212 return mintedLegend
213 case "epic":
214 return mintedEpic
215 case "rare":
216 return mintedRare
217 case "uncommon":
218 return mintedUncommon
219 default:
220 return mintedCommon
221 }
222}
223
224func Name() string {
225 requireInit()
226 return token.GetName()
227}
228
229func Symbol() string {
230 requireInit()
231 return token.GetSymbol()
232}
233
234func BalanceOf(owner string) int64 {
235 requireInit()
236 who := address(owner)
237 if !who.IsValid() {
238 return 0
239 }
240 n, err := token.BalanceOf(who)
241 if err != nil {
242 return 0
243 }
244 return n
245}
246
247func OwnerOf(tid string) (address, error) {
248 requireInit()
249 id, err := strconv.Atoi(tid)
250 if err != nil || id < 1 {
251 return address(""), errors.New("genesis: tid")
252 }
253 v := items.Get(strconv.Itoa(id))
254 if v == nil {
255 return address(""), errors.New("genesis: unknown item")
256 }
257 return v.(*item).owner, nil
258}
259
260func TokenURI(tid string) string {
261 u, err := metaView.TokenURI(grc721.TokenID(tid))
262 if err != nil || u == "" {
263 it := mustItem(parseTid(tid))
264 return tokenJSON(it.name, it.image, it.rarity, it.discountBps)
265 }
266 return u
267}
268
269func TokensOf(owner string) string {
270 requireInit()
271 who := address(owner)
272 if !who.IsValid() {
273 return ""
274 }
275 out := ""
276 items.Iterate("", "", func(_ string, value any) bool {
277 it := value.(*item)
278 if it.owner != who {
279 return false
280 }
281 if out != "" {
282 out += ","
283 }
284 out += strconv.Itoa(it.id)
285 return false
286 })
287 return out
288}
289
290func TransferFrom(cur realm, from, to, tid string) {
291 caller := requireUser(cur)
292 id := parseTid(tid)
293 src := address(from)
294 dst := address(to)
295 if !src.IsValid() || !dst.IsValid() {
296 panic("genesis: addr")
297 }
298 if err := ledger.TransferFrom(caller, src, dst, grc721.TokenID(tid)); err != nil {
299 panic("genesis: transfer")
300 }
301 it := mustItem(id)
302 it.owner = dst
303 items.Set(strconv.Itoa(id), it)
304 gpush.Sync(cross(cur), from, HolderDiscount(from))
305 gpush.Sync(cross(cur), to, HolderDiscount(to))
306}
307
308func canonRarity(rarity string) string {
309 switch strings.ToLower(strings.TrimSpace(rarity)) {
310 case "mythic":
311 return "mythic"
312 case "legendary":
313 return "legendary"
314 case "epic":
315 return "epic"
316 case "rare":
317 return "rare"
318 case "uncommon":
319 return "uncommon"
320 default:
321 return "common"
322 }
323}
324
325func takeRarity(rarity string) {
326 switch canonRarity(rarity) {
327 case "mythic":
328 if mintedMythic >= supplyMythic {
329 panic("genesis: rarity full")
330 }
331 mintedMythic++
332 case "legendary":
333 if mintedLegend >= supplyLegend {
334 panic("genesis: rarity full")
335 }
336 mintedLegend++
337 case "epic":
338 if mintedEpic >= supplyEpic {
339 panic("genesis: rarity full")
340 }
341 mintedEpic++
342 case "rare":
343 if mintedRare >= supplyRare {
344 panic("genesis: rarity full")
345 }
346 mintedRare++
347 case "uncommon":
348 if mintedUncommon >= supplyUncommon {
349 panic("genesis: rarity full")
350 }
351 mintedUncommon++
352 default:
353 if mintedCommon >= supplyCommon {
354 panic("genesis: rarity full")
355 }
356 mintedCommon++
357 }
358}
359
360func rarityDiscount(rarity string) int64 {
361 switch canonRarity(rarity) {
362 case "mythic":
363 return 150
364 case "legendary":
365 return 125
366 case "epic":
367 return 100
368 case "rare":
369 return 75
370 case "uncommon":
371 return 50
372 default:
373 return 25
374 }
375}
376
377func requireInit() {
378 if !inited {
379 panic("genesis: call Init first")
380 }
381}
382
383func requireUser(cur realm) address {
384 requireInit()
385 if !cur.IsCurrent() {
386 panic("genesis: spoofed realm")
387 }
388 if !cur.Previous().IsUserCall() {
389 panic("genesis: EOA only")
390 }
391 return cur.Previous().Address()
392}
393
394func parseTid(tid string) int {
395 id, err := strconv.Atoi(tid)
396 if err != nil || id < 1 {
397 panic("genesis: tid")
398 }
399 return id
400}
401
402func mustItem(id int) *item {
403 v := items.Get(strconv.Itoa(id))
404 if v == nil {
405 panic("genesis: unknown item")
406 }
407 return v.(*item)
408}
409
410func tokenJSON(name, image, rarity string, discount int64) string {
411 body := `{"name":` + jsonStr(name) + `,"image":` + jsonStr(image) + `,"attributes":[{"trait_type":"Rarity","value":` + jsonStr(rarity) + `},{"trait_type":"FeeDiscount","value":` + jsonStr(strconv.FormatInt(discount, 10)) + `},{"trait_type":"ShareWeight","value":` + jsonStr(strconv.FormatInt(discount, 10)) + `}]}`
412 return "data:application/json;charset=utf-8," + pctURI(body)
413}
414
415func jsonStr(s string) string {
416 out := `"`
417 for _, r := range s {
418 switch r {
419 case '"':
420 out += `\"`
421 case '\\':
422 out += `\\`
423 default:
424 out += string(r)
425 }
426 }
427 return out + `"`
428}
429
430func pctURI(s string) string {
431 out := ""
432 for i := 0; i < len(s); i++ {
433 c := s[i]
434 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {
435 out += string(c)
436 continue
437 }
438 hex := "0123456789ABCDEF"
439 out += "%"
440 out += string([]byte{hex[c>>4], hex[c&15]})
441 }
442 return out
443}
444Raw Package Data
Raw JSON data