← Back to Realms

Realm

gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv4

Overview

Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv4
Exported Functions
31
State Entries
28
Source Files
2
Total Package Entries
73

Exported Functions

31 exported functions

State

28 state entries

Source Code

FILES
bazaarv4.gno
go
1package bazaarv4
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/bazaarv4"
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	paid := launchFee
290	takeFactorySend(paid)
291	reserved.Set(slug, &reservation{who: cur.Previous().Address(), paid: paid})
292	chain.Emit("Reserve", "slug", slug, "paid", strconv.FormatInt(paid, 10))
293}
294
295func CancelReserve(cur realm, slug string) {
296	requireInit()
297	if !cur.Previous().IsUserCall() {
298		panic("factory: EOA only")
299	}
300	slug = strings.ToLower(strings.TrimSpace(slug))
301	res := mustReserve(slug)
302	if res.who != cur.Previous().Address() {
303		panic("factory: not reserver")
304	}
305	if bySlug.Has(slug) {
306		panic("factory: already registered")
307	}
308	reserved.Remove(slug)
309	if res.paid > 0 {
310		bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
311		bk.SendCoins(self, res.who, chain.Coins{{Denom: nativeDenom, Amount: res.paid}})
312	}
313	chain.Emit("CancelReserve", "slug", slug, "paid", strconv.FormatInt(res.paid, 10))
314}
315
316func ReservedCreator(slug string) string {
317	requireInit()
318	slug = strings.ToLower(strings.TrimSpace(slug))
319	if !reserved.Has(slug) {
320		return ""
321	}
322	return reserved.Get(slug).(*reservation).who.String()
323}
324
325func takeFactorySend(want int64) {
326	sent := unsafe.OriginSend()
327	if want == 0 {
328		if len(sent) != 0 {
329			panic("factory: extra denoms")
330		}
331		return
332	}
333	if len(sent) != 1 || sent[0].Denom != nativeDenom || sent[0].Amount != want {
334		panic("factory: wrong payment")
335	}
336}
337
338func NoteMinted(cur realm, n int) {
339	requireInit()
340	if n < 0 {
341		panic("factory: minted")
342	}
343	prev := cur.Previous()
344	if !prev.IsCode() {
345		panic("factory: collection realm")
346	}
347	c := mustByPkg(prev.PkgPath())
348	c.minted = n
349	byPkg.Set(c.pkg, c)
350	bySlug.Set(c.slug, c)
351}
352
353func NoteRoyalty(cur realm, bps int) {
354	requireInit()
355	if bps < 0 || bps > maxRoyaltyBps {
356		panic("factory: royalty")
357	}
358	prev := cur.Previous()
359	if !prev.IsCode() {
360		panic("factory: collection realm")
361	}
362	c := mustByPkg(prev.PkgPath())
363	c.royaltyBps = bps
364	byPkg.Set(c.pkg, c)
365	bySlug.Set(c.slug, c)
366}
367
368func CollectionOf(slug string) string {
369	requireInit()
370	c := mustBySlug(slug)
371	return c.pkg
372}
373
374func CollectionAddr(slug string) string {
375	requireInit()
376	c := mustBySlug(slug)
377	return chain.PackageAddress(c.pkg).String()
378}
379
380func RoyaltyOf(slug string) int {
381	requireInit()
382	c := mustBySlug(slug)
383	return c.royaltyBps
384}
385
386func ListCollections() string {
387	requireInit()
388	out := ""
389	bySlug.Iterate("", "", func(_ string, value any) bool {
390		c := value.(*collection)
391		if out != "" {
392			out += "\n"
393		}
394		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)
395		return false
396	})
397	return out
398}
399
400func Withdraw(cur realm) {
401	requireAdmin(cur)
402	bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
403	ugnot := bk.GetCoins(self).AmountOf(nativeDenom)
404	locked := reservedPaid()
405	avail := ugnot - locked
406	if avail <= 0 {
407		panic("factory: no funds")
408	}
409	bk.SendCoins(self, admin, chain.Coins{{Denom: nativeDenom, Amount: avail}})
410	chain.Emit("Withdraw", "amount", strconv.FormatInt(avail, 10))
411}
412
413func TransferAdmin(cur realm, next address) {
414	requireAdmin(cur)
415	if !next.IsValid() {
416		panic("factory: invalid address")
417	}
418	if next == admin {
419		panic("factory: already admin")
420	}
421	admin = next
422	chain.Emit("TransferAdmin", "addr", next.String())
423}
424
425func Render(_ string) string {
426	if !inited {
427		return "# Bazaar factory\n\nCall Init first.\n"
428	}
429	out := "# Bazaar factory\n\n"
430	out += "- launch fee: " + strconv.FormatInt(launchFee, 10) + " ugnot\n"
431	out += "- collections: " + strconv.Itoa(bySlug.Size()) + "\n\n"
432	lines := ListCollections()
433	if lines == "" {
434		out += "_No collections._\n"
435	} else {
436		out += "```\n" + lines + "\n```\n"
437	}
438	return out
439}
440
441func register(pkg string, creator address, name, symbol, cover string, maxSupply int, mintPrice, royaltyBps int64) {
442	if !strings.HasPrefix(pkg, "gno.land/r/") {
443		panic("factory: pkg")
444	}
445	slug := lastElem(pkg)
446	if !validSlug(slug) {
447		panic("factory: slug")
448	}
449	if !validName(name) {
450		panic("factory: name")
451	}
452	if !validSymbol(symbol) {
453		panic("factory: symbol")
454	}
455	if !validImage(cover) {
456		panic("factory: cover")
457	}
458	if maxSupply < 0 || maxSupply > maxSupplyBound {
459		panic("factory: maxSupply")
460	}
461	if mintPrice < 0 {
462		panic("factory: mintPrice")
463	}
464	if royaltyBps < 0 || royaltyBps > maxRoyaltyBps {
465		panic("factory: royalty")
466	}
467	if bySlug.Has(slug) {
468		panic("factory: slug taken")
469	}
470	if byPkg.Has(pkg) {
471		panic("factory: pkg already registered")
472	}
473	c := &collection{
474		slug:       slug,
475		name:       name,
476		symbol:     symbol,
477		cover:      cover,
478		pkg:        pkg,
479		creator:    creator,
480		maxSupply:  maxSupply,
481		mintPrice:  mintPrice,
482		royaltyBps: int(royaltyBps),
483	}
484	bySlug.Set(slug, c)
485	byPkg.Set(pkg, c)
486	chain.Emit("Register", "slug", slug, "pkg", pkg, "creator", creator.String())
487}
488
489func requireInit() {
490	if !inited {
491		panic("factory: call Init first")
492	}
493}
494
495func requireAdmin(cur realm) {
496	requireInit()
497	if !cur.Previous().IsUserCall() {
498		panic("factory: EOA only")
499	}
500	if cur.Previous().Address() != admin {
501		panic("factory: not admin")
502	}
503}
504
505func mustReserve(slug string) *reservation {
506	v := reserved.Get(slug)
507	if v == nil {
508		panic("factory: not reserved")
509	}
510	return v.(*reservation)
511}
512
513func reservedPaid() int64 {
514	var n int64
515	reserved.Iterate("", "", func(_ string, value any) bool {
516		n += value.(*reservation).paid
517		return false
518	})
519	return n
520}
521
522func mustBySlug(slug string) *collection {
523	v := bySlug.Get(slug)
524	if v == nil {
525		panic("factory: unknown collection")
526	}
527	return v.(*collection)
528}
529
530func mustByPkg(pkg string) *collection {
531	v := byPkg.Get(pkg)
532	if v == nil {
533		panic("factory: unknown collection")
534	}
535	return v.(*collection)
536}
537
538func lastElem(pkg string) string {
539	i := strings.LastIndex(pkg, "/")
540	if i < 0 || i+1 >= len(pkg) {
541		panic("factory: pkg")
542	}
543	return pkg[i+1:]
544}
545
546func validSlug(s string) bool {
547	if len(s) < minSlugLen || len(s) > maxSlugLen {
548		return false
549	}
550	if s[0] < 'a' || s[0] > 'z' {
551		return false
552	}
553	return asciiSet(s, "abcdefghijklmnopqrstuvwxyz0123456789")
554}
555
556func validSymbol(s string) bool {
557	if len(s) < 1 || len(s) > maxSymbolLen {
558		return false
559	}
560	return asciiSet(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
561}
562
563func validName(name string) bool {
564	if len(name) < 1 || len(name) > maxNameLen {
565		return false
566	}
567	return !strings.ContainsAny(name, "\n\r|")
568}
569
570func validImage(u string) bool {
571	if u == "" {
572		return true
573	}
574	if len(u) > maxImageLen {
575		return false
576	}
577	if strings.ContainsAny(u, " \t\n\r|<>\"'`") {
578		return false
579	}
580	if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") {
581		return true
582	}
583	const prefix = "/samples/"
584	if !strings.HasPrefix(u, prefix) {
585		return false
586	}
587	rest := u[len(prefix):]
588	if rest == "" {
589		return false
590	}
591	return asciiSet(rest, "abcdefghijklmnopqrstuvwxyz0123456789._-")
592}
593
594func asciiSet(s, allowed string) bool {
595	for i := 0; i < len(s); i++ {
596		if !strings.ContainsRune(allowed, rune(s[i])) {
597			return false
598		}
599	}
600	return true
601}
602

Raw Package Data

Raw JSON data