1package guardians23import (4 "chain"5 "chain/runtime"6 "crypto/sha256"7 "encoding/hex"8 "strconv"9 "strings"10 "time"1112 "gno.land/p/nt/grc721/v0"13 "gno.land/p/nt/ufmt/v0"14)1516const (17 revealStateVeiled = "VEILED"18 revealStateCommitted = "REVEAL_COMMITTED"19 revealStateRevealing = "REVEALING"20 revealStateRevealed = "REVEALED"21 revealStateUnveiledFrozen = "UNVEILED_FROZEN"22 maxRevealBatch = 2023)2425var (26 revealState = revealStateVeiled27 provenanceRoot string28 revealCommitment string29 finalBaseURI string30 revealDeadline int6431 revealSeedHash string32 permutationA int6433 permutationB int6434 revealTarget int6435 revealProcessed int6436)3738func RevealState() string { return revealState }3940func ProvenanceRoot() string { return provenanceRoot }4142func RevealCommitment() string { return revealCommitment }4344func FinalBaseURI() string { return finalBaseURI }4546func RevealDeadline() int64 { return revealDeadline }4748func RevealSeedHash() string { return revealSeedHash }4950func RevealTarget() int64 { return revealTarget }5152func RevealProcessed() int64 { return revealProcessed }5354func RevealOverdue() bool {55 return revealState == revealStateCommitted && time.Now().Unix() > revealDeadline56}5758// ConfigureReveal permanently commits provenance, secret hash and final URI59// layout before the first token can be minted.60func ConfigureReveal(cur realm, provenance, commitment, baseURI string, deadline int64) {61 assertAdmin(cur.Previous().Address())62 if revealState != revealStateVeiled {63 panic("guardians: reveal configuration already committed")64 }65 if token.TotalSupply() != 0 || !salePaused {66 panic("guardians: configure reveal before minting and sale activation")67 }68 normalizedProvenance := normalizeHash(provenance)69 if normalizedProvenance == "" {70 panic("guardians: invalid provenance root")71 }72 normalizedCommitment := normalizeHash(commitment)73 if normalizedCommitment == "" {74 panic("guardians: invalid reveal commitment")75 }76 if !validFinalBaseURI(baseURI) {77 panic("guardians: final base uri must be ipfs/https and end with slash")78 }79 if deadline <= time.Now().Unix() {80 panic("guardians: reveal deadline must be in the future")81 }8283 provenanceRoot = normalizedProvenance84 revealCommitment = normalizedCommitment85 finalBaseURI = baseURI86 revealDeadline = deadline87 revealState = revealStateCommitted8889 chain.Emit(90 "RevealConfigured",91 "provenance", provenanceRoot,92 "commitment", revealCommitment,93 "deadline", ufmt.Sprintf("%d", revealDeadline),94 )95}9697// ComputeRevealCommitment is deterministic but production secrets should be98// committed with the offline helper, never exposed through a public RPC query.99func ComputeRevealCommitment(secret string) string {100 payload := runtime.ChainID() + "|" + CollectionID() + "|REVEAL|" + secret101 return hashBytes([]byte(payload))102}103104// Only the administrator can manually begin reveal after sale end or sellout.105// Neither a deadline nor knowledge of the secret alone starts the reveal.106func Reveal(cur realm, secret string) {107 assertAdmin(cur.Previous().Address())108 if revealState != revealStateCommitted {109 panic("guardians: reveal is not committed")110 }111 if len(secret) < 8 || len(secret) > 256 {112 panic("guardians: reveal secret length must be 8..256")113 }114 // publicEnd=0 means still open, so it must never unlock an early reveal.115 ended := saleConfig.publicEnd > 0 && time.Now().Unix() >= saleConfig.publicEnd116 if !saleConfigured || (!ended && token.TotalSupply() < maxSupply) {117 panic("guardians: public sale has not ended")118 }119 if ComputeRevealCommitment(secret) != revealCommitment {120 panic("guardians: reveal secret does not match commitment")121 }122 if token.TotalSupply() == 0 {123 panic("guardians: cannot reveal an empty collection")124 }125126 revealSeedHash = hashBytes([]byte("GUARDIANS|SEED|" + secret))127 permutationA = derivePermutationA(secret)128 permutationB = deriveSeedNumber("B", secret) % maxSupply129 revealTarget = token.TotalSupply()130 revealProcessed = 0131 revealState = revealStateRevealing132133 chain.Emit(134 "RevealStarted",135 "seedHash", revealSeedHash,136 "target", ufmt.Sprintf("%d", revealTarget),137 )138}139140// ApplyRevealBatch updates sequentially minted tokens. Anyone may execute the141// deterministic batch, so the project operator cannot block progress.142func ApplyRevealBatch(startTokenID int64, quantity int) {143 if revealState != revealStateRevealing {144 panic("guardians: reveal batch is not active")145 }146 if quantity < 1 || quantity > maxRevealBatch {147 panic("guardians: reveal batch quantity must be 1..20")148 }149 if startTokenID != revealProcessed+1 {150 panic("guardians: reveal batch must continue sequentially")151 }152 end := startTokenID + int64(quantity) - 1153 if end > revealTarget {154 panic("guardians: reveal batch exceeds target")155 }156157 for tokenID := startTokenID; tokenID <= end; tokenID++ {158 metadataID := MetadataID(tokenID)159 uri := finalBaseURI + metadataFilename(metadataID)160 if err := metadataLedger.SetTokenURI(grc721.TokenID(ufmt.Sprintf("%d", tokenID)), uri); err != nil {161 panic(err)162 }163 }164 revealProcessed = end165 if revealProcessed == revealTarget {166 revealState = revealStateRevealed167 }168169 chain.Emit(170 "RevealBatchApplied",171 "startTokenId", ufmt.Sprintf("%d", startTokenID),172 "endTokenId", ufmt.Sprintf("%d", end),173 "processed", ufmt.Sprintf("%d", revealProcessed),174 )175}176177// metadataFilename matches the immutable IPFS release layout (0001.json–3333.json).178func metadataFilename(metadataID int64) string {179 serial := ufmt.Sprintf("%d", metadataID)180 for len(serial) < 4 {181 serial = "0" + serial182 }183 return serial + ".json"184}185186func MetadataID(tokenID int64) int64 {187 if revealState != revealStateRevealing &&188 revealState != revealStateRevealed &&189 revealState != revealStateUnveiledFrozen {190 panic("guardians: metadata mapping is not revealed")191 }192 if tokenID < 1 || tokenID > revealTarget {193 panic("guardians: token id outside reveal target")194 }195 return (permutationA*(tokenID-1)+permutationB)%maxSupply + 1196}197198func FreezeMetadata(cur realm) {199 assertAdmin(cur.Previous().Address())200 if revealState != revealStateRevealed || revealProcessed != revealTarget {201 panic("guardians: complete reveal before freezing metadata")202 }203 revealState = revealStateUnveiledFrozen204 chain.Emit("MetadataFrozen", "provenance", provenanceRoot, "seedHash", revealSeedHash)205}206207func assertMintingAllowed() {208 if revealState != revealStateVeiled && revealState != revealStateCommitted {209 panic("guardians: minting is closed after reveal starts")210 }211}212213func validFinalBaseURI(uri string) bool {214 return (strings.HasPrefix(uri, "ipfs://") || strings.HasPrefix(uri, "https://")) &&215 strings.HasSuffix(uri, "/")216}217218func derivePermutationA(secret string) int64 {219 candidate := deriveSeedNumber("A", secret) % maxSupply220 if candidate == 0 {221 candidate = 1222 }223 for gcd(candidate, maxSupply) != 1 {224 candidate++225 if candidate >= maxSupply {226 candidate = 1227 }228 }229 return candidate230}231232func deriveSeedNumber(label, secret string) int64 {233 hash := hashBytes([]byte("GUARDIANS|PERMUTATION|" + label + "|" + secret))234 value, err := strconv.ParseInt(hash[:15], 16, 64)235 if err != nil {236 panic(err)237 }238 return value239}240241func gcd(first, second int64) int64 {242 for second != 0 {243 first, second = second, first%second244 }245 return first246}247248func normalizeHash(value string) string {249 decoded, err := hex.DecodeString(value)250 if err != nil || len(decoded) != sha256.Size {251 return ""252 }253 return hex.EncodeToString(decoded)254}255Admin() string
AdminMint(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, quantity int)
AllowlistEnd() int64
AllowlistLeaf(owner string, allowance int, unitPrice int64) string
AllowlistMinted(owner string) int
AllowlistMintedSupply() int64
AllowlistPrice() int64
AllowlistReservedSupply() int64
AllowlistReserveReleased() bool
AllowlistReserveRemaining() int64
AllowlistRoot() string
AllowlistRootLocked() bool
AllowlistStart() int64
AllowlistUnclaimedSupply() int64
AllowlistWalletLimit() int
ApplyRevealBatch(startTokenID int64, quantity 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, tokenID string)
AuthorizedMarketplace() string
BalanceOf(owner string) int64
CollectionID() string
ComputeRevealCommitment(secret string) string
ConfigureReveal(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}, provenance string, commitment string, baseURI string, deadline int64)
ConfigureSale(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}, allowlistStart int64, allowlistEnd int64, publicStart int64, publicEnd int64, allowlistPrice int64, publicPrice int64, allowlistLimit int, publicLimit int, treasury string)
DefaultRoyalty() (string, int64)
EnumerableSupply() int64
FinalBaseURI() string
FreezeMetadata(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})
GetApproved(tokenID string) string
IsApprovedForAll(owner string, operator string) bool
LaunchVersion() string
MarketplaceTransfer(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}, seller string, buyer string, tokenID string, grossPrice int64, nonce int64)
MaxSupply() int64
MetadataID(tokenID int64) int64
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}, freeQuantity int, paidQuantity int, allowance int, unitPrice int64, proof string)
MintAllowlist(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}, quantity int, allowance int, unitPrice int64, proof string)
MintPublic(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}, quantity int)
Name() string
NextSaleTimestamp() int64
OwnerOf(tokenID string) string
ProvenanceRoot() string
PublicEnd() int64
PublicMintableSupply() int64
PublicMinted(owner string) int
PublicPrice() int64
PublicStart() int64
PublicWalletLimit() int
ReleaseAllowlistReserve(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})
Render(path string) 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}, secret string)
RevealCommitment() string
RevealDeadline() int64
RevealOverdue() bool
RevealProcessed() int64
RevealSeedHash() string
RevealState() string
RevealTarget() int64
RoyaltyInfo(tokenID string, salePrice int64) (string, int64)
RoyaltyMaxBps() int64
SaleConfigured() bool
SalePaused() bool
SalePhase() string
SalePhaseAt(unixTime int64) string
SaleSnapshot() string
SaleTreasury() string
SetAllowlistRoot(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}, root 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)
SetAuthorizedMarketplace(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}, marketplace string)
SetDefaultRoyalty(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}, receiver string, bps int64)
SetPublicEnd(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}, end int64)
SetSalePaused(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}, paused bool)
SetVeiledURI(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}, uri string)
Status() string
Symbol() string
TokenByIndex(index int64) string
TokenOfOwnerByIndex(owner string, index int64) string
TokenURI(tokenID string) string
TotalSupply() 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, tokenID string)
VeiledURI() string
VerifyAllowlist(owner string, allowance int, unitPrice int64, proof string) bool
WalletSnapshot(owner string) string
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.