PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint/stable

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
stable
Namespace
g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4 / trialmint
Files
2 (gnomod.toml)
Exported functions
49
Module
gno.land/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint/stable
gno
0.9

Files (2)

  • gnomod.tomltoml
  • stable.gnogno
stable.gnogno
1package stable23import (4	runtime "chain/runtime"5	unsaferealm "chain/runtime/unsafe"6	"encoding/base64"7	"strconv"8	"strings"910	"gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/avl"11	grc721 "gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/grc721v2"12	campaign "gno.land/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint/campaign"13	artpkg "gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/art3d"14	"gno.land/p/nt/ufmt/v0"15)1617const (18	CollectionName   = "Trial Mint"19	CollectionSymbol = "TRIAL"2021	realmRelPath = "/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint/stable"2223	summaryPageSize = 1024	gnowebListLimit = 1025	pageWindow         = 226	tokenPageImageSize = "420"2728	RevealDelayBlocks = 529)3031var (32	nft *grc721.MetadataNFT3334	collectionOwner address = "g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4"3536	approvedVersion string3738	mintPriceUgnot int64 = 1000000000000 // 100 GNOT, per request3940	whitelistBps avl.Tree4142	royaltyBps       int64   = 50043	royaltyRecipient address = "g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4"4445	mintedIDs []grc721.TokenID4647	mintedTraitCombos avl.Tree // combo key -> true48	tokenComboKeys    avl.Tree // tid.String() -> comboKey string49	mintCountByWallet avl.Tree // address.String() -> int645051	mintingOpenMirror       = true52	maxMintsPerWalletMirror int64 = 05354	burnedRecords avl.Tree // tid.String() -> burnRecord55	burnedIDs     []grc721.TokenID5657	claimRecords []claimRecord5859	sealedData avl.Tree // tid.String() -> traitPreset60	mintHeight avl.Tree // tid.String() -> int646162	sealedImage string63)6465type traitPreset struct {66	Chain    string67	ShapeIdx int68	ColorIdx int69	Metal    string70	Active   bool71	Rarity   string // tier fixed at mint by the version realm (v5.rarityTier)72}7374type claimRecord struct {75	Wallet     address76	Bps        int6477	MintHeight int6478	Tid        grc721.TokenID79}8081type burnRecord struct {82	BurnedBy   address83	BurnHeight int6484	Metadata   grc721.Metadata85}8687func init(cur realm) {88	nft = grc721.NewNFTWithMetadata(0, cur, CollectionName, CollectionSymbol)89	svg := `<svg xmlns="http://www.w3.org/2000/svg" width="170" height="170" viewBox="0 0 170 170">` +90		`<rect width="170" height="170" fill="#0a0b10"/>` +91		`<circle cx="85" cy="78" r="34" fill="none" stroke="#555" stroke-width="4"/>` +92		`<text x="85" y="83" font-size="12" text-anchor="middle" fill="#999" font-family="monospace">SEALED</text>` +93		`</svg>`94	sealedImage = "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg))95}9697func Name() string      { return CollectionName }98func Symbol() string    { return CollectionSymbol }99func TokenCount() int64 { return nft.TokenCount() }100101func OwnerOf(tid grc721.TokenID) (address, error)            { return nft.OwnerOf(tid) }102func BalanceOf(owner address) (int64, error)                 { return nft.BalanceOf(owner) }103func GetApproved(tid grc721.TokenID) (address, error)        { return nft.GetApproved(tid) }104func IsApprovedForAll(owner, operator address) bool          { return nft.IsApprovedForAll(owner, operator) }105106func ApprovedVersion() string { return approvedVersion }107func Owner() address          { return collectionOwner }108func MintPrice() int64        { return mintPriceUgnot }109110func MintedIDs() []grc721.TokenID { return mintedIDs }111func TotalBurned() int64          { return int64(len(burnedIDs)) }112113func MintingOpen() bool        { return mintingOpenMirror }114func MaxMintsPerWallet() int64 { return maxMintsPerWalletMirror }115116func MintsByWallet(owner address) int64 {117	n, _ := mintCountByWallet.Get(owner.String()).(int64)118	return n119}120121func IsTraitComboAvailable(comboKey string) bool {122	return !mintedTraitCombos.Has(comboKey)123}124125func WhitelistBps(wallet address) (bps int64, ok bool) {126	if !whitelistBps.Has(wallet.String()) {127		return 0, false128	}129	return whitelistBps.Get(wallet.String()).(int64), true130}131132func WhitelistCount() int64 { return int64(whitelistBps.Size()) }133134func WhitelistEntry(index int) (wallet string, bps int64) {135	w, v := whitelistBps.GetByIndex(index)136	return w, v.(int64)137}138139func EffectivePrice(caller address) int64 {140	if v, ok := whitelistBps.Get(caller.String()).(int64); ok {141		return mintPriceUgnot * v / 10000142	}143	if bps, _, ok := campaign.Peek(caller); ok {144		return mintPriceUgnot * bps / 10000145	}146	return mintPriceUgnot147}148149func RoyaltyInfo(tid grc721.TokenID, salePrice int64) (address, int64, error) {150	if _, err := nft.OwnerOf(tid); err != nil {151		return "", 0, err152	}153	return royaltyRecipient, salePrice * royaltyBps / 10000, nil154}155156func RoyaltyBps() int64         { return royaltyBps }157func RoyaltyRecipient() address { return royaltyRecipient }158159func callerAddress() address {160	return unsaferealm.PreviousRealm().Address()161}162163func assertOwner() {164	if callerAddress() != collectionOwner {165		panic("unauthorized: caller is not the collection owner")166	}167}168169func SetApprovedVersion(cur realm, pkgpath string) {170	assertOwner()171	approvedVersion = pkgpath172}173174func SetMintPrice(cur realm, ugnot int64) {175	assertOwner()176	if ugnot < 0 {177		panic("price cannot be negative")178	}179	mintPriceUgnot = ugnot180}181182func AddToWhitelist(cur realm, wallet address, bps int64) {183	assertOwner()184	if bps < 0 || bps > 10000 {185		panic("bps must be between 0 and 10000")186	}187	whitelistBps.Set(wallet.String(), bps)188}189190func AddToWhitelistBatch(cur realm, walletsCSV string, bps int64) {191	assertOwner()192	if bps < 0 || bps > 10000 {193		panic("bps must be between 0 and 10000")194	}195	for _, w := range strings.Split(walletsCSV, ",") {196		w = strings.TrimSpace(w)197		if w == "" {198			continue199		}200		whitelistBps.Set(w, bps)201	}202}203204func RemoveFromWhitelist(cur realm, wallet address) {205	assertOwner()206	whitelistBps.Remove(wallet.String())207}208209func SetRoyaltyBps(cur realm, bps int64) {210	assertOwner()211	if bps < 0 || bps > 10000 {212		panic("bps must be between 0 and 10000")213	}214	royaltyBps = bps215}216217func SetRoyaltyRecipient(cur realm, recipient address) {218	assertOwner()219	royaltyRecipient = recipient220}221222func assertCallerIsApprovedVersion(cur realm) {223	caller := cur.Previous().PkgPath()224	if approvedVersion == "" {225		panic("no version realm has been approved yet")226	}227	if caller != approvedVersion {228		panic("unauthorized: caller is not the approved version realm")229	}230}231232func 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 {233	assertCallerIsApprovedVersion(cur)234	if mintedTraitCombos.Has(comboKey) {235		panic("trait combination already minted")236	}237	checkErr(nft.Mint(to, tid))238	checkErr(nft.SetTokenMetadata(to, tid, grc721.Metadata{239		Name:       "Sealed",240		Attributes: []grc721.Trait{{TraitType: "Status", Value: "Sealed"}},241	}))242	mintedTraitCombos.Set(comboKey, true)243	tokenComboKeys.Set(tid.String(), comboKey)244	mintCountByWallet.Set(to.String(), MintsByWallet(to)+1)245	mintedIDs = append(mintedIDs, tid)246247	height := runtime.ChainHeight()248	mintHeight.Set(tid.String(), height)249	sealedData.Set(tid.String(), traitPreset{250		Chain: chainName, ShapeIdx: shapeIdx, ColorIdx: colorIdx, Metal: metal, Active: active, Rarity: rarity,251	})252253	if _, hasOverride := whitelistBps.Get(payer.String()).(int64); !hasOverride {254		if bps, ok := campaign.ConsumeDiscount(cross(cur), payer); ok {255			claimRecords = append(claimRecords, claimRecord{256				Wallet:     payer,257				Bps:        bps,258				MintHeight: height,259				Tid:        tid,260			})261		}262	}263	return tid264}265266func ClaimCount() int64 { return int64(len(claimRecords)) }267268func ClaimAt(index int) (wallet address, bps int64, mintHeight_ int64, tid string) {269	r := claimRecords[index]270	return r.Wallet, r.Bps, r.MintHeight, r.Tid.String()271}272273func ReportMintingOpen(cur realm, open bool) {274	assertCallerIsApprovedVersion(cur)275	mintingOpenMirror = open276}277278func ReportMaxMintsPerWallet(cur realm, max int64) {279	assertCallerIsApprovedVersion(cur)280	maxMintsPerWalletMirror = max281}282283func Age(tid grc721.TokenID) int64 {284	h := mintHeight.Get(tid.String())285	if h == nil {286		return -1287	}288	return runtime.ChainHeight() - h.(int64)289}290291func IsRevealed(tid grc721.TokenID) bool {292	if _, isTraitPreset := sealedData.Get(tid.String()).(traitPreset); !isTraitPreset {293		return true294	}295	age := Age(tid)296	return age >= 0 && age >= RevealDelayBlocks297}298299func currentMetadata(tid grc721.TokenID) (grc721.Metadata, error) {300	stored, err := nft.TokenMetadata(tid)301	if err != nil {302		return stored, err303	}304	tp, hadReveal := sealedData.Get(tid.String()).(traitPreset)305	if !hadReveal {306		return stored, nil307	}308	if !IsRevealed(tid) {309		stored.Image = sealedImage // still sealed: shared placeholder, not per-token storage310		return stored, nil311	}312	return assembledMetadata(tid, tp), nil313}314315func listRow(tid grc721.TokenID) (name, image string) {316	tp, ok := sealedData.Get(tid.String()).(traitPreset)317	if !ok {318		m, _ := nft.TokenMetadata(tid)319		return m.Name, m.Image320	}321	if !IsRevealed(tid) {322		return "Sealed", sealedImage323	}324	name = traitOnlyMetadata(tp).Name325	svg := artpkg.RenderTokenThumbSVG(tp.Chain, tp.Metal, tp.ShapeIdx, tp.ColorIdx, tp.Active, tid.String())326	return name, "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg))327}328329func assembledMetadata(tid grc721.TokenID, tp traitPreset) grc721.Metadata {330	svg, stone, _, colorName := artpkg.RenderTokenSVG(tp.Chain, tp.Metal, tp.ShapeIdx, tp.ColorIdx, tp.Active, tid.String())331	shapeName := artpkg.ShapeNames[tp.ShapeIdx]332	if stone == "Pearl" {333		shapeName = artpkg.PearlShapeNames[tp.ShapeIdx]334	}335	image := "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg))336	status := "Sunset"337	if tp.Active {338		status = "Active"339	}340	return grc721.Metadata{341		Name:            pieceDisplayName(colorName, stone, shapeName, tp.Metal, tp.Chain),342		Description:     ufmt.Sprintf("%s %s %s set in a %s ring, minted on %s.", article(colorName), colorName, shapeName, tp.Metal, tp.Chain),343		Image:           image,344		BackgroundColor: "0a0b10",345		Attributes: []grc721.Trait{346			{TraitType: "Chain", Value: tp.Chain},347			{TraitType: "Stone", Value: stone},348			{TraitType: "Shape", Value: shapeName},349			{TraitType: "Color", Value: colorName},350			{TraitType: "Ring", Value: tp.Metal},351			{TraitType: "Status", Value: status},352			{TraitType: "Rarity", Value: tp.Rarity},353		},354	}355}356357func article(word string) string {358	if word != "" && strings.ContainsRune("AEIOUaeiou", rune(word[0])) {359		return "An"360	}361	return "A"362}363364func pieceDisplayName(colorName, stone, shape, metal, chain string) string {365	piece := colorName + " " + stone + " " + shape366	if stone == "Quartz" || stone == "" {367		piece = colorName + " " + shape368	}369	return piece + " · " + metal + " Ring · " + chain370}371372func colorNameFor(stone string, colorIdx int) string {373	pairs := strings.Split(artpkg.ColorPaletteCSV(stone), ";")374	if colorIdx < 0 || colorIdx >= len(pairs) {375		return ""376	}377	parts := strings.SplitN(pairs[colorIdx], ":", 2)378	if len(parts) != 2 {379		return ""380	}381	return parts[1]382}383384func traitOnlyMetadata(tp traitPreset) grc721.Metadata {385	stone := artpkg.StoneForChain(tp.Chain)386	shapeName := artpkg.ShapeNames[tp.ShapeIdx]387	if stone == "Pearl" {388		shapeName = artpkg.PearlShapeNames[tp.ShapeIdx]389	}390	colorName := colorNameFor(stone, tp.ColorIdx)391	status := "Sunset"392	if tp.Active {393		status = "Active"394	}395	return grc721.Metadata{396		Name:        pieceDisplayName(colorName, stone, shapeName, tp.Metal, tp.Chain),397		Description: ufmt.Sprintf("%s %s %s set in a %s ring, minted on %s.", article(colorName), colorName, shapeName, tp.Metal, tp.Chain),398		Attributes: []grc721.Trait{399			{TraitType: "Chain", Value: tp.Chain},400			{TraitType: "Stone", Value: stone},401			{TraitType: "Shape", Value: shapeName},402			{TraitType: "Color", Value: colorName},403			{TraitType: "Ring", Value: tp.Metal},404			{TraitType: "Status", Value: status},405			{TraitType: "Rarity", Value: tp.Rarity},406		},407	}408}409410func TokenMetadata(tid grc721.TokenID) (grc721.Metadata, error) { return currentMetadata(tid) }411412func TokenURI(tid grc721.TokenID) (string, error) {413	metadata, err := currentMetadata(tid)414	if err != nil {415		return "", err416	}417	return metadataDataURI(metadata), nil418}419420func jsonEscape(s string) string {421	var b strings.Builder422	for i := 0; i < len(s); i++ {423		c := s[i]424		switch c {425		case '"':426			b.WriteString(`\"`)427		case '\\':428			b.WriteString(`\\`)429		default:430			b.WriteByte(c)431		}432	}433	return b.String()434}435436func metadataDataURI(m grc721.Metadata) string {437	image := m.Image438	if image == "" {439		image = m.ImageData440	}441	var b strings.Builder442	b.WriteString(`{"name":"`)443	b.WriteString(jsonEscape(m.Name))444	b.WriteString(`","description":"`)445	b.WriteString(jsonEscape(m.Description))446	b.WriteString(`","image":"`)447	b.WriteString(jsonEscape(image))448	b.WriteString(`"`)449	if m.BackgroundColor != "" {450		b.WriteString(`,"background_color":"`)451		b.WriteString(jsonEscape(m.BackgroundColor))452		b.WriteString(`"`)453	}454	b.WriteString(`,"attributes":[`)455	for i, t := range m.Attributes {456		if i > 0 {457			b.WriteString(",")458		}459		b.WriteString(`{"trait_type":"`)460		b.WriteString(jsonEscape(t.TraitType))461		b.WriteString(`","value":"`)462		b.WriteString(jsonEscape(t.Value))463		b.WriteString(`"}`)464	}465	b.WriteString("]}")466	return "data:application/json;base64," + base64.StdEncoding.EncodeToString([]byte(b.String()))467}468469func Approve(cur realm, to address, tid grc721.TokenID) {470	checkErr(nft.Approve(callerAddress(), to, tid))471}472473func SetApprovalForAll(cur realm, operator address, approved bool) {474	checkErr(nft.SetApprovalForAll(callerAddress(), operator, approved))475}476477func TransferFrom(cur realm, from, to address, tid grc721.TokenID) {478	checkErr(nft.TransferFrom(callerAddress(), from, to, tid))479}480481func SafeTransferFrom(cur realm, from, to address, tid grc721.TokenID) {482	checkErr(nft.SafeTransferFrom(callerAddress(), from, to, tid))483}484485func Burn(cur realm, tid grc721.TokenID) {486	caller := callerAddress()487	owner, err := nft.OwnerOf(tid)488	checkErr(err)489	if caller != owner {490		panic(grc721.ErrCallerIsNotOwner)491	}492493	key := tid.String()494	var archived grc721.Metadata495	if tp, ok := sealedData.Get(key).(traitPreset); ok {496		archived = traitOnlyMetadata(tp)497	}498499	if comboKey, ok := tokenComboKeys.Get(key).(string); ok {500		mintedTraitCombos.Remove(comboKey)501		tokenComboKeys.Remove(key)502	}503	sealedData.Remove(key)504	mintHeight.Remove(key)505506	checkErr(nft.Burn(tid))507508	burnedRecords.Set(key, burnRecord{509		BurnedBy:   caller,510		BurnHeight: runtime.ChainHeight(),511		Metadata:   archived,512	})513	burnedIDs = append(burnedIDs, tid)514}515516func traitsToCSV(attrs []grc721.Trait) string {517	pairs := make([]string, len(attrs))518	for i, a := range attrs {519		pairs[i] = a.TraitType + "=" + a.Value520	}521	return strings.Join(pairs, ";")522}523524func TokenSummary(tid grc721.TokenID) (string, error) { return tokenSummary(tid, false) }525526func thumbSummary(tid grc721.TokenID) (string, error) { return tokenSummary(tid, true) }527528func tokenSummary(tid grc721.TokenID, thumb bool) (string, error) {529	owner, err := nft.OwnerOf(tid)530	if err != nil {531		return "", err532	}533	var metadata grc721.Metadata534	if tp, ok := sealedData.Get(tid.String()).(traitPreset); thumb && ok && IsRevealed(tid) {535		stored, _ := nft.TokenMetadata(tid)536		metadata = traitOnlyMetadata(tp)537		metadata.ExternalURL = stored.ExternalURL538		metadata.BackgroundColor = "0a0b10"539		svg := artpkg.RenderTokenThumbSVG(tp.Chain, tp.Metal, tp.ShapeIdx, tp.ColorIdx, tp.Active, tid.String())540		metadata.Image = "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(svg))541	} else {542		metadata, err = currentMetadata(tid)543		if err != nil {544			return "", err545		}546	}547	enc := base64.StdEncoding548	fields := []string{549		tid.String(),550		owner.String(),551		enc.EncodeToString([]byte(metadata.Name)),552		enc.EncodeToString([]byte(metadata.Description)),553		enc.EncodeToString([]byte(metadata.Image)),554		enc.EncodeToString([]byte(metadata.ExternalURL)),555		enc.EncodeToString([]byte(traitsToCSV(metadata.Attributes))),556		enc.EncodeToString([]byte(metadata.BackgroundColor)),557	}558	return strings.Join(fields, "|"), nil559}560561func CollectionSummary(page int) string {562	total := len(mintedIDs)563	if page < 1 {564		page = 1565	}566	startK := (page - 1) * summaryPageSize567	endK := startK + summaryPageSize568	if endK > total {569		endK = total570	}571	var lines []string572	for k := startK; k < endK; k++ {573		tid := mintedIDs[total-1-k]574		line, err := thumbSummary(tid)575		if err != nil {576			continue577		}578		lines = append(lines, line)579	}580	return strings.Join(lines, "\n")581}582583const (584	walletScanChunk = 100585	walletRenderCap = summaryPageSize586)587588func WalletSummary(owner address, startIdx int64) (summary string, nextIdx int64) {589	total := len(mintedIDs)590	start := int(startIdx)591	if start < 0 {592		start = 0593	}594	end := start + walletScanChunk595	if end > total {596		end = total597	}598	var lines []string599	k := start600	for ; k < end; k++ {601		if len(lines) >= walletRenderCap {602			break603		}604		tid := mintedIDs[total-1-k]605		tokenOwner, err := nft.OwnerOf(tid)606		if err != nil || tokenOwner != owner {607			continue608		}609		line, err := thumbSummary(tid)610		if err != nil {611			continue612		}613		lines = append(lines, line)614	}615	return strings.Join(lines, "\n"), int64(k)616}617618func BurnedTokenSummary(tid grc721.TokenID) (string, error) {619	raw := burnedRecords.Get(tid.String())620	rec, ok := raw.(burnRecord)621	if !ok {622		return "", grc721.ErrInvalidTokenId623	}624	enc := base64.StdEncoding625	fields := []string{626		tid.String(),627		rec.BurnedBy.String(),628		ufmt.Sprintf("%d", rec.BurnHeight),629		enc.EncodeToString([]byte(rec.Metadata.Name)),630		enc.EncodeToString([]byte(rec.Metadata.Description)),631		enc.EncodeToString([]byte(rec.Metadata.Image)),632		enc.EncodeToString([]byte(rec.Metadata.ExternalURL)),633		enc.EncodeToString([]byte(traitsToCSV(rec.Metadata.Attributes))),634		enc.EncodeToString([]byte(rec.Metadata.BackgroundColor)),635	}636	return strings.Join(fields, "|"), nil637}638639func bigTokenImage(image string) string {640	const prefix = "data:image/svg+xml;base64,"641	if !strings.HasPrefix(image, prefix) {642		return image643	}644	raw, err := base64.StdEncoding.DecodeString(image[len(prefix):])645	if err != nil {646		return image647	}648	svg := string(raw)649	resized := strings.Replace(svg, `width="170.00" height="170.00"`, `width="`+tokenPageImageSize+`" height="`+tokenPageImageSize+`"`, 1)650	if resized == svg {651		resized = strings.Replace(svg, `width="170" height="170"`, `width="`+tokenPageImageSize+`" height="`+tokenPageImageSize+`"`, 1)652	}653	return prefix + base64.StdEncoding.EncodeToString([]byte(resized))654}655656func renderMintedListPage(b *strings.Builder, page int) {657	total := len(mintedIDs)658	if total == 0 {659		return660	}661	totalPages := (total + gnowebListLimit - 1) / gnowebListLimit662	if page < 1 {663		page = 1664	}665	if page > totalPages {666		page = totalPages667	}668	startK := (page - 1) * gnowebListLimit669	endK := startK + gnowebListLimit670	if endK > total {671		endK = total672	}673674	b.WriteString("\n## Minted so far\n\n")675	if totalPages > 1 {676		b.WriteString(ufmt.Sprintf("_page %d of %d -- %d minted total_\n\n", page, totalPages, total))677	}678	b.WriteString("| Image | Index | Name | Owner |\n|---|---|---|---|\n")679	for k := startK; k < endK; k++ {680		tid := mintedIDs[total-1-k]681		link := realmRelPath + ":token/" + tid.String()682		owner, err := nft.OwnerOf(tid)683		if err != nil {684			raw := burnedRecords.Get(tid.String())685			rec, ok := raw.(burnRecord)686			if !ok {687				continue // shouldn't happen: burned but no archive record688			}689			name := rec.Metadata.Name690			if name == "" {691				name = "Untitled"692			}693			b.WriteString(ufmt.Sprintf("| Burned | [#%s](%s) | %s (burned at block %d) | %s |\n", tid.String(), link, name, rec.BurnHeight, rec.BurnedBy))694			continue695		}696		name, image := listRow(tid)697		imgCell := ""698		if image != "" {699			imgCell = ufmt.Sprintf("[![%s](%s)](%s)", name, image, link)700		}701		b.WriteString(ufmt.Sprintf("| %s | [#%s](%s) | %s | %s |\n", imgCell, tid.String(), link, name, owner))702	}703	b.WriteString("\n")704705	renderPageLinks(b, page, totalPages, "page")706}707708func renderPageLinks(b *strings.Builder, page, totalPages int, linkBase string) {709	if totalPages <= 1 {710		return711	}712	b.WriteString("\n")713	parts := []string{}714	if page > 1 {715		parts = append(parts, ufmt.Sprintf("[<< First](%s:%s/1)", realmRelPath, linkBase))716		parts = append(parts, ufmt.Sprintf("[<- newer](%s:%s/%d)", realmRelPath, linkBase, page-1))717	}718	lastShown := 0719	for p := 1; p <= totalPages; p++ {720		if p != 1 && p != totalPages && (p < page-pageWindow || p > page+pageWindow) {721			continue722		}723		if lastShown != 0 && p != lastShown+1 {724			parts = append(parts, "...")725		}726		if p == page {727			parts = append(parts, ufmt.Sprintf("**%d**", p))728		} else {729			parts = append(parts, ufmt.Sprintf("[%d](%s:%s/%d)", p, realmRelPath, linkBase, p))730		}731		lastShown = p732	}733	if page < totalPages {734		parts = append(parts, ufmt.Sprintf("[older ->](%s:%s/%d)", realmRelPath, linkBase, page+1))735		parts = append(parts, ufmt.Sprintf("[Last >>](%s:%s/%d)", realmRelPath, linkBase, totalPages))736	}737	b.WriteString(strings.Join(parts, " - "))738	b.WriteString("\n")739}740741func renderBurnedListPage(b *strings.Builder, page int) {742	total := len(burnedIDs)743	if total == 0 {744		return745	}746	totalPages := (total + gnowebListLimit - 1) / gnowebListLimit747	if page < 1 {748		page = 1749	}750	if page > totalPages {751		page = totalPages752	}753	startK := (page - 1) * gnowebListLimit754	endK := startK + gnowebListLimit755	if endK > total {756		endK = total757	}758759	b.WriteString("\n## Burned tokens\n\n")760	if totalPages > 1 {761		b.WriteString(ufmt.Sprintf("_page %d of %d -- %d burned total_\n\n", page, totalPages, total))762	}763	b.WriteString("| Index | Name | Burned By | Block |\n|---|---|---|---|\n")764	for k := startK; k < endK; k++ {765		tid := burnedIDs[total-1-k]766		raw := burnedRecords.Get(tid.String())767		rec, ok := raw.(burnRecord)768		if !ok {769			continue770		}771		name := rec.Metadata.Name772		if name == "" {773			name = "Untitled"774		}775		b.WriteString(ufmt.Sprintf("| #%s | %s | %s | %d |\n", tid.String(), name, rec.BurnedBy, rec.BurnHeight))776	}777	b.WriteString("\n")778779	renderPageLinks(b, page, totalPages, "burned/page")780}781782func renderBurnedTokenPage(tid grc721.TokenID) string {783	raw := burnedRecords.Get(tid.String())784	rec, ok := raw.(burnRecord)785	if !ok {786		return render404()787	}788	name := rec.Metadata.Name789	if name == "" {790		name = "Untitled"791	}792	var b strings.Builder793	b.WriteString(ufmt.Sprintf("# %s #%s (burned)\n\n[<- back to collection](%s)\n\n", nft.Name(), tid.String(), realmRelPath))794	b.WriteString("## Details\n\n")795	b.WriteString(ufmt.Sprintf("* **Collection**: %s\n", nft.Name()))796	b.WriteString(ufmt.Sprintf("* **Name**: %s\n", name))797	b.WriteString(ufmt.Sprintf("* **Index**: #%s\n", tid.String()))798	if rec.Metadata.Description != "" {799		b.WriteString(ufmt.Sprintf("* **Description**: %s\n", rec.Metadata.Description))800	}801	b.WriteString(ufmt.Sprintf("* **Status**: burned by %s at block %d\n", rec.BurnedBy, rec.BurnHeight))802	b.WriteString(ufmt.Sprintf("* **Archive**: [all burned tokens](%s:burned)\n", realmRelPath))803	if len(rec.Metadata.Attributes) > 0 {804		b.WriteString("\n## Traits\n\n")805		for _, attr := range rec.Metadata.Attributes {806			b.WriteString(ufmt.Sprintf("* **%s**: %s\n", attr.TraitType, attr.Value))807		}808	}809	return b.String()810}811812func render404() string {813	return ufmt.Sprintf("# 404\n\n[<- back to collection](%s)\n", realmRelPath)814}815816func Render(path string) string {817	if path == "" {818		var b strings.Builder819		b.WriteString(nft.RenderHome())820		b.WriteString("\n## About\n\n")821		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")822		b.WriteString(ufmt.Sprintf("* **Owner**: %s\n", Owner()))823		b.WriteString(ufmt.Sprintf("* **Assembled trait combinations minted**: %d\n", int64(mintedTraitCombos.Size())))824		b.WriteString("* **Max supply**: unlimited\n")825		if mintingOpenMirror {826			b.WriteString("* **Minting**: open\n")827		} else {828			b.WriteString("* **Minting**: closed\n")829		}830		if mintPriceUgnot > 0 {831			b.WriteString(ufmt.Sprintf("* **Mint price**: %d ugnot\n", mintPriceUgnot))832		} else {833			b.WriteString("* **Mint price**: free\n")834		}835		b.WriteString(ufmt.Sprintf("* **Reveal delay**: %d block(s)\n", RevealDelayBlocks))836		if maxMintsPerWalletMirror > 0 {837			b.WriteString(ufmt.Sprintf("* **Max mints per wallet**: %d\n", maxMintsPerWalletMirror))838		} else {839			b.WriteString("* **Max mints per wallet**: unlimited\n")840		}841		if royaltyBps > 0 {842			b.WriteString(ufmt.Sprintf("* **Secondary sale royalty**: %d.%02d%% (EIP-2981 RoyaltyInfo)\n", royaltyBps/100, royaltyBps%100))843		}844		if len(burnedIDs) > 0 {845			b.WriteString(ufmt.Sprintf("* **Burned**: %d -- [view burned tokens ->](%s:burned)\n", len(burnedIDs), realmRelPath))846		}847848		renderMintedListPage(&b, 1)849		return b.String()850	}851852	parts := strings.Split(path, "/")853	if len(parts) == 2 && parts[0] == "page" {854		page, err := strconv.Atoi(parts[1])855		if err != nil || page < 1 {856			return render404()857		}858		var b strings.Builder859		b.WriteString(ufmt.Sprintf("# %s -- minted tokens\n\n[<- back to collection](%s)\n", nft.Name(), realmRelPath))860		renderMintedListPage(&b, page)861		return b.String()862	}863	if parts[0] == "burned" {864		page := 1865		if len(parts) == 3 && parts[1] == "page" {866			p, err := strconv.Atoi(parts[2])867			if err != nil || p < 1 {868				return render404()869			}870			page = p871		} else if len(parts) != 1 {872			return render404()873		}874		var b strings.Builder875		b.WriteString(ufmt.Sprintf("# %s -- burned tokens\n\n[<- back to collection](%s)\n", nft.Name(), realmRelPath))876		renderBurnedListPage(&b, page)877		return b.String()878	}879	if len(parts) == 2 && parts[0] == "token" {880		tid := grc721.TokenID(parts[1])881		owner, err := nft.OwnerOf(tid)882		if err != nil {883			return renderBurnedTokenPage(tid)884		}885		metadata, _ := currentMetadata(tid)886		var b strings.Builder887		b.WriteString(ufmt.Sprintf("# %s #%s\n\n[<- back to collection](%s)\n\n", nft.Name(), tid.String(), realmRelPath))888		image := metadata.Image889		if image == "" {890			image = metadata.ImageData891		}892		name := metadata.Name893		if name == "" {894			name = "Untitled"895		}896		if image != "" {897			b.WriteString(ufmt.Sprintf("![%s](%s)\n\n", name, bigTokenImage(image)))898		}899900		b.WriteString("## Details\n\n")901		b.WriteString(ufmt.Sprintf("* **Collection**: %s\n", nft.Name()))902		b.WriteString(ufmt.Sprintf("* **Name**: %s\n", name))903		b.WriteString(ufmt.Sprintf("* **Index**: #%s\n", tid.String()))904		if metadata.Description != "" {905			b.WriteString(ufmt.Sprintf("* **Description**: %s\n", metadata.Description))906		}907		b.WriteString(ufmt.Sprintf("* **Owner**: %s\n", owner))908		b.WriteString(ufmt.Sprintf("* **Revealed**: %t (age %d block(s))\n", IsRevealed(tid), Age(tid)))909910		if len(metadata.Attributes) > 0 {911			b.WriteString("\n## Traits\n\n")912			for _, attr := range metadata.Attributes {913				b.WriteString(ufmt.Sprintf("* **%s**: %s\n", attr.TraitType, attr.Value))914			}915		}916		return b.String()917	}918919	return render404()920}921922func checkErr(err error) {923	if err != nil {924		panic(err)925	}926}927

Functions

  • AddToWhitelist(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}, wallet string, bps int64)

  • AddToWhitelistBatch(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}, walletsCSV string, bps int64)

  • Age(tid string) int64

  • 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)

  • ApprovedVersion() string

  • BalanceOf(owner string) (int64, interface {Error func() string})

  • Burn(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}, tid string)

  • BurnedTokenSummary(tid string) (string, interface {Error func() string})

  • ClaimAt(index int) (wallet string, bps int64, mintHeight_ int64, tid string)

  • ClaimCount() int64

  • CollectionSummary(page int) string

  • EffectivePrice(caller string) int64

  • GetApproved(tid string) (string, interface {Error func() string})

  • IsApprovedForAll(owner string, operator string) bool

  • IsRevealed(tid string) bool

  • IsTraitComboAvailable(comboKey string) bool

  • MaxMintsPerWallet() int64

  • MintedIDs() []gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/grc721v2.TokenID

  • MintingOpen() bool

  • MintPrice() int64

  • MintsByWallet(owner string) int64

  • Name() string

  • Owner() string

  • OwnerOf(tid string) (string, interface {Error func() string})

  • RecordMint(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, payer string, tid string, chainName string, shapeIdx int, colorIdx int, metal string, active bool, comboKey string, rarity string) string

  • RemoveFromWhitelist(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}, wallet string)

  • Render(path string) string

  • ReportMaxMintsPerWallet(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}, max int64)

  • ReportMintingOpen(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}, open bool)

  • RoyaltyBps() int64

  • RoyaltyInfo(tid string, salePrice int64) (string, int64, interface {Error func() string})

  • RoyaltyRecipient() string

  • SafeTransferFrom(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)

  • 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)

  • SetApprovedVersion(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}, pkgpath string)

  • 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}, ugnot int64)

  • SetRoyaltyBps(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)

  • SetRoyaltyRecipient(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}, recipient string)

  • Symbol() string

  • TokenCount() int64

  • TokenMetadata(tid string) (struct{Image string; ImageData string; ExternalURL string; Description string; Name string; Attributes []gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/grc721v2.Trait; BackgroundColor string; AnimationURL string; YoutubeURL string}, interface {Error func() string})

  • TokenSummary(tid string) (string, interface {Error func() string})

  • TokenURI(tid string) (string, interface {Error func() string})

  • TotalBurned() int64

  • 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)

  • WalletSummary(owner string, startIdx int64) (summary string, nextIdx int64)

  • WhitelistBps(wallet string) (bps int64, ok bool)

  • WhitelistCount() int64

  • WhitelistEntry(index int) (wallet string, bps int64)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

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.