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}