Transaction

3D21D06C243320…1EF095E7B645

Block 355,389 · index 0 · indexed

Summary

Hash
3D21D06C24332077E4C07A9A1A7DF8BF9B19D053A37FAC90E5F81EF095E7B645
Block
355,389
Size
28214 bytes
Gas used
37,916,119 / 49,290,988
Fee
49291 ugnot
Status
success

Messages

Attached funds
17000000 ugnot

Arguments · 7

  1. #1stable
  2. #2gnomod.toml
  3. #3module = "gno.land/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint2/stable" gno = "0.9"
  4. #4stable.gno
  5. #5package stable import ( runtime "chain/runtime" unsaferealm "chain/runtime/unsafe" "encoding/base64" "strconv" "strings" "gno.land/p/nt/avl/v0" "gno.land/p/nt/grc721/v0" "gno.land/p/nt/seqid/v0" campaign "gno.land/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint2/campaign" artpkg "gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/art3d2" "gno.land/p/nt/ufmt/v0" ) const ( CollectionName = "Trial Mint" CollectionSymbol = "TRIAL" realmRelPath = "/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint2/stable" summaryPageSize = 10 gnowebListLimit = 10 pageWindow = 2 tokenPageImageSize = "420" RevealDelayBlocks = 5 ) var ( token *grc721.Token ledger *grc721.PrivateLedger // private: only this realm mints, burns and moves tokens collectionOwner address = "g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4" approvedVersion string mintPriceUgnot int64 = 1000000000000 // 100 GNOT, per request whitelistBps avl.Tree royaltyBps int64 = 500 royaltyRecipient address = "g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4" mintedIDs []grc721.TokenID mintedTraitCombos avl.Tree // combo key -> true tokenComboKeys avl.Tree // tid.String() -> comboKey string mintCountByWallet avl.Tree // address.String() -> int64 mintingOpenMirror = true maxMintsPerWalletMirror int64 = 0 burnedRecords avl.Tree // tid.String() -> burnRecord burnedIDs []grc721.TokenID claimRecords []claimRecord sealedData avl.Tree // tid.String() -> traitPreset mintHeight avl.Tree // tid.String() -> int64 sealedImage string ) type traitPreset struct { Chain string ShapeIdx int ColorIdx int Metal string Active bool Rarity string // tier fixed at mint by the version realm (v5.rarityTier) } type claimRecord struct { Wallet address Bps int64 MintHeight int64 Tid grc721.TokenID } type burnRecord struct { BurnedBy address BurnHeight int64 Traits traitPreset } func (r burnRecord) metadata() Metadata { if r.Traits.Chain == "" { return Metadata{Name: "Untitled"} } return traitOnlyMetadata(r.Traits) } func init(cur realm) { token, ledger = grc721.NewToken(CollectionName, CollectionSymbol, seqid.ID(0), cur) svg := `<svg xmlns="http://www.w3.org/2000/svg" width="170" height="170" viewBox="0 0 170 170">` + `<rect width="170" height="170" fill="#0a0b10"/>` + `<circle cx="85" cy="78" r="34" fill="none" stroke="#555" stroke-width="4"/>` + `<text x="85" y="83" font-size="12" text-anchor="middle" fill="#999" font-family="monospace">SEALED</text>` + `</svg>` sealedImage = "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg)) } type Trait struct { TraitType string Value string } type Metadata struct { Name string Description string Image string BackgroundColor string Attributes []Trait } func Name() string { return CollectionName } func Symbol() string { return CollectionSymbol } func TokenCount() int64 { return token.TotalSupply() } func TokenID() string { return token.ID() } func GetName() string { return CollectionName } func GetSymbol() string { return CollectionSymbol } func OwnerOf(tid grc721.TokenID) (address, error) { return token.OwnerOf(tid) } func BalanceOf(owner address) (int64, error) { return token.BalanceOf(owner) } func GetApproved(tid grc721.TokenID) (address, error) { return token.GetApproved(tid) } func IsApprovedForAll(owner, operator address) bool { return token.IsApprovedForAll(owner, operator) } func ApprovedVersion() string { return approvedVersion } func Owner() address { return collectionOwner } func MintPrice() int64 { return mintPriceUgnot } func MintedIDs() []grc721.TokenID { return mintedIDs } func TotalBurned() int64 { return int64(len(burnedIDs)) } func MintingOpen() bool { return mintingOpenMirror } func MaxMintsPerWallet() int64 { return maxMintsPerWalletMirror } func MintsByWallet(owner address) int64 { n, _ := mintCountByWallet.Get(owner.String()).(int64) return n } func IsTraitComboAvailable(comboKey string) bool { return !mintedTraitCombos.Has(comboKey) } func WhitelistBps(wallet address) (bps int64, ok bool) { if !whitelistBps.Has(wallet.String()) { return 0, false } return whitelistBps.Get(wallet.String()).(int64), true } func WhitelistCount() int64 { return int64(whitelistBps.Size()) } func WhitelistEntry(index int) (wallet string, bps int64) { w, v := whitelistBps.GetByIndex(index) return w, v.(int64) } func EffectivePrice(caller address) int64 { if v, ok := whitelistBps.Get(caller.String()).(int64); ok { return mintPriceUgnot * v / 10000 } if bps, _, ok := campaign.Peek(caller); ok { return mintPriceUgnot * bps / 10000 } return mintPriceUgnot } func RoyaltyInfo(tid grc721.TokenID, salePrice int64) (address, int64, error) { if _, err := token.OwnerOf(tid); err != nil { return "", 0, err } return royaltyRecipient, salePrice * royaltyBps / 10000, nil } func RoyaltyBps() int64 { return royaltyBps } func RoyaltyRecipient() address { return royaltyRecipient } func callerAddress() address { return unsaferealm.PreviousRealm().Address() } func assertOwner() { if callerAddress() != collectionOwner { panic("unauthorized: caller is not the collection owner") } } func SetApprovedVersion(cur realm, pkgpath string) { assertOwner() approvedVersion = pkgpath } func SetMintPrice(cur realm, ugnot int64) { assertOwner() if ugnot < 0 { panic("price cannot be negative") } mintPriceUgnot = ugnot } func AddToWhitelist(cur realm, wallet address, bps int64) { assertOwner() if bps < 0 || bps > 10000 { panic("bps must be between 0 and 10000") } whitelistBps.Set(wallet.String(), bps) } func AddToWhitelistBatch(cur realm, walletsCSV string, bps int64) { assertOwner() if bps < 0 || bps > 10000 { panic("bps must be between 0 and 10000") } for _, w := range strings.Split(walletsCSV, ",") { w = strings.TrimSpace(w) if w == "" { continue } whitelistBps.Set(w, bps) } } func RemoveFromWhitelist(cur realm, wallet address) { assertOwner() whitelistBps.Remove(wallet.String()) } func SetRoyaltyBps(cur realm, bps int64) { assertOwner() if bps < 0 || bps > 10000 { panic("bps must be between 0 and 10000") } royaltyBps = bps } func SetRoyaltyRecipient(cur realm, recipient address) { assertOwner() royaltyRecipient = recipient } func assertCallerIsApprovedVersion(cur realm) { caller := cur.Previous().PkgPath() if approvedVersion == "" { panic("no version realm has been approved yet") } if caller != approvedVersion { panic("unauthorized: caller is not the approved version realm") } } func RecordMint(cur realm, to address, payer address, tid grc721.TokenID, chainName string, shapeIdx, colorIdx int, metal string, active bool, comboKey string, rarity string) grc721.TokenID { assertCallerIsApprovedVersion(cur) if mintedTraitCombos.Has(comboKey) { panic("trait combination already minted") } checkErr(ledger.Mint(to, tid)) mintedTraitCombos.Set(comboKey, true) tokenComboKeys.Set(tid.String(), comboKey) mintCountByWallet.Set(to.String(), MintsByWallet(to)+1) mintedIDs = append(mintedIDs, tid) height := runtime.ChainHeight() mintHeight.Set(tid.String(), height) sealedData.Set(tid.String(), traitPreset{ Chain: chainName, ShapeIdx: shapeIdx, ColorIdx: colorIdx, Metal: metal, Active: active, Rarity: rarity, }) if _, hasOverride := whitelistBps.Get(payer.String()).(int64); !hasOverride { if bps, ok := campaign.ConsumeDiscount(cross(cur), payer); ok { claimRecords = append(claimRecords, claimRecord{ Wallet: payer, Bps: bps, MintHeight: height, Tid: tid, }) } } return tid } func ClaimCount() int64 { return int64(len(claimRecords)) } func ClaimAt(index int) (wallet address, bps int64, mintHeight_ int64, tid string) { r := claimRecords[index] return r.Wallet, r.Bps, r.MintHeight, r.Tid.String() } func ReportMintingOpen(cur realm, open bool) { assertCallerIsApprovedVersion(cur) mintingOpenMirror = open } func ReportMaxMintsPerWallet(cur realm, max int64) { assertCallerIsApprovedVersion(cur) maxMintsPerWalletMirror = max } func Age(tid grc721.TokenID) int64 { h := mintHeight.Get(tid.String()) if h == nil { return -1 } return runtime.ChainHeight() - h.(int64) } func IsRevealed(tid grc721.TokenID) bool { if _, isTraitPreset := sealedData.Get(tid.String()).(traitPreset); !isTraitPreset { return true } age := Age(tid) return age >= 0 && age >= RevealDelayBlocks } func currentMetadata(tid grc721.TokenID) (Metadata, error) { if _, err := token.OwnerOf(tid); err != nil { return Metadata{}, err } tp, ok := sealedData.Get(tid.String()).(traitPreset) if !ok { return Metadata{}, grc721.ErrInvalidTokenId } if !IsRevealed(tid) { return sealedMetadata(), nil } return assembledMetadata(tid, tp), nil } func sealedMetadata() Metadata { return Metadata{Name: "Sealed", Image: sealedImage, BackgroundColor: "0a0b10", Attributes: []Trait{{TraitType: "Status", Value: "Sealed"}}} } func listRow(tid grc721.TokenID) (name, image string) { tp, ok := sealedData.Get(tid.String()).(traitPreset) if !ok { return "Untitled", "" } if !IsRevealed(tid) { return "Sealed", sealedImage } name = traitOnlyMetadata(tp).Name svg := artpkg.RenderTokenThumbSVG(tp.Chain, tp.Metal, tp.ShapeIdx, tp.ColorIdx, tp.Active, tid.String()) return name, "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg)) } func assembledMetadata(tid grc721.TokenID, tp traitPreset) Metadata { svg, stone, _, colorName := artpkg.RenderTokenSVG(tp.Chain, tp.Metal, tp.ShapeIdx, tp.ColorIdx, tp.Active, tid.String()) shapeName := artpkg.ShapeNames[tp.ShapeIdx] if stone == "Pearl" { shapeName = artpkg.PearlShapeNames[tp.ShapeIdx] } image := "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg)) status := "Sunset" if tp.Active { status = "Active" } return Metadata{ Name: pieceDisplayName(colorName, stone, shapeName, tp.Metal, tp.Chain), Description: ufmt.Sprintf("%s %s %s set in %s %s ring, minted on %s.", article(colorName), colorName, shapeName, strings.ToLower(article(tp.Metal)), tp.Metal, tp.Chain), Image: image, BackgroundColor: "0a0b10", Attributes: []Trait{ {TraitType: "Chain", Value: tp.Chain}, {TraitType: "Stone", Value: stone}, {TraitType: "Shape", Value: shapeName}, {TraitType: "Color", Value: colorName}, {TraitType: "Ring", Value: tp.Metal}, {TraitType: "Status", Value: status}, {TraitType: "Rarity", Value: tp.Rarity}, }, } } func article(word string) string { if word != "" && strings.ContainsRune("AEIOUaeiou", rune(word[0])) { return "An" } return "A" } func pieceDisplayName(colorName, stone, shape, metal, chain string) string { piece := colorName + " " + stone + " " + shape if stone == "Quartz" || stone == "" { piece = colorName + " " + shape } return piece + " · " + metal + " Ring · " + chain } func colorNameFor(stone string, colorIdx int) string { pairs := strings.Split(artpkg.ColorPaletteCSV(stone), ";") if colorIdx < 0 || colorIdx >= len(pairs) { return "" } parts := strings.SplitN(pairs[colorIdx], ":", 2) if len(parts) != 2 { return "" } return parts[1] } func traitOnlyMetadata(tp traitPreset) Metadata { stone := artpkg.StoneForChain(tp.Chain) shapeName := artpkg.ShapeNames[tp.ShapeIdx] if stone == "Pearl" { shapeName = artpkg.PearlShapeNames[tp.ShapeIdx] } colorName := colorNameFor(stone, tp.ColorIdx) status := "Sunset" if tp.Active { status = "Active" } return Metadata{ Name: pieceDisplayName(colorName, stone, shapeName, tp.Metal, tp.Chain), Description: ufmt.Sprintf("%s %s %s set in %s %s ring, minted on %s.", article(colorName), colorName, shapeName, strings.ToLower(article(tp.Metal)), tp.Metal, tp.Chain), BackgroundColor: "0a0b10", Attributes: []Trait{ {TraitType: "Chain", Value: tp.Chain}, {TraitType: "Stone", Value: stone}, {TraitType: "Shape", Value: shapeName}, {TraitType: "Color", Value: colorName}, {TraitType: "Ring", Value: tp.Metal}, {TraitType: "Status", Value: status}, {TraitType: "Rarity", Value: tp.Rarity}, }, } } func TokenURI(tid grc721.TokenID) (string, error) { metadata, err := currentMetadata(tid) if err != nil { return "", err } return metadata.Image, nil } func TokenMetadata(tid grc721.TokenID) (string, error) { if _, err := token.OwnerOf(tid); err != nil { return "", err } m := sealedMetadata() if tp, ok := sealedData.Get(tid.String()).(traitPreset); ok && IsRevealed(tid) { m = traitOnlyMetadata(tp) } m.Image = "" return metadataJSON(m, "traitType", "backgroundColor"), nil } func TokenJSON(tid grc721.TokenID) (string, error) { metadata, err := currentMetadata(tid) if err != nil { return "", err } return "data:application/json;base64," + base64.StdEncoding.EncodeToString([]byte(metadataJSON(metadata, "trait_type", "background_color"))), nil } func jsonEscape(s string) string { var b strings.Builder for i := 0; i < len(s); i++ { c := s[i] switch c { case '"': b.WriteString(`\"`) case '\\': b.WriteString(`\\`) default: b.WriteByte(c) } } return b.String() } func metadataJSON(m Metadata, traitKey, bgKey string) string { var b strings.Builder b.WriteString(`{"name":"`) b.WriteString(jsonEscape(m.Name)) b.WriteString(`","description":"`) b.WriteString(jsonEscape(m.Description)) b.WriteString(`"`) if m.Image != "" { b.WriteString(`,"image":"`) b.WriteString(jsonEscape(m.Image)) b.WriteString(`"`) } if m.BackgroundColor != "" { b.WriteString(`,"` + bgKey + `":"`) b.WriteString(jsonEscape(m.BackgroundColor)) b.WriteString(`"`) } b.WriteString(`,"attributes":[`) for i, t := range m.Attributes { if i > 0 { b.WriteString(",") } b.WriteString(`{"` + traitKey + `":"`) b.WriteString(jsonEscape(t.TraitType)) b.WriteString(`","value":"`) b.WriteString(jsonEscape(t.Value)) b.WriteString(`"}`) } b.WriteString("]}") return b.String() } func Approve(cur realm, to address, tid grc721.TokenID) { checkErr(ledger.Approve(callerAddress(), to, tid)) } func SetApprovalForAll(cur realm, operator address, approved bool) { checkErr(ledger.SetApprovalForAll(callerAddress(), operator, approved)) } func TransferFrom(cur realm, from, to address, tid grc721.TokenID) { checkErr(ledger.TransferFrom(callerAddress(), from, to, tid)) } func SafeTransferFrom(cur realm, from, to address, tid grc721.TokenID) { checkErr(ledger.TransferFrom(callerAddress(), from, to, tid)) } func Burn(cur realm, tid grc721.TokenID) { caller := callerAddress() owner, err := token.OwnerOf(tid) checkErr(err) if caller != owner { panic(grc721.ErrCallerIsNotOwner) } key := tid.String() archived, _ := sealedData.Get(key).(traitPreset) if comboKey, ok := tokenComboKeys.Get(key).(string); ok { mintedTraitCombos.Remove(comboKey) tokenComboKeys.Remove(key) } sealedData.Remove(key) mintHeight.Remove(key) checkErr(ledger.Burn(tid)) burnedRecords.Set(key, burnRecord{ BurnedBy: caller, BurnHeight: runtime.ChainHeight(), Traits: archived, }) burnedIDs = append(burnedIDs, tid) } func traitsToCSV(attrs []Trait) string { pairs := make([]string, len(attrs)) for i, a := range attrs { pairs[i] = a.TraitType + "=" + a.Value } return strings.Join(pairs, ";") } func TokenSummary(tid grc721.TokenID) (string, error) { return tokenSummary(tid, false) } func thumbSummary(tid grc721.TokenID) (string, error) { return tokenSummary(tid, true) } func tokenSummary(tid grc721.TokenID, thumb bool) (string, error) { owner, err := token.OwnerOf(tid) if err != nil { return "", err } var metadata Metadata if tp, ok := sealedData.Get(tid.String()).(traitPreset); thumb && ok && IsRevealed(tid) { metadata = traitOnlyMetadata(tp) svg := artpkg.RenderTokenThumbSVG(tp.Chain, tp.Metal, tp.ShapeIdx, tp.ColorIdx, tp.Active, tid.String()) metadata.Image = "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg)) } else { metadata, err = currentMetadata(tid) if err != nil { return "", err } } enc := base64.StdEncoding fields := []string{ tid.String(), owner.String(), enc.EncodeToString([]byte(metadata.Name)), enc.EncodeToString([]byte(metadata.Description)), enc.EncodeToString([]byte(metadata.Image)), "", // external URL: none enc.EncodeToString([]byte(traitsToCSV(metadata.Attributes))), enc.EncodeToString([]byte(metadata.BackgroundColor)), } return strings.Join(fields, "|"), nil } func CollectionSummary(page int) string { total := len(mintedIDs) if page < 1 { page = 1 } startK := (page - 1) * summaryPageSize endK := startK + summaryPageSize if endK > total { endK = total } var lines []string for k := startK; k < endK; k++ { tid := mintedIDs[total-1-k] line, err := thumbSummary(tid) if err != nil { continue } lines = append(lines, line) } return strings.Join(lines, "\n") } const ( walletScanChunk = 100 walletRenderCap = summaryPageSize ) func WalletSummary(owner address, startIdx int64) (summary string, nextIdx int64) { total := len(mintedIDs) start := int(startIdx) if start < 0 { start = 0 } end := start + walletScanChunk if end > total { end = total } var lines []string k := start for ; k < end; k++ { if len(lines) >= walletRenderCap { break } tid := mintedIDs[total-1-k] tokenOwner, err := token.OwnerOf(tid) if err != nil || tokenOwner != owner { continue } line, err := thumbSummary(tid) if err != nil { continue } lines = append(lines, line) } return strings.Join(lines, "\n"), int64(k) } func BurnedTokenSummary(tid grc721.TokenID) (string, error) { raw := burnedRecords.Get(tid.String()) rec, ok := raw.(burnRecord) if !ok { return "", grc721.ErrInvalidTokenId } m := rec.metadata() enc := base64.StdEncoding fields := []string{ tid.String(), rec.BurnedBy.String(), ufmt.Sprintf("%d", rec.BurnHeight), enc.EncodeToString([]byte(m.Name)), enc.EncodeToString([]byte(m.Description)), "", // image: a burn keeps none "", // external URL: none enc.EncodeToString([]byte(traitsToCSV(m.Attributes))), enc.EncodeToString([]byte(m.BackgroundColor)), } return strings.Join(fields, "|"), nil } func bigTokenImage(image string) string { const prefix = "data:image/svg+xml;base64," if !strings.HasPrefix(image, prefix) { return image } raw, err := base64.StdEncoding.DecodeString(image[len(prefix):]) if err != nil { return image } svg := string(raw) resized := strings.Replace(svg, `width="170.00" height="170.00"`, `width="`+tokenPageImageSize+`" height="`+tokenPageImageSize+`"`, 1) if resized == svg { resized = strings.Replace(svg, `width="170" height="170"`, `width="`+tokenPageImageSize+`" height="`+tokenPageImageSize+`"`, 1) } return prefix + base64.StdEncoding.EncodeToString([]byte(resized)) } func shortAddr(a string) string { if len(a) <= 14 { return a } return a[:6] + "…" + a[len(a)-4:] } func renderMintedListPage(b *strings.Builder, page int) { total := len(mintedIDs) if total == 0 { return } totalPages := (total + gnowebListLimit - 1) / gnowebListLimit if page < 1 { page = 1 } if page > totalPages { page = totalPages } startK := (page - 1) * gnowebListLimit endK := startK + gnowebListLimit if endK > total { endK = total } b.WriteString("\n## Minted so far\n\n") if totalPages > 1 { b.WriteString(ufmt.Sprintf("_page %d of %d -- %d minted total_\n\n", page, totalPages, total)) } b.WriteString("| Image | Index | Name | Owner |\n|:---:|:---:|---|---|\n") for k := startK; k < endK; k++ { tid := mintedIDs[total-1-k] link := realmRelPath + ":token/" + tid.String() owner, err := token.OwnerOf(tid) if err != nil { raw := burnedRecords.Get(tid.String()) rec, ok := raw.(burnRecord) if !ok { continue // shouldn't happen: burned but no archive record } name := rec.metadata().Name b.WriteString(ufmt.Sprintf("| Burned | [#%s](%s) | %s (burned at block %d) | %s |\n", tid.String(), link, name, rec.BurnHeight, shortAddr(rec.BurnedBy.String()))) continue } name, image := listRow(tid) imgCell := "" if image != "" { imgCell = ufmt.Sprintf("[![%s](%s)](%s)", name, image, link) } b.WriteString(ufmt.Sprintf("| %s | [#%s](%s) | %s | %s |\n", imgCell, tid.String(), link, name, shortAddr(owner.String()))) } b.WriteString("\n") renderPageLinks(b, page, totalPages, "page") } func renderPageLinks(b *strings.Builder, page, totalPages int, linkBase string) { if totalPages <= 1 { return } b.WriteString("\n") parts := []string{} if page > 1 { parts = append(parts, ufmt.Sprintf("[<< First](%s:%s/1)", realmRelPath, linkBase)) parts = append(parts, ufmt.Sprintf("[<- newer](%s:%s/%d)", realmRelPath, linkBase, page-1)) } lastShown := 0 for p := 1; p <= totalPages; p++ { if p != 1 && p != totalPages && (p < page-pageWindow || p > page+pageWindow) { continue } if lastShown != 0 && p != lastShown+1 { parts = append(parts, "...") } if p == page { parts = append(parts, ufmt.Sprintf("**%d**", p)) } else { parts = append(parts, ufmt.Sprintf("[%d](%s:%s/%d)", p, realmRelPath, linkBase, p)) } lastShown = p } if page < totalPages { parts = append(parts, ufmt.Sprintf("[older ->](%s:%s/%d)", realmRelPath, linkBase, page+1)) parts = append(parts, ufmt.Sprintf("[Last >>](%s:%s/%d)", realmRelPath, linkBase, totalPages)) } b.WriteString(strings.Join(parts, " - ")) b.WriteString("\n") } func renderBurnedListPage(b *strings.Builder, page int) { total := len(burnedIDs) if total == 0 { return } totalPages := (total + gnowebListLimit - 1) / gnowebListLimit if page < 1 { page = 1 } if page > totalPages { page = totalPages } startK := (page - 1) * gnowebListLimit endK := startK + gnowebListLimit if endK > total { endK = total } b.WriteString("\n## Burned tokens\n\n") if totalPages > 1 { b.WriteString(ufmt.Sprintf("_page %d of %d -- %d burned total_\n\n", page, totalPages, total)) } b.WriteString("| Index | Name | Burned By | Block |\n|:---:|---|---|---|\n") for k := startK; k < endK; k++ { tid := burnedIDs[total-1-k] raw := burnedRecords.Get(tid.String()) rec, ok := raw.(burnRecord) if !ok { continue } name := rec.metadata().Name b.WriteString(ufmt.Sprintf("| #%s | %s | %s | %d |\n", tid.String(), name, shortAddr(rec.BurnedBy.String()), rec.BurnHeight)) } b.WriteString("\n") renderPageLinks(b, page, totalPages, "burned/page") } func renderBurnedTokenPage(tid grc721.TokenID) string { raw := burnedRecords.Get(tid.String()) rec, ok := raw.(burnRecord) if !ok { return render404() } m := rec.metadata() name := m.Name var b strings.Builder b.WriteString(ufmt.Sprintf("# %s #%s (burned)\n\n[<- back to collection](%s)\n\n", CollectionName, tid.String(), realmRelPath)) b.WriteString("## Details\n\n") b.WriteString(ufmt.Sprintf("* **Collection**: %s\n", CollectionName)) b.WriteString(ufmt.Sprintf("* **Name**: %s\n", name)) b.WriteString(ufmt.Sprintf("* **Index**: #%s\n", tid.String())) if m.Description != "" { b.WriteString(ufmt.Sprintf("* **Description**: %s\n", m.Description)) } b.WriteString(ufmt.Sprintf("* **Status**: burned by %s at block %d\n", rec.BurnedBy, rec.BurnHeight)) b.WriteString(ufmt.Sprintf("* **Archive**: [all burned tokens](%s:burned)\n", realmRelPath)) if len(m.Attributes) > 0 { b.WriteString("\n## Traits\n\n") for _, attr := range m.Attributes { b.WriteString(ufmt.Sprintf("* **%s**: %s\n", attr.TraitType, attr.Value)) } } return b.String() } func render404() string { return ufmt.Sprintf("# 404\n\n[<- back to collection](%s)\n", realmRelPath) } func Render(path string) string { if path == "" { var b strings.Builder b.WriteString(token.RenderHome()) b.WriteString("\n## About\n\n") b.WriteString(CollectionName + ": each piece's Chain trait names a gno.land chain generation, and its stone is that chain's real stone -- Topaz, Sapphire, Pearl, Diamond for mainnet, Quartz for the testnets without one. Only real cuts and colours; every image is drawn on-chain. Delayed-reveal mint (real metadata sealed for a short delay after minting).\n\n") b.WriteString(ufmt.Sprintf("* **Owner**: %s\n", Owner())) b.WriteString(ufmt.Sprintf("* **Assembled trait combinations minted**: %d\n", int64(mintedTraitCombos.Size()))) b.WriteString("* **Max supply**: unlimited\n") if mintingOpenMirror { b.WriteString("* **Minting**: open\n") } else { b.WriteString("* **Minting**: closed\n") } if mintPriceUgnot > 0 { b.WriteString(ufmt.Sprintf("* **Mint price**: %d ugnot\n", mintPriceUgnot)) } else { b.WriteString("* **Mint price**: free\n") } b.WriteString(ufmt.Sprintf("* **Reveal delay**: %d block(s)\n", RevealDelayBlocks)) if maxMintsPerWalletMirror > 0 { b.WriteString(ufmt.Sprintf("* **Max mints per wallet**: %d\n", maxMintsPerWalletMirror)) } else { b.WriteString("* **Max mints per wallet**: unlimited\n") } if royaltyBps > 0 { b.WriteString(ufmt.Sprintf("* **Secondary sale royalty**: %d.%02d%% (EIP-2981 RoyaltyInfo)\n", royaltyBps/100, royaltyBps%100)) } if len(burnedIDs) > 0 { b.WriteString(ufmt.Sprintf("* **Burned**: %d -- [view burned tokens ->](%s:burned)\n", len(burnedIDs), realmRelPath)) } renderMintedListPage(&b, 1) return b.String() } parts := strings.Split(path, "/") if len(parts) == 2 && parts[0] == "page" { page, err := strconv.Atoi(parts[1]) if err != nil || page < 1 { return render404() } var b strings.Builder b.WriteString(ufmt.Sprintf("# %s -- minted tokens\n\n[<- back to collection](%s)\n", CollectionName, realmRelPath)) renderMintedListPage(&b, page) return b.String() } if parts[0] == "burned" { page := 1 if len(parts) == 3 && parts[1] == "page" { p, err := strconv.Atoi(parts[2]) if err != nil || p < 1 { return render404() } page = p } else if len(parts) != 1 { return render404() } var b strings.Builder b.WriteString(ufmt.Sprintf("# %s -- burned tokens\n\n[<- back to collection](%s)\n", CollectionName, realmRelPath)) renderBurnedListPage(&b, page) return b.String() } if len(parts) == 2 && parts[0] == "token" { tid := grc721.TokenID(parts[1]) owner, err := token.OwnerOf(tid) if err != nil { return renderBurnedTokenPage(tid) } metadata, _ := currentMetadata(tid) var b strings.Builder b.WriteString(ufmt.Sprintf("# %s #%s\n\n[<- back to collection](%s)\n\n", CollectionName, tid.String(), realmRelPath)) image := metadata.Image name := metadata.Name if name == "" { name = "Untitled" } if image != "" { b.WriteString(ufmt.Sprintf("![%s](%s)\n\n", name, bigTokenImage(image))) } b.WriteString("## Details\n\n") b.WriteString(ufmt.Sprintf("* **Collection**: %s\n", CollectionName)) b.WriteString(ufmt.Sprintf("* **Name**: %s\n", name)) b.WriteString(ufmt.Sprintf("* **Index**: #%s\n", tid.String())) if metadata.Description != "" { b.WriteString(ufmt.Sprintf("* **Description**: %s\n", metadata.Description)) } b.WriteString(ufmt.Sprintf("* **Owner**: %s\n", owner)) b.WriteString(ufmt.Sprintf("* **Revealed**: %t (age %d block(s))\n", IsRevealed(tid), Age(tid))) if len(metadata.Attributes) > 0 { b.WriteString("\n## Traits\n\n") for _, attr := range metadata.Attributes { b.WriteString(ufmt.Sprintf("* **%s**: %s\n", attr.TraitType, attr.Value)) } } return b.String() } return render404() } func checkErr(err error) { if err != nil { panic(err) } }
  6. #6/gno.MemPackageType
  7. #7 MPUserAll

Result log

msg:0,success:true,log:,events:[]