Realm
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol
Overview
Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gcol
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
gcol.gno
go
1package gcol
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)
14
15// Holder perk for Bazaar: col.Buy subtracts HolderDiscount(trader) from
16// factory.ProtocolBps() (highest FeeDiscount in that wallet). Every Genesis NFT
17// also has ShareWeight = FeeDiscount for the 11% protocol-fee pool (claim later).
18
19const (
20 maxImageLen = 200
21 maxDiscountBps = int64(150)
22 maxSupply = 1111
23 supplyCommon = 666
24 supplyUncommon = 222
25 supplyRare = 111
26 supplyEpic = 66
27 supplyLegend = 33
28 supplyMythic = 13
29 defaultImage = "https://bazaar-gno.vercel.app/gnomies.png"
30 shareBps = int64(1100) // 11% of protocol take on Buy
31)
32
33type item struct {
34 id int
35 owner address
36 name string
37 image string
38 rarity string
39 discountBps int64
40}
41
42var (
43 token *grc721.Token
44 ledger *grc721.PrivateLedger
45 metaView *metadata.Metadata
46 metaLed *metadata.Ledger
47 tokenSeq seqid.ID
48 items avl.Tree
49 inited bool
50 creator address
51 nextID int
52 mintedCommon int
53 mintedUncommon int
54 mintedRare int
55 mintedEpic int
56 mintedLegend int
57 mintedMythic int
58)
59
60func Init(cur realm, name, symbol string) {
61 if !cur.IsCurrent() {
62 panic("genesis: spoofed realm")
63 }
64 if !cur.Previous().IsUserCall() {
65 panic("genesis: EOA only")
66 }
67 if inited {
68 panic("genesis: already initialized")
69 }
70 tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)
71 token = tok
72 ledger = led
73 metaView, metaLed = metadata.NewMetadata(led)
74 creator = cur.Previous().Address()
75 inited = true
76 chain.Emit("Init", "creator", creator.String())
77
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
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
305}
306
307func canonRarity(rarity string) string {
308 switch strings.ToLower(strings.TrimSpace(rarity)) {
309 case "mythic":
310 return "mythic"
311 case "legendary":
312 return "legendary"
313 case "epic":
314 return "epic"
315 case "rare":
316 return "rare"
317 case "uncommon":
318 return "uncommon"
319 default:
320 return "common"
321 }
322}
323
324func takeRarity(rarity string) {
325 switch canonRarity(rarity) {
326 case "mythic":
327 if mintedMythic >= supplyMythic {
328 panic("genesis: rarity full")
329 }
330 mintedMythic++
331 case "legendary":
332 if mintedLegend >= supplyLegend {
333 panic("genesis: rarity full")
334 }
335 mintedLegend++
336 case "epic":
337 if mintedEpic >= supplyEpic {
338 panic("genesis: rarity full")
339 }
340 mintedEpic++
341 case "rare":
342 if mintedRare >= supplyRare {
343 panic("genesis: rarity full")
344 }
345 mintedRare++
346 case "uncommon":
347 if mintedUncommon >= supplyUncommon {
348 panic("genesis: rarity full")
349 }
350 mintedUncommon++
351 default:
352 if mintedCommon >= supplyCommon {
353 panic("genesis: rarity full")
354 }
355 mintedCommon++
356 }
357}
358
359func rarityDiscount(rarity string) int64 {
360 switch canonRarity(rarity) {
361 case "mythic":
362 return 150
363 case "legendary":
364 return 125
365 case "epic":
366 return 100
367 case "rare":
368 return 75
369 case "uncommon":
370 return 50
371 default:
372 return 25
373 }
374}
375
376func requireInit() {
377 if !inited {
378 panic("genesis: call Init first")
379 }
380}
381
382func requireUser(cur realm) address {
383 requireInit()
384 if !cur.IsCurrent() {
385 panic("genesis: spoofed realm")
386 }
387 if !cur.Previous().IsUserCall() {
388 panic("genesis: EOA only")
389 }
390 return cur.Previous().Address()
391}
392
393func parseTid(tid string) int {
394 id, err := strconv.Atoi(tid)
395 if err != nil || id < 1 {
396 panic("genesis: tid")
397 }
398 return id
399}
400
401func mustItem(id int) *item {
402 v := items.Get(strconv.Itoa(id))
403 if v == nil {
404 panic("genesis: unknown item")
405 }
406 return v.(*item)
407}
408
409func tokenJSON(name, image, rarity string, discount int64) string {
410 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)) + `}]}`
411 return "data:application/json;charset=utf-8," + pctURI(body)
412}
413
414func jsonStr(s string) string {
415 out := `"`
416 for _, r := range s {
417 switch r {
418 case '"':
419 out += `\"`
420 case '\\':
421 out += `\\`
422 default:
423 out += string(r)
424 }
425 }
426 return out + `"`
427}
428
429func pctURI(s string) string {
430 out := ""
431 for i := 0; i < len(s); i++ {
432 c := s[i]
433 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {
434 out += string(c)
435 continue
436 }
437 hex := "0123456789ABCDEF"
438 out += "%"
439 out += string([]byte{hex[c>>4], hex[c&15]})
440 }
441 return out
442}
443Raw Package Data
Raw JSON data