1package bazaargens23// Mainnet factory template: genesis p/nt/grc721 so Adena indexes NewToken/Transfer,4// plus List/Buy escrow on this realm. Listed OwnerOf is the collection (wallet hides5// the token until Cancel or Buy). Do not import p/bazaar/grc721 here.67import (8 "chain"9 "chain/banker"10 "chain/runtime/unsafe"11 "errors"12 "strconv"13 "strings"14 "time"1516 "gno.land/p/nt/grc721/metadata/v0"17 "gno.land/p/nt/grc721/v0"18 "gno.land/p/nt/avl/v0"19 "gno.land/p/nt/seqid/v0"20 factory "gno.land/r/g1n4pl5uc4yt5r96m9w6fmdznx3x0jyg8l6arhmt/bazaar/bazaarv5"21)2223const (24 maxNameLen = 6425 maxImageLen = 20026 maxSupplyBound = 1_000_00027 maxRoyaltyBps = 100028 nativeDenom = "ugnot"29 publicOrigin = "https://bazaar-gno.vercel.app"30 dropAddCap = 2031 dropSlotHardCap = 1000032 allowAddCap = 5033 minSlugLen = 234)3536type item struct {37 id int38 name string39 image string40 owner address41 seller address42 price int6443 listed bool44 rarity string45 traits string46 revealed bool47 slot int48}4950type dropSlot struct {51 name string52 image string53 rarity string54 traits string55}5657type phaseCount struct {58 wl int59 pub int60}6162var (63 items avl.Tree64 dropSlots avl.Tree65 allowlist avl.Tree66 mintedBy avl.Tree67 token *grc721.Token68 ledger *grc721.PrivateLedger69 metaView *metadata.Metadata70 metaLed *metadata.Ledger71 tokenSeq seqid.ID72 nextID int73 minted int74 loaded int75 allowN int76 mintCap int77 wlMintCap int78 pubMintCap int79 maxSupply int80 mintPrice int6481 wlPrice int6482 wlSupply int83 wlStart int6484 wlEnd int6485 publicStart int6486 publicEnd int6487 publicForced bool88 royaltyBps int89 paused bool90 hidden bool91 inited bool92 creator address93 self address94 colName string95 colSymbol string96 colCover string97 prereveal string98 slug string99)100101func Init(cur realm, name, symbol, cover string, maxSupplyN int, mintPriceUgnot int64, royalty int64) {102 if !cur.Previous().IsUserCall() {103 panic("col: EOA only")104 }105 if !cur.IsCurrent() {106 panic("col: spoofed realm")107 }108 if len(symbol) < 1 || len(symbol) > grc721.MaxSymbolLen {109 panic("col: symbol")110 }111 pathSlug := lastElem(cur.PkgPath())112 if !validPkgName(pathSlug) {113 panic("col: slug")114 }115 if maxSupplyN < 0 || maxSupplyN > maxSupplyBound {116 panic("col: maxSupply")117 }118 if mintPriceUgnot < 0 {119 panic("col: mintPrice")120 }121 if royalty < 0 || royalty > maxRoyaltyBps {122 panic("col: royalty")123 }124 if !validName(name) {125 panic("col: name")126 }127 if !validImage(cover) {128 panic("col: cover")129 }130 if inited {131 panic("col: already initialized")132 }133 slug = pathSlug134 reservedWho := factory.ReservedCreator(slug)135 if reservedWho == "" || reservedWho != cur.Previous().Address().String() {136 panic("col: reserve first")137 }138 takeExact(0)139 tok, led := grc721.NewToken(name, symbol, tokenSeq.Next(), cur)140 token = tok141 ledger = led142 metaView, metaLed = metadata.NewMetadata(led)143 creator = cur.Previous().Address()144 self = cur.Address()145 colName = name146 colSymbol = symbol147 colCover = cover148 prereveal = ""149 maxSupply = maxSupplyN150 mintPrice = mintPriceUgnot151 royaltyBps = int(royalty)152 items = avl.Tree{}153 dropSlots = avl.Tree{}154 allowlist = avl.Tree{}155 mintedBy = avl.Tree{}156 loaded = 0157 allowN = 0158 mintCap = 0159 wlMintCap = 0160 pubMintCap = 0161 wlPrice = 0162 wlSupply = 0163 wlStart = 0164 wlEnd = 0165 publicStart = 0166 publicEnd = 0167 publicForced = false168 hidden = false169 paused = maxSupplyN > 0170 inited = true171 factory.Register(cross(cur), name, symbol, cover, maxSupplyN, mintPriceUgnot, royalty)172 chain.Emit("Init", "creator", creator.String(), "slug", slug)173}174175func Mint(cur realm, name, imageURL string) int {176 caller := requireUser(cur)177 if caller != creator {178 panic("col: not creator")179 }180 id := mintTo(cur, caller, name, imageURL)181 writeTokenMeta(id)182 chain.Emit("Mint", "id", strconv.Itoa(id), "owner", caller.String())183 return id184}185186func PublicMint(cur realm) int {187 caller := requireUser(cur)188 if paused {189 panic("col: paused")190 }191 if mintPhase(time.Now().Unix()) == "closed" {192 panic("col: not open")193 }194 requireAllow(caller)195 checkMintCap(caller)196 if maxSupply > 0 && loaded == 0 {197 panic("col: no slot")198 }199 pay := mintPrice200 if wlPhase() {201 pay = wlPrice202 }203 takeExact(pay)204 name := colName + " #" + strconv.Itoa(minted+1)205 image := publicImage()206 var slot *dropSlot207 slotIndex := -1208 if loaded > 0 {209 if minted >= loaded {210 panic("col: no slot")211 }212 v := dropSlots.Get(slotKey(minted))213 if v == nil {214 panic("col: no slot")215 }216 slot = v.(*dropSlot)217 slotIndex = minted218 if hidden {219 name = "Unrevealed"220 image = prerevealImage()221 } else {222 name = slot.name223 image = slot.image224 }225 }226 id := mintTo(cur, caller, name, image)227 if slot != nil {228 if hidden {229 it := mustItem(id)230 it.revealed = false231 it.slot = slotIndex232 items.Set(itemKey(id), it)233 } else {234 applySlot(id, slot, slotIndex)235 }236 }237 writeTokenMeta(id)238 noteMint(caller)239 if pay > 0 {240 bk := banker.NewBanker(banker.BankerTypeRealmSend, cur)241 bk.SendCoins(self, creator, chain.Coins{{Denom: nativeDenom, Amount: pay}})242 }243 chain.Emit("PublicMint", "id", strconv.Itoa(id), "n", strconv.Itoa(minted))244 return id245}246247func AddDropItems(cur realm, blob string) {248 caller := requireUser(cur)249 if caller != creator {250 panic("col: not creator")251 }252 lines := splitDropLines(blob)253 if len(lines) < 1 || len(lines) > dropAddCap {254 panic("col: slots")255 }256 if loaded+len(lines) > slotCap() {257 panic("col: slots")258 }259 for _, line := range lines {260 name, image, rarity, traits := parseDropLine(line)261 if !validName(name) {262 panic("col: name")263 }264 if !validImage(image) {265 panic("col: image")266 }267 if !validRarity(rarity) {268 panic("col: rarity")269 }270 if !validTraits(traits) {271 panic("col: traits")272 }273 dropSlots.Set(slotKey(loaded), &dropSlot{name: name, image: image, rarity: rarity, traits: traits})274 loaded++275 }276 chain.Emit("AddDropItems", "slug", slug, "loaded", strconv.Itoa(loaded))277}278279func ListDropSlots() string {280 requireInit()281 if hidden {282 return ""283 }284 out := ""285 for i := 0; i < loaded; i++ {286 v := dropSlots.Get(slotKey(i))287 if v == nil {288 continue289 }290 s := v.(*dropSlot)291 if out != "" {292 out += "\n"293 }294 out += strconv.Itoa(i) + "|" + s.name + "|" + s.image + "|" + s.rarity + "|" + s.traits295 }296 return out297}298299func LoadedOf() int {300 requireInit()301 return loaded302}303304func PauseMint(cur realm) {305 caller := requireUser(cur)306 if caller != creator {307 panic("col: not creator")308 }309 paused = true310 chain.Emit("PauseMint", "slug", slug)311}312313func ResumeMint(cur realm) {314 caller := requireUser(cur)315 if caller != creator {316 panic("col: not creator")317 }318 if maxSupply > 0 && loaded < 1 {319 panic("col: no slot")320 }321 paused = false322 chain.Emit("ResumeMint", "slug", slug)323}324325func AddAllowlist(cur realm, blob string) {326 caller := requireUser(cur)327 if caller != creator {328 panic("col: not creator")329 }330 lines := splitDropLines(blob)331 if len(lines) < 1 || len(lines) > allowAddCap {332 panic("col: allowlist")333 }334 for _, line := range lines {335 who := strings.ToLower(strings.TrimSpace(line))336 if !validG1(who) {337 panic("col: address")338 }339 if allowlist.Get(who) != nil {340 continue341 }342 allowlist.Set(who, true)343 allowN++344 }345 chain.Emit("AddAllowlist", "n", strconv.Itoa(allowN))346}347348func AllowN() int {349 requireInit()350 return allowN351}352353func OnAllowlist(addr string) bool {354 requireInit()355 who := strings.ToLower(strings.TrimSpace(addr))356 return allowlist.Get(who) != nil357}358359func SetDropSale(cur realm, wlPriceUgnot int64, wlQty int) {360 caller := requireUser(cur)361 if caller != creator {362 panic("col: not creator")363 }364 if minted > 0 {365 panic("col: minted")366 }367 if wlPriceUgnot < 0 {368 panic("col: wlPrice")369 }370 if wlQty < 0 {371 panic("col: wlSupply")372 }373 if maxSupply > 0 && wlQty > maxSupply {374 panic("col: wlSupply")375 }376 wlPrice = wlPriceUgnot377 wlSupply = wlQty378 chain.Emit("SetDropSale", "wlSupply", strconv.Itoa(wlQty))379}380381func StartPublic(cur realm) {382 caller := requireUser(cur)383 if caller != creator {384 panic("col: not creator")385 }386 if wlSupply <= 0 || minted >= wlSupply {387 panic("col: public")388 }389 publicForced = true390 wlSupply = minted391 now := time.Now().Unix()392 if wlStart > now {393 wlStart = now394 }395 if publicStart > now {396 publicStart = now397 }398 if publicEnd > 0 && publicEnd <= now {399 publicEnd = 0400 }401 chain.Emit("StartPublic", "minted", strconv.Itoa(minted))402}403404// SetMintSchedule sets unix-second bounds for both rounds. 0,0,0,0 clears the clock.405// An end of 0 means that round has no hard close. wlStart > 0 requires publicStart > wlStart.406// A set end must be after its own start. publicStart must not fall inside the whitelist window.407// Locked once any token has minted.408func SetMintSchedule(cur realm, wlStartUnix, wlEndUnix, publicStartUnix, publicEndUnix int64) {409 caller := requireUser(cur)410 if caller != creator {411 panic("col: not creator")412 }413 if minted > 0 {414 panic("col: minted")415 }416 if wlStartUnix < 0 || wlEndUnix < 0 || publicStartUnix < 0 || publicEndUnix < 0 {417 panic("col: schedule")418 }419 if wlStartUnix == 0 && wlEndUnix == 0 && publicStartUnix == 0 && publicEndUnix == 0 {420 wlStart = 0421 wlEnd = 0422 publicStart = 0423 publicEnd = 0424 chain.Emit("SetMintSchedule", "wlStart", "0", "wlEnd", "0", "publicStart", "0", "publicEnd", "0")425 return426 }427 if wlStartUnix > 0 && publicStartUnix <= wlStartUnix {428 panic("col: schedule")429 }430 if wlEndUnix > 0 && (wlStartUnix == 0 || wlEndUnix <= wlStartUnix) {431 panic("col: schedule")432 }433 if publicEndUnix > 0 && (publicStartUnix == 0 || publicEndUnix <= publicStartUnix) {434 panic("col: schedule")435 }436 if wlEndUnix > 0 && publicStartUnix > 0 && publicStartUnix < wlEndUnix {437 panic("col: schedule")438 }439 wlStart = wlStartUnix440 wlEnd = wlEndUnix441 publicStart = publicStartUnix442 publicEnd = publicEndUnix443 chain.Emit(444 "SetMintSchedule",445 "wlStart", strconv.FormatInt(wlStartUnix, 10),446 "wlEnd", strconv.FormatInt(wlEndUnix, 10),447 "publicStart", strconv.FormatInt(publicStartUnix, 10),448 "publicEnd", strconv.FormatInt(publicEndUnix, 10),449 )450}451452func MintScheduleOf() string {453 requireInit()454 return strconv.FormatInt(wlStart, 10) + "|" + strconv.FormatInt(wlEnd, 10) + "|" + strconv.FormatInt(publicStart, 10) + "|" + strconv.FormatInt(publicEnd, 10)455}456457func SetMintCap(cur realm, n int) {458 if n < 0 {459 panic("col: mintCap")460 }461 caller := requireUser(cur)462 if caller != creator {463 panic("col: not creator")464 }465 mintCap = n466 chain.Emit("SetMintCap", "cap", strconv.Itoa(n))467}468469// SetPhaseMintCap sets how many times one wallet may PublicMint in each round.470// 0 means that round has no per-wallet limit. Counts are separate: a whitelist471// mint does not consume the public allowance. mintCap still caps the total.472func SetPhaseMintCap(cur realm, wlN, pubN int) {473 if wlN < 0 || pubN < 0 {474 panic("col: phase cap")475 }476 caller := requireUser(cur)477 if caller != creator {478 panic("col: not creator")479 }480 wlMintCap = wlN481 pubMintCap = pubN482 chain.Emit("SetPhaseMintCap", "wl", strconv.Itoa(wlN), "public", strconv.Itoa(pubN))483}484485func SetMintPrice(cur realm, priceUgnot int64) {486 if priceUgnot < 0 {487 panic("col: price")488 }489 caller := requireUser(cur)490 if caller != creator {491 panic("col: not creator")492 }493 mintPrice = priceUgnot494 chain.Emit("SetMintPrice", "price", strconv.FormatInt(priceUgnot, 10))495}496497func MintCapOf() int {498 requireInit()499 return mintCap500}501502func PhaseMintCapOf() string {503 requireInit()504 return strconv.Itoa(wlMintCap) + "|" + strconv.Itoa(pubMintCap)505}506507func DropSaleOf() string {508 requireInit()509 return strconv.Itoa(royaltyBps) + "|" + strconv.FormatInt(wlPrice, 10) + "|" + strconv.Itoa(wlSupply) + "|" + mintPhase(time.Now().Unix()) + "|" + strconv.FormatInt(mintPrice, 10)510}511512func wlPhase() bool {513 return mintPhase(time.Now().Unix()) == "whitelist"514}515516// mintPhase is closed, whitelist, or public. A future publicStart does not517// fall through to public price when the allowlist quantity is already used.518// After wlEnd, and after publicEnd, the sale is closed until the next open bound.519func mintPhase(now int64) string {520 if publicForced {521 if publicEnd > 0 && now >= publicEnd {522 return "closed"523 }524 return "public"525 }526 if wlStart > 0 && now < wlStart {527 return "closed"528 }529 if publicStart > 0 && now >= publicStart {530 if publicEnd > 0 && now >= publicEnd {531 return "closed"532 }533 return "public"534 }535 if publicStart > 0 && now < publicStart {536 if wlEnd > 0 && now >= wlEnd {537 return "closed"538 }539 if wlSupply > 0 && minted < wlSupply {540 return "whitelist"541 }542 return "closed"543 }544 if wlEnd > 0 && now >= wlEnd {545 return "public"546 }547 if wlSupply > 0 && minted < wlSupply {548 return "whitelist"549 }550 return "public"551}552553func requireAllow(caller address) {554 if publicForced {555 return556 }557 who := strings.ToLower(caller.String())558 if wlPhase() {559 if allowN <= 0 {560 panic("col: allowlist")561 }562 if allowlist.Get(who) == nil {563 panic("col: not allowed")564 }565 return566 }567 if wlSupply > 0 {568 return569 }570 if allowN <= 0 {571 return572 }573 if allowlist.Get(who) == nil {574 panic("col: not allowed")575 }576}577578func walletPhase(who string) phaseCount {579 if v := mintedBy.Get(who); v != nil {580 return v.(phaseCount)581 }582 return phaseCount{}583}584585func checkMintCap(caller address) {586 who := strings.ToLower(caller.String())587 got := walletPhase(who)588 if mintCap > 0 && got.wl+got.pub >= mintCap {589 panic("col: mint cap")590 }591 if wlPhase() {592 if wlMintCap > 0 && got.wl >= wlMintCap {593 panic("col: wl mint cap")594 }595 return596 }597 if pubMintCap > 0 && got.pub >= pubMintCap {598 panic("col: public mint cap")599 }600}601602func noteMint(caller address) {603 who := strings.ToLower(caller.String())604 got := walletPhase(who)605 if wlPhase() {606 got.wl++607 } else {608 got.pub++609 }610 mintedBy.Set(who, got)611}612613func Hidden() bool {614 requireInit()615 return hidden616}617618func SetHidden(cur realm, hide bool) {619 caller := requireUser(cur)620 if caller != creator {621 panic("col: not creator")622 }623 if !hide {624 panic("col: reveal")625 }626 if loaded < 1 {627 panic("col: no slots")628 }629 if !hidden && minted > 0 {630 panic("col: revealed")631 }632 hidden = true633 chain.Emit("SetHidden", "hidden", "true")634}635636// SetPrereveal is the image every sealed NFT shows. Empty uses the cover.637// Creator only. After RevealCollection the real slot image is already public.638func SetPrereveal(cur realm, image string) {639 caller := requireUser(cur)640 if caller != creator {641 panic("col: not creator")642 }643 image = strings.TrimSpace(image)644 if image != "" && !validImage(image) {645 panic("col: image")646 }647 if !hidden && minted > 0 {648 panic("col: revealed")649 }650 prereveal = image651 if hidden {652 paintSealed(prerevealImage())653 }654 chain.Emit("SetPrereveal", "image", image)655}656657func Prereveal() string {658 requireInit()659 return prereveal660}661662// RevealCollection opens every minted NFT. Creator only, and only while sealed.663func RevealCollection(cur realm) {664 caller := requireUser(cur)665 if caller != creator {666 panic("col: not creator")667 }668 if !hidden {669 panic("col: revealed")670 }671 if loaded < 1 {672 panic("col: no slots")673 }674 hidden = false675 for _, id := range sealedIDs() {676 it := mustItem(id)677 v := dropSlots.Get(slotKey(it.slot))678 if v == nil {679 panic("col: no slot")680 }681 applySlot(id, v.(*dropSlot), it.slot)682 writeTokenMeta(id)683 }684 chain.Emit("RevealCollection", "slug", slug)685}686687func Revealed(id int) bool {688 return mustItem(id).revealed689}690691func Reveal(cur realm, id int) {692 caller := requireUser(cur)693 it := mustItem(id)694 if caller != it.owner {695 panic("col: not owner")696 }697 if hidden {698 panic("col: sealed")699 }700 if it.revealed {701 panic("col: revealed")702 }703 if it.slot < 0 {704 panic("col: no slot")705 }706 v := dropSlots.Get(slotKey(it.slot))707 if v == nil {708 panic("col: no slot")709 }710 applySlot(id, v.(*dropSlot), it.slot)711 writeTokenMeta(id)712 chain.Emit("Reveal", "id", strconv.Itoa(id))713}714715// Adena IIFE: owner, err := OwnerOf("1"); owner.String() == wallet716func OwnerOf(tid string) (address, error) {717 requireInit()718 id, err := strconv.Atoi(tid)719 if err != nil || id < 1 {720 return address(""), errors.New("col: tid")721 }722 v := items.Get(itemKey(id))723 if v == nil {724 return address(""), errors.New("col: unknown item")725 }726 return v.(*item).owner, nil727}728729func GrcName() string {730 requireInit()731 return token.GetName()732}733734func GrcSymbol() string {735 requireInit()736 return token.GetSymbol()737}738739func GrcBalanceOf(addr string) int64 {740 requireInit()741 who := address(addr)742 if !who.IsValid() {743 return 0744 }745 n, err := token.BalanceOf(who)746 if err != nil {747 return 0748 }749 return n750}751752func GrcOwnerOf(id int) string {753 requireInit()754 owner, err := token.OwnerOf(itemTid(id))755 if err != nil {756 panic("col: owner")757 }758 return owner.String()759}760761func Name() string {762 return GrcName()763}764765func Symbol() string {766 return GrcSymbol()767}768769func BalanceOf(addr string) int64 {770 return GrcBalanceOf(addr)771}772773func TokenURI(tid string) string {774 id := parseTid(tid)775 mustItem(id)776 if metaView != nil {777 u, err := metaView.TokenURI(itemTid(id))778 if err == nil && u != "" {779 return u780 }781 }782 return tokenJSON(mustItem(id))783}784785func guardUnlisted(id int) {786 if mustItem(id).listed {787 panic("col: listed")788 }789}790791func Approve(cur realm, to, tid string) {792 caller := requireUser(cur)793 id := parseTid(tid)794 guardUnlisted(id)795 who := address(to)796 if to != "" && !who.IsValid() {797 panic("col: addr")798 }799 if err := ledger.Approve(caller, who, itemTid(id)); err != nil {800 panic("col: approve")801 }802}803804func SetApprovalForAll(cur realm, operator string, approved bool) {805 caller := requireUser(cur)806 op := address(operator)807 if !op.IsValid() {808 panic("col: addr")809 }810 if err := ledger.SetApprovalForAll(caller, op, approved); err != nil {811 panic("col: approve")812 }813}814815func TransferFrom(cur realm, from, to, tid string) {816 caller := requireUser(cur)817 id := parseTid(tid)818 guardUnlisted(id)819 src := address(from)820 dst := address(to)821 if !src.IsValid() || !dst.IsValid() {822 panic("col: addr")823 }824 if err := ledger.TransferFrom(caller, src, dst, itemTid(id)); err != nil {825 panic("col: transfer")826 }827 it := mustItem(id)828 it.owner = dst829 items.Set(itemKey(id), it)830}831832func GetApproved(id int) string {833 requireInit()834 who, err := token.GetApproved(itemTid(id))835 if err != nil {836 return ""837 }838 return who.String()839}840841func IsApprovedForAll(owner, operator string) bool {842 requireInit()843 a := address(owner)844 b := address(operator)845 if !a.IsValid() || !b.IsValid() {846 return false847 }848 return token.IsApprovedForAll(a, b)849}850851func TotalSupply() int64 {852 requireInit()853 return token.TotalSupply()854}855856func SellerOf(id int) address {857 return mustItem(id).seller858}859860func PriceOf(id int) int64 {861 return mustItem(id).price862}863864func NameOf(id int) string {865 it := mustItem(id)866 if !it.revealed {867 return "Unrevealed"868 }869 return it.name870}871872func ImageOf(id int) string {873 it := mustItem(id)874 if !it.revealed {875 return prerevealImage()876 }877 return it.image878}879880func Listed(id int) bool {881 return mustItem(id).listed882}883884func NextID() int {885 return nextID886}887888func CollectionOf(_ int) string {889 requireInit()890 return slug891}892893func ItemLine(id int) string {894 it := mustItem(id)895 name := it.name896 image := it.image897 rarity := it.rarity898 traits := it.traits899 if !it.revealed {900 name = "Unrevealed"901 image = prerevealImage()902 rarity = ""903 traits = ""904 }905 return strconv.Itoa(it.id) + "|" + name + "|" + it.owner.String() + "|" + it.seller.String() + "|" + strconv.FormatInt(it.price, 10) + "|" + strconv.FormatBool(it.listed) + "|" + image + "|" + slug + "|" + rarity + "|" + traits + "|" + strconv.FormatBool(it.revealed) + "|" + strconv.Itoa(it.slot)906}907908func ListOpen() string {909 requireInit()910 out := ""911 n := 0912 items.Iterate("", "", func(_ string, value any) bool {913 it := value.(*item)914 if !it.listed {915 return false916 }917 if n >= 50 {918 return true919 }920 if out != "" {921 out += "\n"922 }923 out += ItemLine(it.id)924 n++925 return false926 })927 return out928}929930func TokensOf(owner string) string {931 requireInit()932 who := address(owner)933 if !who.IsValid() {934 return ""935 }936 out := ""937 items.Iterate("", "", func(_ string, value any) bool {938 it := value.(*item)939 if it.owner != who {940 return false941 }942 if out != "" {943 out += ","944 }945 out += strconv.Itoa(it.id)946 return false947 })948 return out949}950951func TokenOfOwnerByIndex(owner string, index int64) string {952 requireInit()953 who := address(owner)954 if !who.IsValid() {955 panic("col: addr")956 }957 if index < 0 {958 panic("col: index")959 }960 var n int64961 found := ""962 items.Iterate("", "", func(_ string, value any) bool {963 it := value.(*item)964 if it.owner != who {965 return false966 }967 if n == index {968 found = strconv.Itoa(it.id)969 return true970 }971 n++972 return false973 })974 if found == "" {975 panic("col: index")976 }977 return found978}979980func TokenByIndex(index int64) string {981 requireInit()982 if index < 0 {983 panic("col: index")984 }985 var n int64986 found := ""987 items.Iterate("", "", func(_ string, value any) bool {988 if n == index {989 found = strconv.Itoa(value.(*item).id)990 return true991 }992 n++993 return false994 })995 if found == "" {996 panic("col: index")997 }998 return found999}10001001func Creator() string {1002 requireInit()1003 return creator.String()1004}10051006func MintPrice() int64 {1007 requireInit()1008 return mintPrice1009}10101011func MaxSupply() int {1012 requireInit()1013 return maxSupply1014}10151016func Minted() int {1017 requireInit()1018 return minted1019}10201021func RoyaltyBps() int {1022 requireInit()1023 return royaltyBps1024}10251026func SetRoyalty(cur realm, bps int) {1027 caller := requireUser(cur)1028 if caller != creator {1029 panic("col: not creator")1030 }1031 if minted != 0 {1032 panic("col: minted")1033 }1034 if bps < 0 || bps > maxRoyaltyBps {1035 panic("col: royalty")1036 }1037 royaltyBps = bps1038 factory.NoteRoyalty(cross(cur), bps)1039 chain.Emit("SetRoyalty", "slug", slug, "bps", strconv.Itoa(bps))1040}10411042func RarityOf(id int) string {1043 it := mustItem(id)1044 if !it.revealed {1045 return ""1046 }1047 return it.rarity1048}10491050func TraitsOf(id int) string {1051 it := mustItem(id)1052 if !it.revealed {1053 return ""1054 }1055 return it.traits1056}10571058func Paused() bool {1059 requireInit()1060 return paused1061}10621063func Render(_ string) string {1064 if !inited {1065 return "# Collection\n\nCall Init first.\n"1066 }1067 out := "# " + colName + " ($" + colSymbol + ")\n\n"1068 out += "- minted: " + strconv.Itoa(minted) + "\n"1069 out += "- mint price: " + strconv.FormatInt(mintPrice, 10) + " ugnot\n\n"1070 open := ListOpen()1071 if open == "" {1072 out += "_No open listings._\n"1073 } else {1074 out += "```\n" + open + "\n```\n"1075 }1076 return out1077}10781079func mintTo(cur realm, caller address, name, imageURL string) int {1080 requireInit()1081 if !cur.IsCurrent() {1082 panic("col: spoofed realm")1083 }1084 if maxSupply > 0 && minted >= maxSupply {1085 panic("col: sold out")1086 }1087 if !validName(name) {1088 panic("col: name")1089 }1090 if !validImage(imageURL) {1091 panic("col: image")1092 }1093 nextID++1094 id := nextID1095 it := &item{1096 id: id,1097 name: name,1098 image: imageURL,1099 owner: caller,1100 rarity: rarityOf(id),1101 revealed: true,1102 slot: -1,1103 }1104 items.Set(itemKey(id), it)1105 err := ledger.Mint(caller, itemTid(id))1106 if err != nil {1107 panic("col: mint")1108 }1109 minted++1110 factory.NoteMinted(cross(cur), minted)1111 return id1112}11131114func takeExact(want int64) {1115 sent := unsafe.OriginSend()1116 if want == 0 {1117 if len(sent) != 0 {1118 panic("col: extra denoms")1119 }1120 return1121 }1122 if len(sent) != 1 || sent[0].Denom != nativeDenom || sent[0].Amount != want {1123 panic("col: wrong payment")1124 }1125}11261127func prerevealImage() string {1128 if prereveal != "" {1129 return prereveal1130 }1131 return colCover1132}11331134func sealedIDs() []int {1135 out := []int{}1136 items.Iterate("", "", func(_ string, value any) bool {1137 it := value.(*item)1138 if !it.revealed && it.slot >= 0 {1139 out = append(out, it.id)1140 }1141 return false1142 })1143 return out1144}11451146func paintSealed(image string) {1147 for _, id := range sealedIDs() {1148 it := mustItem(id)1149 it.image = image1150 items.Set(itemKey(id), it)1151 writeTokenMeta(id)1152 }1153}11541155func publicImage() string {1156 img := "/samples/" + slug + "-0" + strconv.Itoa(minted+1) + ".png"1157 if validImage(img) {1158 return img1159 }1160 return colCover1161}11621163func requireInit() {1164 if !inited {1165 panic("col: call Init first")1166 }1167}11681169func requireUser(cur realm) address {1170 requireInit()1171 if !cur.IsCurrent() {1172 panic("col: spoofed realm")1173 }1174 if !cur.Previous().IsUserCall() {1175 panic("col: EOA only")1176 }1177 return cur.Previous().Address()1178}11791180func itemTid(id int) grc721.TokenID {1181 return grc721.TokenID(strconv.Itoa(id))1182}11831184func parseTid(tid string) int {1185 id, err := strconv.Atoi(tid)1186 if err != nil || id < 1 {1187 panic("col: tid")1188 }1189 return id1190}11911192func mustItem(id int) *item {1193 v := items.Get(itemKey(id))1194 if v == nil {1195 panic("col: unknown item")1196 }1197 return v.(*item)1198}11991200func itemKey(id int) string {1201 return strconv.Itoa(id)1202}12031204func lastElem(pkg string) string {1205 i := strings.LastIndex(pkg, "/")1206 if i < 0 || i+1 >= len(pkg) {1207 panic("col: pkg")1208 }1209 return pkg[i+1:]1210}12111212func slotKey(i int) string {1213 return strconv.Itoa(i)1214}12151216func slotCap() int {1217 if maxSupply > 0 {1218 return maxSupply1219 }1220 return dropSlotHardCap1221}12221223func applySlot(id int, s *dropSlot, slotIndex int) {1224 it := mustItem(id)1225 it.name = s.name1226 it.image = s.image1227 if s.rarity != "" {1228 it.rarity = s.rarity1229 }1230 it.traits = s.traits1231 it.revealed = true1232 it.slot = slotIndex1233 items.Set(itemKey(id), it)1234}12351236func writeTokenMeta(id int) {1237 it := mustItem(id)1238 name, image, attrs := publicMeta(it)1239 uri := tokenJSON(it)1240 if metaLed == nil {1241 return1242 }1243 if err := metaLed.SetTokenURI(itemTid(id), uri); err != nil {1244 panic("col: tokenURI")1245 }1246 if err := metaLed.SetTokenMetadata(itemTid(id), metadata.Data{1247 Name: name,1248 Description: colName + " #" + strconv.Itoa(id),1249 Image: absImage(image),1250 Attributes: attrs,1251 }); err != nil {1252 panic("col: metadata")1253 }1254}12551256func publicMeta(it *item) (string, string, []metadata.Trait) {1257 if !it.revealed {1258 return "Unrevealed", prerevealImage(), []metadata.Trait{}1259 }1260 return it.name, it.image, traitAttrs(it)1261}12621263func tokenJSON(it *item) string {1264 name, image, traits := publicMeta(it)1265 attrs := ""1266 for _, tr := range traits {1267 if attrs != "" {1268 attrs += ","1269 }1270 attrs += `{"trait_type":` + jsonStr(tr.TraitType) + `,"value":` + jsonStr(tr.Value) + `}`1271 }1272 body := `{"name":` + jsonStr(name) + `,"description":` + jsonStr(colName+" #"+strconv.Itoa(it.id)) + `,"image":` + jsonStr(absImage(image)) + `,"attributes":[` + attrs + `]}`1273 return "data:application/json;charset=utf-8," + pctURI(body)1274}12751276func traitAttrs(it *item) []metadata.Trait {1277 out := []metadata.Trait{}1278 if it.rarity != "" {1279 out = append(out, metadata.Trait{TraitType: "Rarity", Value: it.rarity})1280 }1281 for _, part := range strings.Split(it.traits, ";") {1282 part = strings.TrimSpace(part)1283 colon := strings.Index(part, ":")1284 if colon <= 0 || colon >= len(part)-1 {1285 continue1286 }1287 key := strings.TrimSpace(part[:colon])1288 val := strings.TrimSpace(part[colon+1:])1289 if key == "" || val == "" {1290 continue1291 }1292 out = append(out, metadata.Trait{TraitType: key, Value: val})1293 }1294 return out1295}12961297func absImage(u string) string {1298 if u == "" {1299 return u1300 }1301 if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") || strings.HasPrefix(u, "ipfs://") {1302 return u1303 }1304 if strings.HasPrefix(u, "/") {1305 return publicOrigin + u1306 }1307 return u1308}13091310func jsonStr(s string) string {1311 out := `"`1312 for _, r := range s {1313 switch r {1314 case '"':1315 out += `\"`1316 case '\\':1317 out += `\\`1318 case '\n':1319 out += `\n`1320 case '\r':1321 out += `\r`1322 case '\t':1323 out += `\t`1324 default:1325 if r < 0x20 {1326 out += `\u00`1327 hex := "0123456789abcdef"1328 out += string([]byte{hex[r>>4], hex[r&15]})1329 } else {1330 out += string(r)1331 }1332 }1333 }1334 return out + `"`1335}13361337func pctURI(s string) string {1338 out := ""1339 for i := 0; i < len(s); i++ {1340 c := s[i]1341 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~' {1342 out += string(c)1343 continue1344 }1345 hex := "0123456789ABCDEF"1346 out += "%"1347 out += string([]byte{hex[c>>4], hex[c&15]})1348 }1349 return out1350}13511352func splitDropLines(blob string) []string {1353 blob = strings.ReplaceAll(blob, "\x1e", "\n")1354 raw := strings.Split(blob, "\n")1355 out := make([]string, 0, len(raw))1356 for _, line := range raw {1357 line = strings.TrimSpace(line)1358 if line == "" {1359 continue1360 }1361 out = append(out, line)1362 }1363 return out1364}13651366func parseDropLine(line string) (string, string, string, string) {1367 parts := strings.Split(line, "|")1368 name := ""1369 image := ""1370 rarity := ""1371 traits := ""1372 if len(parts) > 0 {1373 name = strings.TrimSpace(parts[0])1374 }1375 if len(parts) > 1 {1376 image = strings.TrimSpace(parts[1])1377 }1378 if len(parts) > 2 {1379 rarity = strings.TrimSpace(parts[2])1380 }1381 if len(parts) > 3 {1382 traits = strings.TrimSpace(parts[3])1383 }1384 return name, image, rarity, traits1385}13861387func validRarity(s string) bool {1388 if s == "" {1389 return true1390 }1391 return s == "Common" || s == "Uncommon" || s == "Rare" || s == "Epic" || s == "Legendary" || s == "Mythic"1392}13931394func validTraits(s string) bool {1395 if s == "" {1396 return true1397 }1398 if len(s) > 200 {1399 return false1400 }1401 if strings.ContainsAny(s, "\n\r|") {1402 return false1403 }1404 n := 01405 for _, part := range strings.Split(s, ";") {1406 part = strings.TrimSpace(part)1407 if part == "" {1408 continue1409 }1410 colon := strings.Index(part, ":")1411 if colon <= 0 || colon >= len(part)-1 {1412 return false1413 }1414 key := strings.TrimSpace(part[:colon])1415 val := strings.TrimSpace(part[colon+1:])1416 if key == "" || val == "" {1417 return false1418 }1419 if !asciiSet(key, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _-") {1420 return false1421 }1422 if !asciiSet(val, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 _-") {1423 return false1424 }1425 n++1426 if n > 8 {1427 return false1428 }1429 }1430 return n >= 11431}14321433func validG1(s string) bool {1434 if len(s) < 39 || len(s) > 64 {1435 return false1436 }1437 if s[0] != 'g' || s[1] != '1' {1438 return false1439 }1440 return asciiSet(s, "abcdefghijklmnopqrstuvwxyz0123456789")1441}14421443func validPkgName(s string) bool {1444 if len(s) < minSlugLen || len(s) > grc721.MaxSymbolLen {1445 return false1446 }1447 if s[0] < 'a' || s[0] > 'z' {1448 return false1449 }1450 return asciiSet(s, "abcdefghijklmnopqrstuvwxyz0123456789")1451}14521453func rarityOf(id int) string {1454 r := id % 101455 if r == 0 {1456 return "Legendary"1457 }1458 if r == 1 {1459 return "Common"1460 }1461 if r == 2 {1462 return "Uncommon"1463 }1464 if r == 3 {1465 return "Rare"1466 }1467 if r == 4 {1468 return "Epic"1469 }1470 if r <= 7 {1471 return "Uncommon"1472 }1473 return "Common"1474}14751476func validName(name string) bool {1477 if len(name) < 1 || len(name) > maxNameLen {1478 return false1479 }1480 if strings.ContainsAny(name, "\n\r|\t\"\\") {1481 return false1482 }1483 for _, r := range name {1484 if r < 0x20 {1485 return false1486 }1487 }1488 return true1489}14901491func validImage(u string) bool {1492 if u == "" {1493 return true1494 }1495 if len(u) > maxImageLen {1496 return false1497 }1498 if strings.ContainsAny(u, " \t\n\r|<>\"'`") {1499 return false1500 }1501 if strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "http://") {1502 return true1503 }1504 const prefix = "/samples/"1505 if !strings.HasPrefix(u, prefix) {1506 return false1507 }1508 rest := u[len(prefix):]1509 if rest == "" {1510 return false1511 }1512 return asciiSet(rest, "abcdefghijklmnopqrstuvwxyz0123456789._-")1513}15141515func asciiSet(s, allowed string) bool {1516 for i := 0; i < len(s); i++ {1517 if !strings.ContainsRune(allowed, rune(s[i])) {1518 return false1519 }1520 }1521 return true1522}1523AddAllowlist(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}, blob string)
AddDropItems(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}, blob string)
AllowN() int
Approve(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}, to string, tid string)
BalanceOf(addr string) int64
Buy(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}, id int)
Cancel(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}, id int)
CollectionOf(int) string
Creator() string
DropSaleOf() string
GetApproved(id int) string
GrcBalanceOf(addr string) int64
GrcName() string
GrcOwnerOf(id int) string
GrcSymbol() string
Hidden() bool
ImageOf(id int) string
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, cover string, maxSupplyN int, mintPriceUgnot int64, royalty int64)
IsApprovedForAll(owner string, operator string) bool
ItemLine(id int) string
List(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}, id int, priceUgnot int64)
ListDropSlots() string
Listed(id int) bool
ListOpen() string
LoadedOf() int
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, imageURL string) int
MintCapOf() int
Minted() int
MintPrice() int64
MintScheduleOf() string
Name() string
NameOf(id int) string
NextID() int
OnAllowlist(addr string) bool
OwnerOf(tid string) (string, interface {Error func() string})
Paused() bool
PauseMint(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})
PhaseMintCapOf() string
Prereveal() string
PriceOf(id int) int64
ProtocolBps() int64
PublicMint(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}) int
RarityOf(id int) string
Render(string) string
ResumeMint(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})
Reveal(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}, id int)
RevealCollection(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})
Revealed(id int) bool
RoyaltyBps() int
SellerOf(id int) string
SetApprovalForAll(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}, operator string, approved bool)
SetDropSale(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}, wlPriceUgnot int64, wlQty int)
SetHidden(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}, hide bool)
SetMintCap(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)
SetMintPrice(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}, priceUgnot int64)
SetMintSchedule(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}, wlStartUnix int64, wlEndUnix int64, publicStartUnix int64, publicEndUnix int64)
SetPhaseMintCap(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}, wlN int, pubN int)
SetPrereveal(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}, image string)
SetRoyalty(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)
StartPublic(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})
Symbol() string
TokenByIndex(index int64) string
TokenOfOwnerByIndex(owner string, index int64) string
TokensOf(owner string) string
TokenURI(tid string) string
TotalSupply() int64
TraitsOf(id int) string
Transfer(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}, to string, id 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)
UpdatePrice(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}, id int, priceUgnot int64)
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.