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}
460Raw Package Data
Raw JSON data