1package metadata23import (4 "chain"56 "gno.land/p/nt/grc721/v0"7)89const Kind = "metadata"1011// NewMetadata attaches a metadata extension and registers it as a core Extension.12// Attach it before the first mint so every token is indexed.13func NewMetadata(coreLedger *grc721.PrivateLedger) (*Metadata, *Ledger) {14 if coreLedger == nil {15 panic("metadata: nil core ledger")16 }1718 st := &storage{}19 core := coreLedger.ReadToken()20 led := &Ledger{core: core, st: st}21 coreLedger.RegisterExtension(led)2223 return &Metadata{core: core, st: st}, led24}2526func (m *Metadata) ExtensionKind() string { return Kind }2728func (m *Metadata) TokenID() string { return m.core.ID() }2930func (m *Metadata) TokenURI(tid grc721.TokenID) (string, error) {31 v := m.st.uris.Get(tid.String())32 if v == nil {33 return "", ErrNoTokenURI34 }3536 return v.(string), nil37}3839func (m *Metadata) TokenMetadata(tid grc721.TokenID) (Data, error) {40 v := m.st.data.Get(tid.String())41 if v == nil {42 return Data{}, ErrNoMetadata43 }4445 return v.(Data), nil46}4748func (m *Metadata) HasTokenURI(tid grc721.TokenID) bool {49 return m.st.uris.Has(tid.String())50}5152func (m *Metadata) HasMetadata(tid grc721.TokenID) bool {53 return m.st.data.Has(tid.String())54}5556// SetTokenURI sets the EIP-721 tokenURI of tid; issuer-only so a holder cannot57// rewrite the URI after acquisition.58func (led *Ledger) SetTokenURI(tid grc721.TokenID, uri string) error {59 if _, err := led.core.OwnerOf(tid); err != nil {60 return ErrTokenNotMinted61 }6263 led.st.uris.Set(tid.String(), uri)6465 chain.Emit(66 TokenURIUpdateEvent,67 "token", led.core.ID(),68 "tokenId", tid.String(),69 "uri", uri,70 )7172 return nil73}7475// SetTokenMetadata sets the OpenSea on-chain metadata of tid; issuer-only.76func (led *Ledger) SetTokenMetadata(tid grc721.TokenID, data Data) error {77 if _, err := led.core.OwnerOf(tid); err != nil {78 return ErrTokenNotMinted79 }8081 led.st.data.Set(tid.String(), data)8283 chain.Emit(84 MetadataUpdateEvent,85 "token", led.core.ID(),86 "tokenId", tid.String(),87 )8889 return nil90}9192func (led *Ledger) ExtensionKind() string { return Kind }9394func (led *Ledger) OnMint(to address, tid grc721.TokenID) {}95func (led *Ledger) OnTransfer(from, to address, tid grc721.TokenID) {}9697// OnBurn clears per-token state so it cannot resurface on re-mint.98func (led *Ledger) OnBurn(tid grc721.TokenID) {99 tidStr := tid.String()100 led.st.uris.Remove(tidStr)101 led.st.data.Remove(tidStr)102}103Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.