← Back to Realms

Realm

gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv2

Overview

Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv2
Exported Functions
20
State Entries
22
Source Files
2
Total Package Entries
56

Exported Functions

20 exported functions

State

22 state entries

Source Code

FILES
bazaarv2.gno
go
1package bazaarv2
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/bazaarv2"
15	nativeDenom      = "ugnot"
16	defaultLaunchFee = int64(10_000_000) // 10 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	launchFee   int64
54	protocolBps int64
55)
56
57func Init(cur realm) {
58	if inited {
59		panic("factory: already initialized")
60	}
61	if !cur.Previous().IsUserCall() {
62		panic("factory: EOA only")
63	}
64	admin = cur.Previous().Address()
65	self = cur.Address()
66	bySlug = avl.Tree{}
67	byPkg = avl.Tree{}
68	reserved = avl.Tree{}
69	launchFee = defaultLaunchFee
70	protocolBps = defaultProtocolBps
71	inited = true
72	chain.Emit("Init", "admin", admin.String())
73}
74
75func Address() address {
76	return chain.PackageAddress(pkgPath)
77}
78
79func LaunchFee() int64 {
80	requireInit()
81	return launchFee
82}
83
84func Admin() string {
85	requireInit()
86	return admin.String()
87}
88
89func SetLaunchFee(cur realm, ugnot int64) {
90	requireAdmin(cur)
91	if ugnot < 0 {
92		panic("factory: launch fee")
93	}
94	launchFee = ugnot
95	chain.Emit("SetLaunchFee", "ugnot", strconv.FormatInt(ugnot, 10))
96}
97
98func ProtocolBps() int64 {
99	requireInit()
100	if protocolBps <= 0 {
101		return defaultProtocolBps
102	}
103	return protocolBps
104}
105
106func SetProtocolBps(cur realm, bps int64) {
107	requireAdmin(cur)
108	if bps < 0 || bps > maxProtocolBps {
109		panic("factory: protocol bps")
110	}
111	protocolBps = bps
112	chain.Emit("SetProtocolBps", "bps", strconv.FormatInt(bps, 10))
113}
114
115func Register(cur realm, name, symbol, cover string, maxSupply int, mintPrice, royaltyBps int64) {
116	requireInit()
117	prev := cur.Previous()
118	if !prev.IsCode() {
119		panic("factory: collection realm")
120	}
121	creatorRlm := prev.Previous()
122	if !creatorRlm.IsUserCall() {
123		panic("factory: EOA creator")
124	}
125	pkg := prev.PkgPath()
126	slug := lastElem(pkg)
127	res := mustReserve(slug)
128	if creatorRlm.Address() != res.who {
129		panic("factory: reserved")
130	}
131	reserved.Remove(slug)
132	register(pkg, creatorRlm.Address(), name, symbol, cover, maxSupply, mintPrice, royaltyBps)
133}
134
135func Reserve(cur realm, slug string) {
136	requireInit()
137	if !cur.Previous().IsUserCall() {
138		panic("factory: EOA only")
139	}
140	slug = strings.ToLower(strings.TrimSpace(slug))
141	if !validSlug(slug) {
142		panic("factory: slug")
143	}
144	if bySlug.Has(slug) || reserved.Has(slug) {
145		panic("factory: slug taken")
146	}
147	paid := launchFee
148	takeFactorySend(paid)
149	reserved.Set(slug, &reservation{who: cur.Previous().Address(), paid: paid})
150	chain.Emit("Reserve", "slug", slug, "paid", strconv.FormatInt(paid, 10))
151}
152
153func CancelReserve(cur realm, slug string) {
154	requireInit()
155	if !cur.Previous().IsUserCall() {
156		panic("factory: EOA only")
157	}
158	slug = strings.ToLower(strings.TrimSpace(slug))
159	res := mustReserve(slug)
160	if res.who != cur.Previous().Address() {
161		panic("factory: not reserver")
162	}
163	if bySlug.Has(slug) {
164		panic("factory: already registered")
165	}
166	reserved.Remove(slug)
167	if res.paid > 0 {
168		bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
169		bk.SendCoins(self, res.who, chain.Coins{{Denom: nativeDenom, Amount: res.paid}})
170	}
171	chain.Emit("CancelReserve", "slug", slug, "paid", strconv.FormatInt(res.paid, 10))
172}
173
174func ReservedCreator(slug string) string {
175	requireInit()
176	slug = strings.ToLower(strings.TrimSpace(slug))
177	if !reserved.Has(slug) {
178		return ""
179	}
180	return reserved.Get(slug).(*reservation).who.String()
181}
182
183func takeFactorySend(want int64) {
184	sent := unsafe.OriginSend()
185	if want == 0 {
186		if len(sent) != 0 {
187			panic("factory: extra denoms")
188		}
189		return
190	}
191	if len(sent) != 1 || sent[0].Denom != nativeDenom || sent[0].Amount != want {
192		panic("factory: wrong payment")
193	}
194}
195
196func NoteMinted(cur realm, n int) {
197	requireInit()
198	if n < 0 {
199		panic("factory: minted")
200	}
201	prev := cur.Previous()
202	if !prev.IsCode() {
203		panic("factory: collection realm")
204	}
205	c := mustByPkg(prev.PkgPath())
206	c.minted = n
207	byPkg.Set(c.pkg, c)
208	bySlug.Set(c.slug, c)
209}
210
211func NoteRoyalty(cur realm, bps int) {
212	requireInit()
213	if bps < 0 || bps > maxRoyaltyBps {
214		panic("factory: royalty")
215	}
216	prev := cur.Previous()
217	if !prev.IsCode() {
218		panic("factory: collection realm")
219	}
220	c := mustByPkg(prev.PkgPath())
221	c.royaltyBps = bps
222	byPkg.Set(c.pkg, c)
223	bySlug.Set(c.slug, c)
224}
225
226func CollectionOf(slug string) string {
227	requireInit()
228	c := mustBySlug(slug)
229	return c.pkg
230}
231
232func CollectionAddr(slug string) string {
233	requireInit()
234	c := mustBySlug(slug)
235	return chain.PackageAddress(c.pkg).String()
236}
237
238func RoyaltyOf(slug string) int {
239	requireInit()
240	c := mustBySlug(slug)
241	return c.royaltyBps
242}
243
244func ListCollections() string {
245	requireInit()
246	out := ""
247	bySlug.Iterate("", "", func(_ string, value any) bool {
248		c := value.(*collection)
249		if out != "" {
250			out += "\n"
251		}
252		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)
253		return false
254	})
255	return out
256}
257
258func Withdraw(cur realm) {
259	requireAdmin(cur)
260	bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)
261	ugnot := bk.GetCoins(self).AmountOf(nativeDenom)
262	locked := reservedPaid()
263	avail := ugnot - locked
264	if avail <= 0 {
265		panic("factory: no funds")
266	}
267	bk.SendCoins(self, admin, chain.Coins{{Denom: nativeDenom, Amount: avail}})
268	chain.Emit("Withdraw", "amount", strconv.FormatInt(avail, 10))
269}
270
271func TransferAdmin(cur realm, next address) {
272	requireAdmin(cur)
273	if !next.IsValid() {
274		panic("factory: invalid address")
275	}
276	if next == admin {
277		panic("factory: already admin")
278	}
279	admin = next
280	chain.Emit("TransferAdmin", "addr", next.String())
281}
282
283func Render(_ string) string {
284	if !inited {
285		return "# Bazaar factory\n\nCall Init first.\n"
286	}
287	out := "# Bazaar factory\n\n"
288	out += "- launch fee: " + strconv.FormatInt(launchFee, 10) + " ugnot\n"
289	out += "- collections: " + strconv.Itoa(bySlug.Size()) + "\n\n"
290	lines := ListCollections()
291	if lines == "" {
292		out += "_No collections._\n"
293	} else {
294		out += "```\n" + lines + "\n```\n"
295	}
296	return out
297}
298
299func register(pkg string, creator address, name, symbol, cover string, maxSupply int, mintPrice, royaltyBps int64) {
300	if !strings.HasPrefix(pkg, "gno.land/r/") {
301		panic("factory: pkg")
302	}
303	slug := lastElem(pkg)
304	if !validSlug(slug) {
305		panic("factory: slug")
306	}
307	if !validName(name) {
308		panic("factory: name")
309	}
310	if !validSymbol(symbol) {
311		panic("factory: symbol")
312	}
313	if !validImage(cover) {
314		panic("factory: cover")
315	}
316	if maxSupply < 0 || maxSupply > maxSupplyBound {
317		panic("factory: maxSupply")
318	}
319	if mintPrice < 0 {
320		panic("factory: mintPrice")
321	}
322	if royaltyBps < 0 || royaltyBps > maxRoyaltyBps {
323		panic("factory: royalty")
324	}
325	if bySlug.Has(slug) {
326		panic("factory: slug taken")
327	}
328	if byPkg.Has(pkg) {
329		panic("factory: pkg already registered")
330	}
331	c := &collection{
332		slug:       slug,
333		name:       name,
334		symbol:     symbol,
335		cover:      cover,
336		pkg:        pkg,
337		creator:    creator,
338		maxSupply:  maxSupply,
339		mintPrice:  mintPrice,
340		royaltyBps: int(royaltyBps),
341	}
342	bySlug.Set(slug, c)
343	byPkg.Set(pkg, c)
344	chain.Emit("Register", "slug", slug, "pkg", pkg, "creator", creator.String())
345}
346
347func requireInit() {
348	if !inited {
349		panic("factory: call Init first")
350	}
351}
352
353func requireAdmin(cur realm) {
354	requireInit()
355	if !cur.Previous().IsUserCall() {
356		panic("factory: EOA only")
357	}
358	if cur.Previous().Address() != admin {
359		panic("factory: not admin")
360	}
361}
362
363func mustReserve(slug string) *reservation {
364	v := reserved.Get(slug)
365	if v == nil {
366		panic("factory: not reserved")
367	}
368	return v.(*reservation)
369}
370
371func reservedPaid() int64 {
372	var n int64
373	reserved.Iterate("", "", func(_ string, value any) bool {
374		n += value.(*reservation).paid
375		return false
376	})
377	return n
378}
379
380func mustBySlug(slug string) *collection {
381	v := bySlug.Get(slug)
382	if v == nil {
383		panic("factory: unknown collection")
384	}
385	return v.(*collection)
386}
387
388func mustByPkg(pkg string) *collection {
389	v := byPkg.Get(pkg)
390	if v == nil {
391		panic("factory: unknown collection")
392	}
393	return v.(*collection)
394}
395
396func lastElem(pkg string) string {
397	i := strings.LastIndex(pkg, "/")
398	if i < 0 || i+1 >= len(pkg) {
399		panic("factory: pkg")
400	}
401	return pkg[i+1:]
402}
403
404func validSlug(s string) bool {
405	if len(s) < minSlugLen || len(s) > maxSlugLen {
406		return false
407	}
408	if s[0] < 'a' || s[0] > 'z' {
409		return false
410	}
411	return asciiSet(s, "abcdefghijklmnopqrstuvwxyz0123456789")
412}
413
414func validSymbol(s string) bool {
415	if len(s) < 1 || len(s) > maxSymbolLen {
416		return false
417	}
418	return asciiSet(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")
419}
420
421func validName(name string) bool {
422	if len(name) < 1 || len(name) > maxNameLen {
423		return false
424	}
425	return !strings.ContainsAny(name, "\n\r|")
426}
427
428func validImage(u string) bool {
429	if u == "" {
430		return true
431	}
432	if len(u) > maxImageLen {
433		return false
434	}
435	if strings.ContainsAny(u, " \t\n\r|<>\"'`") {
436		return false
437	}
438	if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") {
439		return true
440	}
441	const prefix = "/samples/"
442	if !strings.HasPrefix(u, prefix) {
443		return false
444	}
445	rest := u[len(prefix):]
446	if rest == "" {
447		return false
448	}
449	return asciiSet(rest, "abcdefghijklmnopqrstuvwxyz0123456789._-")
450}
451
452func asciiSet(s, allowed string) bool {
453	for i := 0; i < len(s); i++ {
454		if !strings.ContainsRune(allowed, rune(s[i])) {
455			return false
456		}
457	}
458	return true
459}
460

Raw Package Data

Raw JSON data