Realm
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv3
Overview
Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv3
Exported Functions
29
State Entries
27
Source Files
2
Total Package Entries
70
Exported Functions
29 exported functions
State
27 state entries
Source Code
FILES
bazaarv3.gno
go
1package bazaarv3
2
3import (
4 "chain"
5 "chain/banker"
6 "chain/runtime/unsafe"
7 "strconv"
8 "strings"
9
10 "gno.land/p/nt/avl/v0"
11)
12
13const (
14 pkgPath = "gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv3"
15 nativeDenom = "ugnot"
16 defaultLaunchFee = int64(1_000_000_000) // 1000 GNOT
17 maxNameLen = 64
18 maxImageLen = 200
19 maxSupplyBound = 1_000_000
20 maxRoyaltyBps = 1000
21 maxSymbolLen = 11
22 maxSlugLen = 11
23 minSlugLen = 2
24 defaultProtocolBps = int64(200) // 2% Magic Eden
25 maxProtocolBps = int64(500)
26)
27
28type collection struct {
29 slug string
30 name string
31 symbol string
32 cover string
33 pkg string
34 creator address
35 maxSupply int
36 mintPrice int64
37 minted int
38 royaltyBps int
39}
40
41type reservation struct {
42 who address
43 paid int64
44}
45
46var (
47 bySlug avl.Tree
48 byPkg avl.Tree
49 reserved avl.Tree
50 inited bool
51 admin address
52 self address
53 protocolSink address
54 launchFee int64
55 protocolBps int64
56 genesisPkg string
57 genesisSink address
58 genesisShareBps int64
59 holderDisc avl.Tree
60)
61
62func Init(cur realm) {
63 if inited {
64 panic("factory: already initialized")
65 }
66 if !cur.Previous().IsUserCall() {
67 panic("factory: EOA only")
68 }
69 admin = cur.Previous().Address()
70 protocolSink = admin
71 self = cur.Address()
72 bySlug = avl.Tree{}
73 byPkg = avl.Tree{}
74 reserved = avl.Tree{}
75 holderDisc = avl.Tree{}
76 genesisPkg = ""
77 genesisShareBps = 0
78 var z address
79 genesisSink = z
80 launchFee = defaultLaunchFee
81 protocolBps = defaultProtocolBps
82 inited = true
83 chain.Emit("Init", "admin", admin.String())
84}
85
86func Address() address {
87 return chain.PackageAddress(pkgPath)
88}
89
90func LaunchFee() int64 {
91 requireInit()
92 return launchFee
93}
94
95func Admin() string {
96 requireInit()
97 return admin.String()
98}
99
100func SetLaunchFee(cur realm, ugnot int64) {
101 requireAdmin(cur)
102 if ugnot < 0 {
103 panic("factory: launch fee")
104 }
105 launchFee = ugnot
106 chain.Emit("SetLaunchFee", "ugnot", strconv.FormatInt(ugnot, 10))
107}
108
109func ProtocolBps() int64 {
110 requireInit()
111 if protocolBps <= 0 {
112 return defaultProtocolBps
113 }
114 return protocolBps
115}
116
117func SetProtocolBps(cur realm, bps int64) {
118 requireAdmin(cur)
119 if bps < 0 || bps > maxProtocolBps {
120 panic("factory: protocol bps")
121 }
122 protocolBps = bps
123 chain.Emit("SetProtocolBps", "bps", strconv.FormatInt(bps, 10))
124}
125
126func ProtocolSink() address {
127 requireInit()
128 return protocolSink
129}
130
131func SetProtocolSink(cur realm, next address) {
132 requireAdmin(cur)
133 if !next.IsValid() {
134 panic("factory: invalid address")
135 }
136 if next == protocolSink {
137 panic("factory: already sink")
138 }
139 protocolSink = next
140 chain.Emit("SetProtocolSink", "addr", next.String())
141}
142
143func GenesisPkg() string {
144 requireInit()
145 return genesisPkg
146}
147
148func GenesisSink() address {
149 requireInit()
150 return genesisSink
151}
152
153func GenesisShareBps() int64 {
154 requireInit()
155 if genesisPkg == "" {
156 return 0
157 }
158 if genesisShareBps <= 0 {
159 return 1100
160 }
161 return genesisShareBps
162}
163
164func HolderDiscount(owner string) int64 {
165 requireInit()
166 if genesisPkg == "" {
167 return 0
168 }
169 v := holderDisc.Get(strings.ToLower(strings.TrimSpace(owner)))
170 if v == nil {
171 return 0
172 }
173 return v.(int64)
174}
175
176func AttachGenesis(cur realm) {
177 requireInit()
178 prev := cur.Previous()
179 if !prev.IsCode() {
180 panic("factory: genesis realm")
181 }
182 eoa := prev.Previous()
183 if !eoa.IsUserCall() || eoa.Address() != admin {
184 panic("factory: not admin")
185 }
186 pkg := prev.PkgPath()
187 if pkg == "" {
188 panic("factory: genesis pkg")
189 }
190 if genesisPkg != "" && genesisPkg != pkg {
191 panic("factory: genesis set")
192 }
193 genesisPkg = pkg
194 genesisSink = prev.Address()
195 if genesisShareBps <= 0 {
196 genesisShareBps = 1100
197 }
198 chain.Emit("AttachGenesis", "pkg", pkg)
199}
200
201func SetGenesis(cur realm, pkg string) {
202 requireAdmin(cur)
203 pkg = strings.TrimSpace(pkg)
204 if pkg == "" || !strings.HasPrefix(pkg, "gno.land/r/") {
205 panic("factory: genesis pkg")
206 }
207 genesisPkg = pkg
208 genesisSink = chain.PackageAddress(pkg)
209 if genesisShareBps <= 0 {
210 genesisShareBps = 1100
211 }
212 chain.Emit("SetGenesis", "pkg", pkg)
213}
214
215func NoteHolderDiscount(cur realm, owner string, bps int64) {
216 requireInit()
217 prev := cur.Previous()
218 if genesisPkg == "" || !prev.IsCode() || prev.PkgPath() != genesisPkg {
219 panic("factory: not genesis")
220 }
221 if bps < 0 {
222 panic("factory: discount")
223 }
224 who := strings.ToLower(strings.TrimSpace(owner))
225 if who == "" {
226 panic("factory: owner")
227 }
228 if bps == 0 {
229 holderDisc.Remove(who)
230 return
231 }
232 holderDisc.Set(who, bps)
233}
234
235func Register(cur realm, name, symbol, cover string, maxSupply int, mintPrice, royaltyBps int64) {
236 requireInit()
237 prev := cur.Previous()
238 if !prev.IsCode() {
239 panic("factory: collection realm")
240 }
241 creatorRlm := prev.Previous()
242 if !creatorRlm.IsUserCall() {
243 panic("factory: EOA creator")
244 }
245 pkg := prev.PkgPath()
246 slug := lastElem(pkg)
247 res := mustReserve(slug)
248 if creatorRlm.Address() != res.who {
249 panic("factory: reserved")
250 }
251 reserved.Remove(slug)
252 register(pkg, creatorRlm.Address(), name, symbol, cover, maxSupply, mintPrice, royaltyBps)
253}
254
255func Reserve(cur realm, slug string) {
256 requireInit()
257 if !cur.Previous().IsUserCall() {
258 panic("factory: EOA only")
259 }
260 slug = strings.ToLower(strings.TrimSpace(slug))
261 if !validSlug(slug) {
262 panic("factory: slug")
263 }
264 if bySlug.Has(slug) || reserved.Has(slug) {
265 panic("factory: slug taken")
266 }
267 paid := launchFee
268 takeFactorySend(paid)
269 reserved.Set(slug, &reservation{who: cur.Previous().Address(), paid: paid})
270 chain.Emit("Reserve", "slug", slug, "paid", strconv.FormatInt(paid, 10))
271}
272
273func CancelReserve(cur realm, slug string) {
274 requireInit()
275 if !cur.Previous().IsUserCall() {
276 panic("factory: EOA only")
277 }
278 slug = strings.ToLower(strings.TrimSpace(slug))
279 res := mustReserve(slug)
280 if res.who != cur.Previous().Address() {
281 panic("factory: not reserver")
282 }
283 if bySlug.Has(slug) {
284 panic("factory: already registered")
285 }
286 reserved.Remove(slug)
287 if res.paid > 0 {
288 bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
289 bk.SendCoins(self, res.who, chain.Coins{{Denom: nativeDenom, Amount: res.paid}})
290 }
291 chain.Emit("CancelReserve", "slug", slug, "paid", strconv.FormatInt(res.paid, 10))
292}
293
294func ReservedCreator(slug string) string {
295 requireInit()
296 slug = strings.ToLower(strings.TrimSpace(slug))
297 if !reserved.Has(slug) {
298 return ""
299 }
300 return reserved.Get(slug).(*reservation).who.String()
301}
302
303func takeFactorySend(want int64) {
304 sent := unsafe.OriginSend()
305 if want == 0 {
306 if len(sent) != 0 {
307 panic("factory: extra denoms")
308 }
309 return
310 }
311 if len(sent) != 1 || sent[0].Denom != nativeDenom || sent[0].Amount != want {
312 panic("factory: wrong payment")
313 }
314}
315
316func NoteMinted(cur realm, n int) {
317 requireInit()
318 if n < 0 {
319 panic("factory: minted")
320 }
321 prev := cur.Previous()
322 if !prev.IsCode() {
323 panic("factory: collection realm")
324 }
325 c := mustByPkg(prev.PkgPath())
326 c.minted = n
327 byPkg.Set(c.pkg, c)
328 bySlug.Set(c.slug, c)
329}
330
331func NoteRoyalty(cur realm, bps int) {
332 requireInit()
333 if bps < 0 || bps > maxRoyaltyBps {
334 panic("factory: royalty")
335 }
336 prev := cur.Previous()
337 if !prev.IsCode() {
338 panic("factory: collection realm")
339 }
340 c := mustByPkg(prev.PkgPath())
341 c.royaltyBps = bps
342 byPkg.Set(c.pkg, c)
343 bySlug.Set(c.slug, c)
344}
345
346func CollectionOf(slug string) string {
347 requireInit()
348 c := mustBySlug(slug)
349 return c.pkg
350}
351
352func CollectionAddr(slug string) string {
353 requireInit()
354 c := mustBySlug(slug)
355 return chain.PackageAddress(c.pkg).String()
356}
357
358func RoyaltyOf(slug string) int {
359 requireInit()
360 c := mustBySlug(slug)
361 return c.royaltyBps
362}
363
364func ListCollections() string {
365 requireInit()
366 out := ""
367 bySlug.Iterate("", "", func(_ string, value any) bool {
368 c := value.(*collection)
369 if out != "" {
370 out += "\n"
371 }
372 out += c.slug + "|" + c.name + "|" + c.cover + "|" + c.pkg + "|" + strconv.FormatInt(c.mintPrice, 10) + "|" + strconv.Itoa(c.maxSupply) + "|" + strconv.Itoa(c.minted) + "|" + c.creator.String() + "|" + chain.PackageAddress(c.pkg).String() + "|" + strconv.Itoa(c.royaltyBps)
373 return false
374 })
375 return out
376}
377
378func Withdraw(cur realm) {
379 requireAdmin(cur)
380 bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
381 ugnot := bk.GetCoins(self).AmountOf(nativeDenom)
382 locked := reservedPaid()
383 avail := ugnot - locked
384 if avail <= 0 {
385 panic("factory: no funds")
386 }
387 bk.SendCoins(self, admin, chain.Coins{{Denom: nativeDenom, Amount: avail}})
388 chain.Emit("Withdraw", "amount", strconv.FormatInt(avail, 10))
389}
390
391func TransferAdmin(cur realm, next address) {
392 requireAdmin(cur)
393 if !next.IsValid() {
394 panic("factory: invalid address")
395 }
396 if next == admin {
397 panic("factory: already admin")
398 }
399 admin = next
400 chain.Emit("TransferAdmin", "addr", next.String())
401}
402
403func Render(_ string) string {
404 if !inited {
405 return "# Bazaar factory\n\nCall Init first.\n"
406 }
407 out := "# Bazaar factory\n\n"
408 out += "- launch fee: " + strconv.FormatInt(launchFee, 10) + " ugnot\n"
409 out += "- collections: " + strconv.Itoa(bySlug.Size()) + "\n\n"
410 lines := ListCollections()
411 if lines == "" {
412 out += "_No collections._\n"
413 } else {
414 out += "```\n" + lines + "\n```\n"
415 }
416 return out
417}
418
419func register(pkg string, creator address, name, symbol, cover string, maxSupply int, mintPrice, royaltyBps int64) {
420 if !strings.HasPrefix(pkg, "gno.land/r/") {
421 panic("factory: pkg")
422 }
423 slug := lastElem(pkg)
424 if !validSlug(slug) {
425 panic("factory: slug")
426 }
427 if !validName(name) {
428 panic("factory: name")
429 }
430 if !validSymbol(symbol) {
431 panic("factory: symbol")
432 }
433 if !validImage(cover) {
434 panic("factory: cover")
435 }
436 if maxSupply < 0 || maxSupply > maxSupplyBound {
437 panic("factory: maxSupply")
438 }
439 if mintPrice < 0 {
440 panic("factory: mintPrice")
441 }
442 if royaltyBps < 0 || royaltyBps > maxRoyaltyBps {
443 panic("factory: royalty")
444 }
445 if bySlug.Has(slug) {
446 panic("factory: slug taken")
447 }
448 if byPkg.Has(pkg) {
449 panic("factory: pkg already registered")
450 }
451 c := &collection{
452 slug: slug,
453 name: name,
454 symbol: symbol,
455 cover: cover,
456 pkg: pkg,
457 creator: creator,
458 maxSupply: maxSupply,
459 mintPrice: mintPrice,
460 royaltyBps: int(royaltyBps),
461 }
462 bySlug.Set(slug, c)
463 byPkg.Set(pkg, c)
464 chain.Emit("Register", "slug", slug, "pkg", pkg, "creator", creator.String())
465}
466
467func requireInit() {
468 if !inited {
469 panic("factory: call Init first")
470 }
471}
472
473func requireAdmin(cur realm) {
474 requireInit()
475 if !cur.Previous().IsUserCall() {
476 panic("factory: EOA only")
477 }
478 if cur.Previous().Address() != admin {
479 panic("factory: not admin")
480 }
481}
482
483func mustReserve(slug string) *reservation {
484 v := reserved.Get(slug)
485 if v == nil {
486 panic("factory: not reserved")
487 }
488 return v.(*reservation)
489}
490
491func reservedPaid() int64 {
492 var n int64
493 reserved.Iterate("", "", func(_ string, value any) bool {
494 n += value.(*reservation).paid
495 return false
496 })
497 return n
498}
499
500func mustBySlug(slug string) *collection {
501 v := bySlug.Get(slug)
502 if v == nil {
503 panic("factory: unknown collection")
504 }
505 return v.(*collection)
506}
507
508func mustByPkg(pkg string) *collection {
509 v := byPkg.Get(pkg)
510 if v == nil {
511 panic("factory: unknown collection")
512 }
513 return v.(*collection)
514}
515
516func lastElem(pkg string) string {
517 i := strings.LastIndex(pkg, "/")
518 if i < 0 || i+1 >= len(pkg) {
519 panic("factory: pkg")
520 }
521 return pkg[i+1:]
522}
523
524func validSlug(s string) bool {
525 if len(s) < minSlugLen || len(s) > maxSlugLen {
526 return false
527 }
528 if s[0] < 'a' || s[0] > 'z' {
529 return false
530 }
531 return asciiSet(s, "abcdefghijklmnopqrstuvwxyz0123456789")
532}
533
534func validSymbol(s string) bool {
535 if len(s) < 1 || len(s) > maxSymbolLen {
536 return false
537 }
538 return asciiSet(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
539}
540
541func validName(name string) bool {
542 if len(name) < 1 || len(name) > maxNameLen {
543 return false
544 }
545 return !strings.ContainsAny(name, "\n\r|")
546}
547
548func validImage(u string) bool {
549 if u == "" {
550 return true
551 }
552 if len(u) > maxImageLen {
553 return false
554 }
555 if strings.ContainsAny(u, " \t\n\r|<>\"'`") {
556 return false
557 }
558 if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") {
559 return true
560 }
561 const prefix = "/samples/"
562 if !strings.HasPrefix(u, prefix) {
563 return false
564 }
565 rest := u[len(prefix):]
566 if rest == "" {
567 return false
568 }
569 return asciiSet(rest, "abcdefghijklmnopqrstuvwxyz0123456789._-")
570}
571
572func asciiSet(s, allowed string) bool {
573 for i := 0; i < len(s); i++ {
574 if !strings.ContainsRune(allowed, rune(s[i])) {
575 return false
576 }
577 }
578 return true
579}
580Raw Package Data
Raw JSON data