← Back to Realms

Realm

gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv1

Overview

Realm Path
gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv1
Exported Functions
21
State Entries
23
Source Files
2
Total Package Entries
57

Exported Functions

21 exported functions

State

23 state entries

Source Code

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

Raw Package Data

Raw JSON data