1package bazaarv423import (4 "chain"5 "chain/banker"6 "chain/runtime/unsafe"7 "strconv"8 "strings"910 "gno.land/p/nt/avl/v0"11)1213const (14 pkgPath = "gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv4"15 nativeDenom = "ugnot"16 defaultLaunchFee = int64(1_000_000_000) // 1000 GNOT17 maxNameLen = 6418 maxImageLen = 20019 maxSupplyBound = 1_000_00020 maxRoyaltyBps = 100021 maxSymbolLen = 1122 maxSlugLen = 1123 minSlugLen = 224 defaultProtocolBps = int64(200) // 2% Magic Eden25 maxProtocolBps = int64(500)26)2728type collection struct {29 slug string30 name string31 symbol string32 cover string33 pkg string34 creator address35 maxSupply int36 mintPrice int6437 minted int38 royaltyBps int39}4041type reservation struct {42 who address43 paid int6444}4546var (47 bySlug avl.Tree48 byPkg avl.Tree49 reserved avl.Tree50 inited bool51 admin address52 self address53 protocolSink address54 launchFee int6455 protocolBps int6456 genesisPkg string57 genesisSink address58 genesisShareBps int6459 discountPusher string60 holderDisc avl.Tree61)6263func 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 = admin72 self = cur.Address()73 bySlug = avl.Tree{}74 byPkg = avl.Tree{}75 reserved = avl.Tree{}76 holderDisc = avl.Tree{}77 genesisPkg = ""78 genesisShareBps = 079 discountPusher = ""80 var z address81 genesisSink = z82 launchFee = defaultLaunchFee83 protocolBps = defaultProtocolBps84 inited = true85 chain.Emit("Init", "admin", admin.String())86}8788func Address() address {89 return chain.PackageAddress(pkgPath)90}9192func LaunchFee() int64 {93 requireInit()94 return launchFee95}9697func Admin() string {98 requireInit()99 return admin.String()100}101102func SetLaunchFee(cur realm, ugnot int64) {103 requireAdmin(cur)104 if ugnot < 0 {105 panic("factory: launch fee")106 }107 launchFee = ugnot108 chain.Emit("SetLaunchFee", "ugnot", strconv.FormatInt(ugnot, 10))109}110111func ProtocolBps() int64 {112 requireInit()113 if protocolBps <= 0 {114 return defaultProtocolBps115 }116 return protocolBps117}118119func SetProtocolBps(cur realm, bps int64) {120 requireAdmin(cur)121 if bps < 0 || bps > maxProtocolBps {122 panic("factory: protocol bps")123 }124 protocolBps = bps125 chain.Emit("SetProtocolBps", "bps", strconv.FormatInt(bps, 10))126}127128func ProtocolSink() address {129 requireInit()130 return protocolSink131}132133func 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 = next142 chain.Emit("SetProtocolSink", "addr", next.String())143}144145func GenesisPkg() string {146 requireInit()147 return genesisPkg148}149150func GenesisSink() address {151 requireInit()152 return genesisSink153}154155func GenesisShareBps() int64 {156 requireInit()157 if genesisPkg == "" {158 return 0159 }160 if genesisShareBps <= 0 {161 return 1100162 }163 return genesisShareBps164}165166func HolderDiscount(owner string) int64 {167 requireInit()168 if genesisPkg == "" {169 return 0170 }171 v := holderDisc.Get(strings.ToLower(strings.TrimSpace(owner)))172 if v == nil {173 return 0174 }175 return v.(int64)176}177178func 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 = pkg196 genesisSink = prev.Address()197 if genesisShareBps <= 0 {198 genesisShareBps = 1100199 }200 chain.Emit("AttachGenesis", "pkg", pkg)201}202203func 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 = pkg210 genesisSink = chain.PackageAddress(pkg)211 if genesisShareBps <= 0 {212 genesisShareBps = 1100213 }214 chain.Emit("SetGenesis", "pkg", pkg)215}216217func 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 = pkg224 chain.Emit("SetDiscountPusher", "pkg", pkg)225}226227func DiscountPusher() string {228 requireInit()229 return discountPusher230}231232func 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 return253 }254 holderDisc.Set(who, bps)255}256257func 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}276277func 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 := launchFee290 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}294295func 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}315316func 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}324325func takeFactorySend(want int64) {326 sent := unsafe.OriginSend()327 if want == 0 {328 if len(sent) != 0 {329 panic("factory: extra denoms")330 }331 return332 }333 if len(sent) != 1 || sent[0].Denom != nativeDenom || sent[0].Amount != want {334 panic("factory: wrong payment")335 }336}337338func 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 = n349 byPkg.Set(c.pkg, c)350 bySlug.Set(c.slug, c)351}352353func 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 = bps364 byPkg.Set(c.pkg, c)365 bySlug.Set(c.slug, c)366}367368func CollectionOf(slug string) string {369 requireInit()370 c := mustBySlug(slug)371 return c.pkg372}373374func CollectionAddr(slug string) string {375 requireInit()376 c := mustBySlug(slug)377 return chain.PackageAddress(c.pkg).String()378}379380func RoyaltyOf(slug string) int {381 requireInit()382 c := mustBySlug(slug)383 return c.royaltyBps384}385386func 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 false396 })397 return out398}399400func 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 - locked406 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}412413func 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 = next422 chain.Emit("TransferAdmin", "addr", next.String())423}424425func 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 out439}440441func 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}488489func requireInit() {490 if !inited {491 panic("factory: call Init first")492 }493}494495func 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}504505func mustReserve(slug string) *reservation {506 v := reserved.Get(slug)507 if v == nil {508 panic("factory: not reserved")509 }510 return v.(*reservation)511}512513func reservedPaid() int64 {514 var n int64515 reserved.Iterate("", "", func(_ string, value any) bool {516 n += value.(*reservation).paid517 return false518 })519 return n520}521522func mustBySlug(slug string) *collection {523 v := bySlug.Get(slug)524 if v == nil {525 panic("factory: unknown collection")526 }527 return v.(*collection)528}529530func mustByPkg(pkg string) *collection {531 v := byPkg.Get(pkg)532 if v == nil {533 panic("factory: unknown collection")534 }535 return v.(*collection)536}537538func 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}545546func validSlug(s string) bool {547 if len(s) < minSlugLen || len(s) > maxSlugLen {548 return false549 }550 if s[0] < 'a' || s[0] > 'z' {551 return false552 }553 return asciiSet(s, "abcdefghijklmnopqrstuvwxyz0123456789")554}555556func validSymbol(s string) bool {557 if len(s) < 1 || len(s) > maxSymbolLen {558 return false559 }560 return asciiSet(s, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")561}562563func validName(name string) bool {564 if len(name) < 1 || len(name) > maxNameLen {565 return false566 }567 return !strings.ContainsAny(name, "\n\r|")568}569570func validImage(u string) bool {571 if u == "" {572 return true573 }574 if len(u) > maxImageLen {575 return false576 }577 if strings.ContainsAny(u, " \t\n\r|<>\"'`") {578 return false579 }580 if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") {581 return true582 }583 const prefix = "/samples/"584 if !strings.HasPrefix(u, prefix) {585 return false586 }587 rest := u[len(prefix):]588 if rest == "" {589 return false590 }591 return asciiSet(rest, "abcdefghijklmnopqrstuvwxyz0123456789._-")592}593594func asciiSet(s, allowed string) bool {595 for i := 0; i < len(s); i++ {596 if !strings.ContainsRune(allowed, rune(s[i])) {597 return false598 }599 }600 return true601}602Address() string
Admin() string
AttachGenesis(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
CancelReserve(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string)
CollectionAddr(slug string) string
CollectionOf(slug string) string
DiscountPusher() string
GenesisPkg() string
GenesisShareBps() int64
GenesisSink() string
HolderDiscount(owner string) int64
Init(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
LaunchFee() int64
ListCollections() string
NoteHolderDiscount(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, owner string, bps int64)
NoteMinted(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, n int)
NoteRoyalty(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, bps int)
ProtocolBps() int64
ProtocolSink() string
Register(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, name string, symbol string, cover string, maxSupply int, mintPrice int64, royaltyBps int64)
Render(string) string
Reserve(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, slug string)
ReservedCreator(slug string) string
RoyaltyOf(slug string) int
SetDiscountPusher(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pkg string)
SetGenesis(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, pkg string)
SetLaunchFee(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, ugnot int64)
SetProtocolBps(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, bps int64)
SetProtocolSink(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, next string)
TransferAdmin(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, next string)
Withdraw(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.