← Back to Realms

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

Raw Package Data

Raw JSON data