1package gcol223import (4 "chain"5 "errors"6 "strconv"7 "strings"89 "gno.land/p/nt/avl/v0"10 "gno.land/p/nt/grc721/metadata/v0"11 "gno.land/p/nt/grc721/v0"12 "gno.land/p/nt/seqid/v0"13 "gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/gpush"14)1516// Holder perk for Bazaar: col.Buy subtracts HolderDiscount(trader) from17// factory.ProtocolBps() (highest FeeDiscount in that wallet). Every Genesis NFT18// also has ShareWeight = FeeDiscount for the 11% protocol-fee pool (claim later).1920const (21 maxImageLen = 20022 maxDiscountBps = int64(150)23 maxSupply = 111124 supplyCommon = 66625 supplyUncommon = 22226 supplyRare = 11127 supplyEpic = 6628 supplyLegend = 3329 supplyMythic = 1330 defaultImage = "https://bazaar-gno.vercel.app/gnomies.png"31 shareBps = int64(1100) // 11% of protocol take on Buy32)3334type item struct {35 id int36 owner address37 name string38 image string39 rarity string40 discountBps int6441}4243var (44 token *grc721.Token45 ledger *grc721.PrivateLedger46 metaView *metadata.Metadata47 metaLed *metadata.Ledger48 tokenSeq seqid.ID49 items avl.Tree50 inited bool51 creator address52 nextID int53 mintedCommon int54 mintedUncommon int55 mintedRare int56 mintedEpic int57 mintedLegend int58 mintedMythic int59)6061func Init(cur realm, name, symbol string) {62 if !cur.IsCurrent() {63 panic("genesis: spoofed realm")64 }65 if !cur.Previous().IsUserCall() {66 panic("genesis: EOA only")67 }68 if inited {69 panic("genesis: already initialized")70 }71 tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)72 token = tok73 ledger = led74 metaView, metaLed = metadata.NewMetadata(led)75 creator = cur.Previous().Address()76 inited = true77 chain.Emit("Init", "creator", creator.String())78}7980func Mint(cur realm, name, image, rarity string, discountBps int64) int {81 caller := requireUser(cur)82 if caller != creator {83 panic("genesis: not creator")84 }85 if name == "" || len(name) > grc721.MaxNameLen {86 panic("genesis: name")87 }88 if image == "" {89 image = defaultImage90 }91 if len(image) > maxImageLen {92 panic("genesis: image")93 }94 d := discountBps95 if d < 0 {96 d = rarityDiscount(rarity)97 }98 if d > maxDiscountBps {99 panic("genesis: discount")100 }101 if nextID >= maxSupply {102 panic("genesis: sold out")103 }104 takeRarity(rarity)105 nextID++106 id := nextID107 tid := grc721.TokenID(strconv.Itoa(id))108 if err := ledger.Mint(caller, tid); err != nil {109 panic("genesis: mint")110 }111 if err := metaLed.SetTokenURI(tid, tokenJSON(name, image, rarity, d)); err != nil {112 panic("genesis: tokenURI")113 }114 if err := metaLed.SetTokenMetadata(tid, metadata.Data{115 Name: name,116 Description: name,117 Image: image,118 Attributes: []metadata.Trait{119 {TraitType: "Rarity", Value: rarity},120 {TraitType: "FeeDiscount", Value: strconv.FormatInt(d, 10)},121 {TraitType: "ShareWeight", Value: strconv.FormatInt(d, 10)},122 },123 }); err != nil {124 panic("genesis: metadata")125 }126 items.Set(strconv.Itoa(id), &item{id: id, owner: caller, name: name, image: image, rarity: rarity, discountBps: d})127 gpush.Sync(cross(cur), caller.String(), HolderDiscount(caller.String()))128 chain.Emit("Mint", "id", strconv.Itoa(id), "discount", strconv.FormatInt(d, 10))129 return id130}131132// HolderDiscount is the best FeeDiscount among Genesis NFTs currently in `owner`'s wallet (not listed/escrowed).133func HolderDiscount(owner string) int64 {134 if !inited {135 return 0136 }137 who := address(owner)138 if !who.IsValid() {139 return 0140 }141 var best int64142 items.Iterate("", "", func(_ string, value any) bool {143 it := value.(*item)144 if it.owner == who && it.discountBps > best {145 best = it.discountBps146 }147 return false148 })149 return best150}151152// ShareWeight sums FeeDiscount of every Genesis NFT in `owner`'s wallet (not listed/escrowed).153func ShareWeight(owner string) int64 {154 if !inited {155 return 0156 }157 who := address(owner)158 if !who.IsValid() {159 return 0160 }161 var sum int64162 items.Iterate("", "", func(_ string, value any) bool {163 it := value.(*item)164 if it.owner == who {165 sum += it.discountBps166 }167 return false168 })169 return sum170}171172func DiscountOf(tid string) int64 {173 return mustItem(parseTid(tid)).discountBps174}175176func ShareBps() int64 {177 return shareBps178}179180func MaxSupply() int {181 return maxSupply182}183184func TotalSupply() int {185 requireInit()186 return nextID187}188189func SupplyOf(rarity string) int {190 switch canonRarity(rarity) {191 case "mythic":192 return supplyMythic193 case "legendary":194 return supplyLegend195 case "epic":196 return supplyEpic197 case "rare":198 return supplyRare199 case "uncommon":200 return supplyUncommon201 default:202 return supplyCommon203 }204}205206func MintedOf(rarity string) int {207 requireInit()208 switch canonRarity(rarity) {209 case "mythic":210 return mintedMythic211 case "legendary":212 return mintedLegend213 case "epic":214 return mintedEpic215 case "rare":216 return mintedRare217 case "uncommon":218 return mintedUncommon219 default:220 return mintedCommon221 }222}223224func Name() string {225 requireInit()226 return token.GetName()227}228229func Symbol() string {230 requireInit()231 return token.GetSymbol()232}233234func BalanceOf(owner string) int64 {235 requireInit()236 who := address(owner)237 if !who.IsValid() {238 return 0239 }240 n, err := token.BalanceOf(who)241 if err != nil {242 return 0243 }244 return n245}246247func OwnerOf(tid string) (address, error) {248 requireInit()249 id, err := strconv.Atoi(tid)250 if err != nil || id < 1 {251 return address(""), errors.New("genesis: tid")252 }253 v := items.Get(strconv.Itoa(id))254 if v == nil {255 return address(""), errors.New("genesis: unknown item")256 }257 return v.(*item).owner, nil258}259260func TokenURI(tid string) string {261 u, err := metaView.TokenURI(grc721.TokenID(tid))262 if err != nil || u == "" {263 it := mustItem(parseTid(tid))264 return tokenJSON(it.name, it.image, it.rarity, it.discountBps)265 }266 return u267}268269func TokensOf(owner string) string {270 requireInit()271 who := address(owner)272 if !who.IsValid() {273 return ""274 }275 out := ""276 items.Iterate("", "", func(_ string, value any) bool {277 it := value.(*item)278 if it.owner != who {279 return false280 }281 if out != "" {282 out += ","283 }284 out += strconv.Itoa(it.id)285 return false286 })287 return out288}289290func TransferFrom(cur realm, from, to, tid string) {291 caller := requireUser(cur)292 id := parseTid(tid)293 src := address(from)294 dst := address(to)295 if !src.IsValid() || !dst.IsValid() {296 panic("genesis: addr")297 }298 if err := ledger.TransferFrom(caller, src, dst, grc721.TokenID(tid)); err != nil {299 panic("genesis: transfer")300 }301 it := mustItem(id)302 it.owner = dst303 items.Set(strconv.Itoa(id), it)304 gpush.Sync(cross(cur), from, HolderDiscount(from))305 gpush.Sync(cross(cur), to, HolderDiscount(to))306}307308func canonRarity(rarity string) string {309 switch strings.ToLower(strings.TrimSpace(rarity)) {310 case "mythic":311 return "mythic"312 case "legendary":313 return "legendary"314 case "epic":315 return "epic"316 case "rare":317 return "rare"318 case "uncommon":319 return "uncommon"320 default:321 return "common"322 }323}324325func takeRarity(rarity string) {326 switch canonRarity(rarity) {327 case "mythic":328 if mintedMythic >= supplyMythic {329 panic("genesis: rarity full")330 }331 mintedMythic++332 case "legendary":333 if mintedLegend >= supplyLegend {334 panic("genesis: rarity full")335 }336 mintedLegend++337 case "epic":338 if mintedEpic >= supplyEpic {339 panic("genesis: rarity full")340 }341 mintedEpic++342 case "rare":343 if mintedRare >= supplyRare {344 panic("genesis: rarity full")345 }346 mintedRare++347 case "uncommon":348 if mintedUncommon >= supplyUncommon {349 panic("genesis: rarity full")350 }351 mintedUncommon++352 default:353 if mintedCommon >= supplyCommon {354 panic("genesis: rarity full")355 }356 mintedCommon++357 }358}359360func rarityDiscount(rarity string) int64 {361 switch canonRarity(rarity) {362 case "mythic":363 return 150364 case "legendary":365 return 125366 case "epic":367 return 100368 case "rare":369 return 75370 case "uncommon":371 return 50372 default:373 return 25374 }375}376377func requireInit() {378 if !inited {379 panic("genesis: call Init first")380 }381}382383func requireUser(cur realm) address {384 requireInit()385 if !cur.IsCurrent() {386 panic("genesis: spoofed realm")387 }388 if !cur.Previous().IsUserCall() {389 panic("genesis: EOA only")390 }391 return cur.Previous().Address()392}393394func parseTid(tid string) int {395 id, err := strconv.Atoi(tid)396 if err != nil || id < 1 {397 panic("genesis: tid")398 }399 return id400}401402func mustItem(id int) *item {403 v := items.Get(strconv.Itoa(id))404 if v == nil {405 panic("genesis: unknown item")406 }407 return v.(*item)408}409410func tokenJSON(name, image, rarity string, discount int64) string {411 body := `{"name":` + jsonStr(name) + `,"image":` + jsonStr(image) + `,"attributes":[{"trait_type":"Rarity","value":` + jsonStr(rarity) + `},{"trait_type":"FeeDiscount","value":` + jsonStr(strconv.FormatInt(discount, 10)) + `},{"trait_type":"ShareWeight","value":` + jsonStr(strconv.FormatInt(discount, 10)) + `}]}`412 return "data:application/json;charset=utf-8," + pctURI(body)413}414415func jsonStr(s string) string {416 out := `"`417 for _, r := range s {418 switch r {419 case '"':420 out += `\"`421 case '\\':422 out += `\\`423 default:424 out += string(r)425 }426 }427 return out + `"`428}429430func pctURI(s string) string {431 out := ""432 for i := 0; i < len(s); i++ {433 c := s[i]434 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {435 out += string(c)436 continue437 }438 hex := "0123456789ABCDEF"439 out += "%"440 out += string([]byte{hex[c>>4], hex[c&15]})441 }442 return out443}444BalanceOf(owner string) int64
DiscountOf(tid string) int64
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}, name string, symbol string)
MaxSupply() int
Mint(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, image string, rarity string, discountBps int64) int
MintedOf(rarity string) int
Name() string
OwnerOf(tid string) (string, interface {Error func() string})
ShareBps() int64
ShareWeight(owner string) int64
SupplyOf(rarity string) int
Symbol() string
TokensOf(owner string) string
TokenURI(tid string) string
TotalSupply() int
TransferFrom(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}, from string, to string, tid string)
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
Render unavailable
rate limit exceeded — the render query is served by the Gnoland archive RPC.
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.