← Back to Realms

Realm

gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv5

Overview

Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv5
Exported Functions
32
State Entries
28
Source Files
2
Total Package Entries
76

Exported Functions

32 exported functions

State

28 state entries

Source Code

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

Raw Package Data

Raw JSON data