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/g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems/gems/v1

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v1
Namespace
g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems / gems
Files
2 (gnomod.toml)
Exported functions
12
Module
gno.land/r/g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems/gems/v1
gno
0.9

Files (2)

  • gnomod.tomltoml
  • v1.gnogno
v1.gnogno
1// Package gems6v2 (deployed at .../gems6v2/v5, wired to ../stablev5; v4 with the gemsart3d2 art) is the mint-mechanics2// half of the gems6v2 candidate design: trait assembly and chain-3// roster management live here, same as the real gems6/v1. The one4// real difference is what MintAssembled does with the assembled5// traits: it never renders anything -- no gemsart.RenderTokenSVG call6// at all -- it just hands the raw trait fields to ../stable's7// RecordMint, which writes a cheap sealed placeholder. Rendering8// happens later, lazily, on a read (see ../stable's currentMetadata).9// This realm still imports gemsartfab, but only for its roster helper10// functions (StoneForChain, ColorPaletteSize, ShapeNames, ...) used to11// pick a valid random combination -- never for the expensive render12// itself.13package gems1415import (16	"chain"17	"chain/banker"18	"chain/runtime"19	unsaferealm "chain/runtime/unsafe"20	"crypto/sha256"21	"strings"2223	gemsart "gno.land/p/g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems/gemsart3d2"24	"gno.land/p/nt/grc721/v0"25	stable "gno.land/r/g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems/gems/stable"26	"gno.land/p/nt/seqid/v0"27	"gno.land/p/nt/ufmt/v0"28)2930var (31	collectionOwnerHere address = "g17cjym5e9hhws46lt6329pv2gtx2ay0503hgems"3233	mintingOpen = true3435	maxMintsPerWallet int64 = 03637	nextID seqid.ID38)3940/* -------------------------------- Minting ---------------------------------- */4142// MintAssembled: assembles a fresh random (chain, shape, color, metal,43// active) combination, verifies payment against ../stable's discount-44// adjusted price, then hands the RAW trait fields (not a rendered45// image) to ../stable.RecordMint -- the render happens later, lazily,46// on a read. This is the whole fix: nothing shape-dependent runs47// inside this transaction, so its real gas cost stays small and48// uniform regardless of which random trait combination gets drawn49// (confirmed on the throwaway revealpoc spike before this real50// split-realm version was built).51func MintAssembled(cur realm, to address) grc721.TokenID {52	if !mintingOpen {53		panic("minting is closed")54	}55	if maxMintsPerWallet > 0 && callerAddress() != collectionOwnerHere && stable.MintsByWallet(to) >= maxMintsPerWallet {56		panic(ufmt.Sprintf("wallet %s has already reached the %d-mint limit for this collection", to.String(), maxMintsPerWallet))57	}5859	caller := callerAddress()60	requiredPrice := stable.EffectivePrice(caller)61	if requiredPrice != 0 {62		runtime.AssertOriginCall()63		if !unsaferealm.PreviousRealm().IsUserCall() {64			panic("payment verification requires a direct user call")65		}66		got := unsaferealm.OriginSend().AmountOf("ugnot")67		if got != requiredPrice {68			panic(ufmt.Sprintf("payment required: exactly %d ugnot, got %d", requiredPrice, got))69		}70		b := banker.NewBanker(banker.BankerTypeRealmSend, cur)71		b.SendCoins(cur.Address(), stable.Owner(), chain.Coins{{Denom: "ugnot", Amount: got}})72	}7374	tp, comboKey := assembleUniqueTraits()7576	// plain numbers from 1 (#1, #2, ...), not seqid's padded base-3277	// string ("000003q"): what collectors and marketplaces expect to read78	tid := grc721.TokenID(ufmt.Sprintf("%d", uint64(nextID)+1))79	stable.RecordMint(cross(cur), to, caller, tid, tp.Chain, tp.ShapeIdx, tp.ColorIdx, tp.Metal, tp.Active, comboKey, rarityTier(tp))8081	nextID.Next()8283	return tid84}8586/* --------------------------- trait assembly --------------------------- */8788type traitPreset struct {89	Chain    string90	ShapeIdx int91	ColorIdx int92	Metal    string93	Active   bool94}9596func traitComboKey(chainName string, shapeIdx, colorIdx int, metal string, active bool) string {97	return ufmt.Sprintf("%s|%d|%d|%s|%t", chainName, shapeIdx, colorIdx, metal, active)98}99100const activeProbability = 0.2101102type prng struct {103	seed string104	n    int105}106107func newPRNG(seed string) *prng { return &prng{seed: seed} }108109func (p *prng) next() uint64 {110	p.n++111	material := ufmt.Sprintf("%s:%d", p.seed, p.n)112	sum := sha256.Sum256([]byte(material))113	var v uint64114	for i := 0; i < 8; i++ {115		v = v<<8 | uint64(sum[i])116	}117	return v118}119120func (p *prng) floatIn(lo, hi float64) float64 {121	frac := float64(p.next()%1000000) / 1000000.0122	return lo + frac*(hi-lo)123}124125func (p *prng) intn(n int) int {126	if n <= 1 {127		return 0128	}129	return int(p.next() % uint64(n))130}131132func randomTraitPreset(attempt int) traitPreset {133	material := ufmt.Sprintf("assemble:%d:%d:%s:%d",134		runtime.ChainHeight(), uint64(nextID), callerAddress().String(), attempt)135	rnd := newPRNG(material)136137	chainName := chainRoster[rnd.intn(len(chainRoster))]138	stone := gemsart.StoneForChain(chainName)139	// colour by the stone's real-world odds (gemsart.colorWeights)140	colorIdx := gemsart.PickColorIndex(stone, rnd.next())141	// only cuts the stone is really sold in; pearls have their own shapes142	var shapeIdx int143	if stone == "Pearl" {144		shapeIdx = rnd.intn(len(gemsart.PearlShapeNames))145	} else {146		cuts := gemsart.CutsForStone(stone)147		shapeIdx = cuts[rnd.intn(len(cuts))]148	}149	metal := gemsart.MetalNames[rnd.intn(len(gemsart.MetalNames))]150	// mainnet never sunsets: always Active, so metadata matches the image151	active := rnd.floatIn(0, 1) < activeProbability || gemsart.ChainAlwaysActive(chainName)152153	return traitPreset{Chain: chainName, ShapeIdx: shapeIdx, ColorIdx: colorIdx, Metal: metal, Active: active}154}155156const maxAssembleAttempts = 200157158func assembleUniqueTraits() (traitPreset, string) {159	for attempt := 0; attempt < maxAssembleAttempts; attempt++ {160		tp := randomTraitPreset(attempt)161		key := traitComboKey(tp.Chain, tp.ShapeIdx, tp.ColorIdx, tp.Metal, tp.Active)162		if stable.IsTraitComboAvailable(key) {163			return tp, key164		}165	}166	panic("could not find an unused trait combination after many attempts -- the collection may be fully minted")167}168169func IsTraitComboAvailable(chainName string, shapeIdx, colorIdx int, metal string, active bool) bool {170	key := traitComboKey(chainName, shapeIdx, colorIdx, metal, active)171	return stable.IsTraitComboAvailable(key)172}173174func MaxUniqueTraitCombos() int64 {175	var total int64176	for _, chainName := range chainRoster {177		stone := gemsart.StoneForChain(chainName)178		shapeCount := len(gemsart.CutsForStone(stone))179		if stone == "Pearl" {180			shapeCount = len(gemsart.PearlShapeNames)181		}182		states := int64(2) // active or sunset183		if gemsart.ChainAlwaysActive(chainName) {184			states = 1185		}186		total += int64(gemsart.ColorPaletteSize(stone)) * int64(shapeCount) * states187	}188	return total * int64(len(gemsart.MetalNames))189}190191/* -------------------------------- rarity -------------------------------- */192193// ComboProbability is the chance (0..1) that one mint draws this piece's194// look: its stone (the share of roster chains that carry it), its cut195// (even among the stone's cuts), its colour (the stone's colour odds) and196// its status (20% active; always active on mainnet). Ring metal and the197// chain's own name are equally likely for every piece, so they leave the198// ranking unchanged and are left out.199func ComboProbability(chainName string, shapeIdx, colorIdx int, active bool) float64 {200	stone := gemsart.StoneForChain(chainName)201	same := 0202	for _, c := range chainRoster {203		if gemsart.StoneForChain(c) == stone {204			same++205		}206	}207	p := float64(same) / float64(len(chainRoster))208	if stone == "Pearl" {209		p /= float64(len(gemsart.PearlShapeNames))210	} else {211		p /= float64(len(gemsart.CutsForStone(stone)))212	}213	p *= gemsart.ColorProbability(stone, colorIdx)214	if !gemsart.ChainAlwaysActive(chainName) {215		if active {216			p *= activeProbability217		} else {218			p *= 1 - activeProbability219		}220	}221	return p222}223224// rarityTiers are upper bounds on ComboProbability, rarest first. Set225// (2026-09-25) from the full distribution of all 691 looks on the226// 18-chain roster, each bound the midpoint between two neighbouring227// probability values, so no two equally likely looks fall on different228// sides and float rounding cannot move one. Shares of mints:229//   Legendary 1.3%, Epic 3.5%, Rare 11.1%, Uncommon 25.0%, Common 59.1%.230// A tier is fixed at mint (stored with the token); adding a chain later231// only changes the odds of new mints, never an existing token's tier.232var rarityTiers = []struct {233	name string234	maxP float64235}{236	{"Legendary", 0.000127233877},237	{"Epic", 0.000423465423},238	{"Rare", 0.000754985755},239	{"Uncommon", 0.00364957265},240}241242func rarityTier(tp traitPreset) string {243	p := ComboProbability(tp.Chain, tp.ShapeIdx, tp.ColorIdx, tp.Active)244	for _, t := range rarityTiers {245		if p <= t.maxP {246			return t.name247		}248	}249	return "Common"250}251252/* ----------------------------- Chain roster ---------------------------- */253254var chainRoster = []string{255	"TEST-1", "TEST-2", "TEST-3", "TEST-4", "TEST-5", "TEST-6", "TEST-7",256	"TEST-8", "TEST-9", "TEST-10", "TEST-11", "TEST-12", "TEST-13",257	"TOPAZ-1", "SAPPHIRE-1", "PEARL-1", "BETANET", "GNOLAND-1",258}259260func AddChain(cur realm, chainName string) {261	assertOwner()262	if chainName == "" {263		panic("chain name must not be empty")264	}265	for _, c := range chainRoster {266		if c == chainName {267			panic("chain already in the roster")268		}269	}270	chainRoster = append(chainRoster, chainName)271}272273func ChainRosterSize() int64 { return int64(len(chainRoster)) }274func ChainRosterCSV() string { return strings.Join(chainRoster, ";") }275276/* ----------------------------- Owner controls -------------------------------- */277278func callerAddress() address {279	return unsaferealm.PreviousRealm().Address()280}281282func assertOwner() {283	if callerAddress() != collectionOwnerHere {284		panic("unauthorized: caller is not the collection owner")285	}286}287288func MintingOpen() bool { return mintingOpen }289290func SetMintingOpen(cur realm, open bool) {291	assertOwner()292	mintingOpen = open293	stable.ReportMintingOpen(cross(cur), open)294}295296func MaxMintsPerWallet() int64 { return maxMintsPerWallet }297298func SetMaxMintsPerWallet(cur realm, max int64) {299	assertOwner()300	if max < 0 {301		panic("max must be non-negative (0 = unlimited)")302	}303	maxMintsPerWallet = max304	stable.ReportMaxMintsPerWallet(cross(cur), max)305}306307func MintsByWallet(owner address) int64 {308	return stable.MintsByWallet(owner)309}310

Functions

  • AddChain(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}, chainName string)

  • ChainRosterCSV() string

  • ChainRosterSize() int64

  • ComboProbability(chainName string, shapeIdx int, colorIdx int, active bool) float64

  • IsTraitComboAvailable(chainName string, shapeIdx int, colorIdx int, metal string, active bool) bool

  • MaxMintsPerWallet() int64

  • MaxUniqueTraitCombos() int64

  • MintAssembled(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) string

  • MintingOpen() bool

  • MintsByWallet(owner string) int64

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

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

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

Rendered

RenderedRawgnoweb ↗
This realm does not provide renderable output (vm/qrender → NoRenderDeclError). View on gnoweb ↗

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.