1package trialmint23import (4 "chain"5 "chain/banker"6 "chain/runtime"7 unsaferealm "chain/runtime/unsafe"8 "crypto/sha256"9 "strings"1011 artpkg "gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/art3d"12 grc721 "gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/grc721v2"13 stable "gno.land/r/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/trialmint/stable"14 "gno.land/p/nt/seqid/v0"15 "gno.land/p/nt/ufmt/v0"16)1718var (19 collectionOwnerHere address = "g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4"2021 mintingOpen = true2223 maxMintsPerWallet int64 = 02425 nextID seqid.ID26)2728func MintAssembled(cur realm, to address) grc721.TokenID {29 if !mintingOpen {30 panic("minting is closed")31 }32 if maxMintsPerWallet > 0 && callerAddress() != collectionOwnerHere && stable.MintsByWallet(to) >= maxMintsPerWallet {33 panic(ufmt.Sprintf("wallet %s has already reached the %d-mint limit for this collection", to.String(), maxMintsPerWallet))34 }3536 caller := callerAddress()37 requiredPrice := stable.EffectivePrice(caller)38 if requiredPrice != 0 {39 runtime.AssertOriginCall()40 if !unsaferealm.PreviousRealm().IsUserCall() {41 panic("payment verification requires a direct user call")42 }43 got := unsaferealm.OriginSend().AmountOf("ugnot")44 if got != requiredPrice {45 panic(ufmt.Sprintf("payment required: exactly %d ugnot, got %d", requiredPrice, got))46 }47 b := banker.NewBanker(banker.BankerTypeRealmSend, cur)48 b.SendCoins(cur.Address(), stable.Owner(), chain.Coins{{Denom: "ugnot", Amount: got}})49 }5051 tp, comboKey := assembleUniqueTraits()5253 tid := grc721.TokenID(ufmt.Sprintf("%d", uint64(nextID)+1))54 stable.RecordMint(cross(cur), to, caller, tid, tp.Chain, tp.ShapeIdx, tp.ColorIdx, tp.Metal, tp.Active, comboKey, rarityTier(tp))5556 nextID.Next()5758 return tid59}6061type traitPreset struct {62 Chain string63 ShapeIdx int64 ColorIdx int65 Metal string66 Active bool67}6869func traitComboKey(chainName string, shapeIdx, colorIdx int, metal string, active bool) string {70 return ufmt.Sprintf("%s|%d|%d|%s|%t", chainName, shapeIdx, colorIdx, metal, active)71}7273const activeProbability = 0.27475type prng struct {76 seed string77 n int78}7980func newPRNG(seed string) *prng { return &prng{seed: seed} }8182func (p *prng) next() uint64 {83 p.n++84 material := ufmt.Sprintf("%s:%d", p.seed, p.n)85 sum := sha256.Sum256([]byte(material))86 var v uint6487 for i := 0; i < 8; i++ {88 v = v<<8 | uint64(sum[i])89 }90 return v91}9293func (p *prng) floatIn(lo, hi float64) float64 {94 frac := float64(p.next()%1000000) / 1000000.095 return lo + frac*(hi-lo)96}9798func (p *prng) intn(n int) int {99 if n <= 1 {100 return 0101 }102 return int(p.next() % uint64(n))103}104105func randomTraitPreset(attempt int) traitPreset {106 material := ufmt.Sprintf("assemble:%d:%d:%s:%d",107 runtime.ChainHeight(), uint64(nextID), callerAddress().String(), attempt)108 rnd := newPRNG(material)109110 chainName := chainRoster[rnd.intn(len(chainRoster))]111 stone := artpkg.StoneForChain(chainName)112 colorIdx := artpkg.PickColorIndex(stone, rnd.next())113 var shapeIdx int114 if stone == "Pearl" {115 shapeIdx = rnd.intn(len(artpkg.PearlShapeNames))116 } else {117 cuts := artpkg.CutsForStone(stone)118 shapeIdx = cuts[rnd.intn(len(cuts))]119 }120 metal := artpkg.MetalNames[rnd.intn(len(artpkg.MetalNames))]121 active := rnd.floatIn(0, 1) < activeProbability || artpkg.ChainAlwaysActive(chainName)122123 return traitPreset{Chain: chainName, ShapeIdx: shapeIdx, ColorIdx: colorIdx, Metal: metal, Active: active}124}125126const maxAssembleAttempts = 200127128func assembleUniqueTraits() (traitPreset, string) {129 for attempt := 0; attempt < maxAssembleAttempts; attempt++ {130 tp := randomTraitPreset(attempt)131 key := traitComboKey(tp.Chain, tp.ShapeIdx, tp.ColorIdx, tp.Metal, tp.Active)132 if stable.IsTraitComboAvailable(key) {133 return tp, key134 }135 }136 panic("could not find an unused trait combination after many attempts -- the collection may be fully minted")137}138139func IsTraitComboAvailable(chainName string, shapeIdx, colorIdx int, metal string, active bool) bool {140 key := traitComboKey(chainName, shapeIdx, colorIdx, metal, active)141 return stable.IsTraitComboAvailable(key)142}143144func MaxUniqueTraitCombos() int64 {145 var total int64146 for _, chainName := range chainRoster {147 stone := artpkg.StoneForChain(chainName)148 shapeCount := len(artpkg.CutsForStone(stone))149 if stone == "Pearl" {150 shapeCount = len(artpkg.PearlShapeNames)151 }152 states := int64(2) // active or sunset153 if artpkg.ChainAlwaysActive(chainName) {154 states = 1155 }156 total += int64(artpkg.ColorPaletteSize(stone)) * int64(shapeCount) * states157 }158 return total * int64(len(artpkg.MetalNames))159}160161func ComboProbability(chainName string, shapeIdx, colorIdx int, active bool) float64 {162 stone := artpkg.StoneForChain(chainName)163 same := 0164 for _, c := range chainRoster {165 if artpkg.StoneForChain(c) == stone {166 same++167 }168 }169 p := float64(same) / float64(len(chainRoster))170 if stone == "Pearl" {171 p /= float64(len(artpkg.PearlShapeNames))172 } else {173 p /= float64(len(artpkg.CutsForStone(stone)))174 }175 p *= artpkg.ColorProbability(stone, colorIdx)176 if !artpkg.ChainAlwaysActive(chainName) {177 if active {178 p *= activeProbability179 } else {180 p *= 1 - activeProbability181 }182 }183 return p184}185186var rarityTiers = []struct {187 name string188 maxP float64189}{190 {"Legendary", 0.000127233877},191 {"Epic", 0.000423465423},192 {"Rare", 0.000754985755},193 {"Uncommon", 0.00364957265},194}195196func rarityTier(tp traitPreset) string {197 p := ComboProbability(tp.Chain, tp.ShapeIdx, tp.ColorIdx, tp.Active)198 for _, t := range rarityTiers {199 if p <= t.maxP {200 return t.name201 }202 }203 return "Common"204}205206var chainRoster = []string{207 "TEST-1", "TEST-2", "TEST-3", "TEST-4", "TEST-5", "TEST-6", "TEST-7",208 "TEST-8", "TEST-9", "TEST-10", "TEST-11", "TEST-12", "TEST-13",209 "TOPAZ-1", "SAPPHIRE-1", "PEARL-1", "BETANET", "GNOLAND-1",210}211212func AddChain(cur realm, chainName string) {213 assertOwner()214 if chainName == "" {215 panic("chain name must not be empty")216 }217 for _, c := range chainRoster {218 if c == chainName {219 panic("chain already in the roster")220 }221 }222 chainRoster = append(chainRoster, chainName)223}224225func ChainRosterSize() int64 { return int64(len(chainRoster)) }226func ChainRosterCSV() string { return strings.Join(chainRoster, ";") }227228func callerAddress() address {229 return unsaferealm.PreviousRealm().Address()230}231232func assertOwner() {233 if callerAddress() != collectionOwnerHere {234 panic("unauthorized: caller is not the collection owner")235 }236}237238func MintingOpen() bool { return mintingOpen }239240func SetMintingOpen(cur realm, open bool) {241 assertOwner()242 mintingOpen = open243 stable.ReportMintingOpen(cross(cur), open)244}245246func MaxMintsPerWallet() int64 { return maxMintsPerWallet }247248func SetMaxMintsPerWallet(cur realm, max int64) {249 assertOwner()250 if max < 0 {251 panic("max must be non-negative (0 = unlimited)")252 }253 maxMintsPerWallet = max254 stable.ReportMaxMintsPerWallet(cross(cur), max)255}256257func MintsByWallet(owner address) int64 {258 return stable.MintsByWallet(owner)259}260AddChain(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.
Render unavailable
rate limit exceeded — the render query is served by the Gnoland archive RPC.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.