Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

grc721_metadata.gno

4.52 Kb · 199 lines
  1package grc721v2
  2
  3import (
  4	"chain"
  5	"encoding/base64"
  6	"strings"
  7
  8	"gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/avl"
  9)
 10
 11type MetadataNFT struct {
 12	*BasicNFT
 13	extensions *avl.Tree // AVL tree for storing metadata extensions
 14}
 15
 16var _ IGRC721MetadataOnchain = (*MetadataNFT)(nil)
 17
 18func NewNFTWithMetadata(_ int, rlm realm, name, symbol string) *MetadataNFT {
 19	return &MetadataNFT{
 20		BasicNFT:   NewBasicNFT(0, rlm, name, symbol),
 21		extensions: avl.NewTree(),
 22	}
 23}
 24
 25func (s *MetadataNFT) SetTokenMetadata(caller address, tid TokenID, metadata Metadata) error {
 26	owner, err := s.OwnerOf(tid)
 27	if err != nil {
 28		return err
 29	}
 30	if caller != owner {
 31		return ErrCallerIsNotOwner
 32	}
 33
 34	s.extensions.Set(tid.String(), metadata)
 35
 36	chain.Emit(
 37		MetadataUpdateEvent,
 38		"token", s.ID(),
 39		"tokenId", tid.String(),
 40	)
 41
 42	return nil
 43}
 44
 45func (s *MetadataNFT) TokenMetadata(tid TokenID) (Metadata, error) {
 46	metadata := s.extensions.Get(tid.String())
 47	if metadata == nil {
 48		return Metadata{}, ErrInvalidTokenId
 49	}
 50
 51	return metadata.(Metadata), nil
 52}
 53
 54func (s *MetadataNFT) Name() string {
 55	return s.BasicNFT.Name()
 56}
 57
 58func (s *MetadataNFT) Symbol() string {
 59	return s.BasicNFT.Symbol()
 60}
 61
 62func (s *MetadataNFT) TokenCount() int64 {
 63	return s.BasicNFT.TokenCount()
 64}
 65
 66func (s *MetadataNFT) BalanceOf(addr address) (int64, error) {
 67	return s.BasicNFT.BalanceOf(addr)
 68}
 69
 70func (s *MetadataNFT) OwnerOf(tid TokenID) (address, error) {
 71	return s.BasicNFT.OwnerOf(tid)
 72}
 73
 74func (s *MetadataNFT) TokenURI(tid TokenID) (string, error) {
 75	metadata, err := s.TokenMetadata(tid)
 76	if err != nil {
 77		return "", err
 78	}
 79	return metadataDataURI(metadata), nil
 80}
 81
 82func jsonEscape(s string) string {
 83	var b strings.Builder
 84	for _, r := range s {
 85		switch r {
 86		case '\\':
 87			b.WriteString(`\\`)
 88		case '"':
 89			b.WriteString(`\"`)
 90		case '\n':
 91			b.WriteString(`\n`)
 92		case '\r':
 93			b.WriteString(`\r`)
 94		case '\t':
 95			b.WriteString(`\t`)
 96		default:
 97			b.WriteRune(r)
 98		}
 99	}
100	return b.String()
101}
102
103func metadataDataURI(m Metadata) string {
104	image := m.Image
105	if image == "" {
106		image = m.ImageData
107	}
108
109	var b strings.Builder
110	b.WriteString(`{"name":"`)
111	b.WriteString(jsonEscape(m.Name))
112	b.WriteString(`","description":"`)
113	b.WriteString(jsonEscape(m.Description))
114	b.WriteString(`","image":"`)
115	b.WriteString(jsonEscape(image))
116	b.WriteString(`"`)
117	if m.ExternalURL != "" {
118		b.WriteString(`,"external_url":"`)
119		b.WriteString(jsonEscape(m.ExternalURL))
120		b.WriteString(`"`)
121	}
122	if m.BackgroundColor != "" {
123		b.WriteString(`,"background_color":"`)
124		b.WriteString(jsonEscape(m.BackgroundColor))
125		b.WriteString(`"`)
126	}
127	if m.AnimationURL != "" {
128		b.WriteString(`,"animation_url":"`)
129		b.WriteString(jsonEscape(m.AnimationURL))
130		b.WriteString(`"`)
131	}
132	if m.YoutubeURL != "" {
133		b.WriteString(`,"youtube_url":"`)
134		b.WriteString(jsonEscape(m.YoutubeURL))
135		b.WriteString(`"`)
136	}
137	b.WriteString(`,"attributes":[`)
138	for i, t := range m.Attributes {
139		if i > 0 {
140			b.WriteString(",")
141		}
142		b.WriteString(`{"trait_type":"`)
143		b.WriteString(jsonEscape(t.TraitType))
144		b.WriteString(`","value":"`)
145		b.WriteString(jsonEscape(t.Value))
146		b.WriteString(`"}`)
147	}
148	b.WriteString("]}")
149
150	return "data:application/json;base64," + base64.StdEncoding.EncodeToString([]byte(b.String()))
151}
152
153func (s *MetadataNFT) SetTokenURI(caller address, tid TokenID, tURI TokenURI) (bool, error) {
154	return s.BasicNFT.SetTokenURI(caller, tid, tURI)
155}
156
157func (s *MetadataNFT) IsApprovedForAll(owner, operator address) bool {
158	return s.BasicNFT.IsApprovedForAll(owner, operator)
159}
160
161func (s *MetadataNFT) Approve(caller, to address, tid TokenID) error {
162	return s.BasicNFT.Approve(caller, to, tid)
163}
164
165func (s *MetadataNFT) GetApproved(tid TokenID) (address, error) {
166	return s.BasicNFT.GetApproved(tid)
167}
168
169func (s *MetadataNFT) SetApprovalForAll(caller, operator address, approved bool) error {
170	return s.BasicNFT.SetApprovalForAll(caller, operator, approved)
171}
172
173func (s *MetadataNFT) SafeTransferFrom(caller, from, to address, tid TokenID) error {
174	return s.BasicNFT.SafeTransferFrom(caller, from, to, tid)
175}
176
177func (s *MetadataNFT) TransferFrom(caller, from, to address, tid TokenID) error {
178	return s.BasicNFT.TransferFrom(caller, from, to, tid)
179}
180
181func (s *MetadataNFT) Mint(to address, tid TokenID) error {
182	return s.BasicNFT.Mint(to, tid)
183}
184
185func (s *MetadataNFT) SafeMint(to address, tid TokenID) error {
186	return s.BasicNFT.SafeMint(to, tid)
187}
188
189func (s *MetadataNFT) Burn(tid TokenID) error {
190	if err := s.BasicNFT.Burn(tid); err != nil {
191		return err
192	}
193	s.extensions.Remove(tid.String())
194	return nil
195}
196
197func (s *MetadataNFT) RenderHome() string {
198	return s.BasicNFT.RenderHome()
199}