basic_nft.gno
8.21 Kb · 411 lines
1package grc721v2
2
3import (
4 "chain"
5 unsaferealm "chain/runtime/unsafe"
6 "math/overflow"
7 "strconv"
8
9 "gno.land/p/g1wx60nkcp5lfsxmedeg7sfs47tzxkath05u63t4/avl"
10 "gno.land/p/nt/ufmt/v0"
11)
12
13type BasicNFT struct {
14 name string
15 symbol string
16 origRealm string // owning realm's package path
17 owners avl.Tree // tokenId -> OwnerAddress
18 balances avl.Tree // OwnerAddress -> TokenCount
19 tokenApprovals avl.Tree // TokenId -> ApprovedAddress
20 tokenURIs avl.Tree // TokenId -> URIs
21 operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool
22}
23
24func NewBasicNFT(_ int, _ realm, name, symbol string) *BasicNFT {
25 pkgPath := unsaferealm.CurrentRealm().PkgPath()
26 if pkgPath == "" {
27 panic(ErrNotRealm)
28 }
29 if !validName(name) {
30 panic(ErrInvalidName)
31 }
32 if !validSymbol(symbol) {
33 panic(ErrInvalidSymbol)
34 }
35
36 return &BasicNFT{
37 name: name,
38 symbol: symbol,
39 origRealm: pkgPath,
40
41 owners: avl.Tree{},
42 balances: avl.Tree{},
43 tokenApprovals: avl.Tree{},
44 tokenURIs: avl.Tree{},
45 operatorApprovals: avl.Tree{},
46 }
47}
48
49func (s *BasicNFT) Name() string { return s.name }
50func (s *BasicNFT) Symbol() string { return s.symbol }
51func (s *BasicNFT) TokenCount() int64 { return int64(s.owners.Size()) }
52
53func (s *BasicNFT) ID() string {
54 return s.origRealm + "." + s.symbol
55}
56
57func (s *BasicNFT) BalanceOf(addr address) (int64, error) {
58 if err := isValidAddress(addr); err != nil {
59 return 0, err
60 }
61
62 balance := s.balances.Get(addr.String())
63 if balance == nil {
64 return 0, nil
65 }
66
67 return balance.(int64), nil
68}
69
70func (s *BasicNFT) OwnerOf(tid TokenID) (address, error) {
71 owner := s.owners.Get(string(tid))
72 if owner == nil {
73 return "", ErrInvalidTokenId
74 }
75
76 return owner.(address), nil
77}
78
79func (s *BasicNFT) TokenURI(tid TokenID) (string, error) {
80 uri := s.tokenURIs.Get(tid.String())
81 if uri == nil {
82 return "", ErrInvalidTokenId
83 }
84
85 return uri.(string), nil
86}
87
88func (s *BasicNFT) SetTokenURI(caller address, tid TokenID, tURI TokenURI) (bool, error) {
89 if !s.exists(tid) {
90 return false, ErrInvalidTokenId
91 }
92
93 owner, err := s.OwnerOf(tid)
94 if err != nil {
95 return false, err
96 }
97 if caller != owner {
98 return false, ErrCallerIsNotOwner
99 }
100 s.tokenURIs.Set(tid.String(), tURI.String())
101
102 chain.Emit(
103 TokenURIUpdateEvent,
104 "token", s.ID(),
105 "tokenId", tid.String(),
106 )
107
108 return true, nil
109}
110
111func (s *BasicNFT) IsApprovedForAll(owner, operator address) bool {
112 key := owner.String() + ":" + operator.String()
113 approved := s.operatorApprovals.Get(key)
114 if approved == nil {
115 return false
116 }
117
118 return approved.(bool)
119}
120
121func (s *BasicNFT) Approve(caller, to address, tid TokenID) error {
122 if err := isValidAddress(to); err != nil {
123 return err
124 }
125
126 owner, err := s.OwnerOf(tid)
127 if err != nil {
128 return err
129 }
130 if owner == to {
131 return ErrApprovalToCurrentOwner
132 }
133
134 if caller != owner && !s.IsApprovedForAll(owner, caller) {
135 return ErrCallerIsNotOwnerOrApproved
136 }
137
138 tidStr := tid.String()
139 s.tokenApprovals.Set(tidStr, to)
140
141 chain.Emit(
142 ApprovalEvent,
143 "token", s.ID(),
144 "owner", owner.String(),
145 "to", to.String(),
146 "tokenId", tidStr,
147 )
148
149 return nil
150}
151
152func (s *BasicNFT) GetApproved(tid TokenID) (address, error) {
153 addr := s.tokenApprovals.Get(tid.String())
154 if addr == nil {
155 return zeroAddress, ErrTokenIdNotHasApproved
156 }
157
158 return addr.(address), nil
159}
160
161func (s *BasicNFT) SetApprovalForAll(caller, operator address, approved bool) error {
162 if err := isValidAddress(operator); err != nil {
163 return ErrInvalidAddress
164 }
165 return s.setApprovalForAll(caller, operator, approved)
166}
167
168func (s *BasicNFT) SafeTransferFrom(caller, from, to address, tid TokenID) error {
169 if !s.isApprovedOrOwner(caller, tid) {
170 return ErrCallerIsNotOwnerOrApproved
171 }
172
173 err := s.transfer(from, to, tid)
174 if err != nil {
175 return err
176 }
177
178 if !s.checkOnGRC721Received(from, to, tid) {
179 return ErrTransferToNonGRC721Receiver
180 }
181
182 return nil
183}
184
185func (s *BasicNFT) TransferFrom(caller, from, to address, tid TokenID) error {
186 if !s.isApprovedOrOwner(caller, tid) {
187 return ErrCallerIsNotOwnerOrApproved
188 }
189
190 err := s.transfer(from, to, tid)
191 if err != nil {
192 return err
193 }
194
195 return nil
196}
197
198func (s *BasicNFT) Mint(to address, tid TokenID) error {
199 return s.mint(to, tid)
200}
201
202func (s *BasicNFT) SafeMint(to address, tid TokenID) error {
203 err := s.mint(to, tid)
204 if err != nil {
205 return err
206 }
207
208 if !s.checkOnGRC721Received(zeroAddress, to, tid) {
209 return ErrTransferToNonGRC721Receiver
210 }
211
212 return nil
213}
214
215func (s *BasicNFT) Burn(tid TokenID) error {
216 owner, err := s.OwnerOf(tid)
217 if err != nil {
218 return err
219 }
220
221 s.beforeTokenTransfer(owner, zeroAddress, tid, 1)
222
223 tidStr := tid.String()
224 s.tokenApprovals.Remove(tidStr)
225 balance, err := s.BalanceOf(owner)
226 if err != nil {
227 return err
228 }
229 balance = overflow.Sub64p(balance, 1)
230
231 ownerStr := owner.String()
232 s.balances.Set(ownerStr, balance)
233 s.owners.Remove(tidStr)
234
235 chain.Emit(
236 BurnEvent,
237 "token", s.ID(),
238 "from", ownerStr,
239 "tokenId", tidStr,
240 )
241
242 s.afterTokenTransfer(owner, zeroAddress, tid, 1)
243
244 return nil
245}
246
247func (s *BasicNFT) setApprovalForAll(owner, operator address, approved bool) error {
248 if owner == operator {
249 return ErrApprovalToCurrentOwner
250 }
251
252 key := owner.String() + ":" + operator.String()
253 s.operatorApprovals.Set(key, approved)
254
255 chain.Emit(
256 ApprovalForAllEvent,
257 "token", s.ID(),
258 "owner", owner.String(),
259 "to", operator.String(),
260 "approved", strconv.FormatBool(approved),
261 )
262
263 return nil
264}
265
266func (s *BasicNFT) transfer(from, to address, tid TokenID) error {
267 if err := isValidAddress(from); err != nil {
268 return ErrInvalidAddress
269 }
270 if err := isValidAddress(to); err != nil {
271 return ErrInvalidAddress
272 }
273
274 if from == to {
275 return ErrCannotTransferToSelf
276 }
277
278 owner, err := s.OwnerOf(tid)
279 if err != nil {
280 return err
281 }
282 if owner != from {
283 return ErrTransferFromIncorrectOwner
284 }
285
286 s.beforeTokenTransfer(from, to, tid, 1)
287
288 owner, err = s.OwnerOf(tid)
289 if err != nil {
290 return err
291 }
292 if owner != from {
293 return ErrTransferFromIncorrectOwner
294 }
295
296 tidStr := tid.String()
297 s.tokenApprovals.Remove(tidStr)
298 fromBalance, err := s.BalanceOf(from)
299 if err != nil {
300 return err
301 }
302 toBalance, err := s.BalanceOf(to)
303 if err != nil {
304 return err
305 }
306 fromBalance = overflow.Sub64p(fromBalance, 1)
307 toBalance = overflow.Add64p(toBalance, 1)
308
309 fromStr := from.String()
310 toStr := to.String()
311
312 s.balances.Set(fromStr, fromBalance)
313 s.balances.Set(toStr, toBalance)
314 s.owners.Set(tidStr, to)
315
316 chain.Emit(
317 TransferEvent,
318 "token", s.ID(),
319 "from", fromStr,
320 "to", toStr,
321 "tokenId", tidStr,
322 )
323
324 s.afterTokenTransfer(from, to, tid, 1)
325
326 return nil
327}
328
329func (s *BasicNFT) mint(to address, tid TokenID) error {
330 if err := isValidAddress(to); err != nil {
331 return err
332 }
333
334 if s.exists(tid) {
335 return ErrTokenIdAlreadyExists
336 }
337
338 s.beforeTokenTransfer(zeroAddress, to, tid, 1)
339
340 if s.exists(tid) {
341 return ErrTokenIdAlreadyExists
342 }
343
344 toBalance, err := s.BalanceOf(to)
345 if err != nil {
346 return err
347 }
348 toBalance = overflow.Add64p(toBalance, 1)
349 toStr := to.String()
350 tidStr := tid.String()
351 s.balances.Set(toStr, toBalance)
352 s.owners.Set(tidStr, to)
353
354 chain.Emit(
355 MintEvent,
356 "token", s.ID(),
357 "to", toStr,
358 "tokenId", tidStr,
359 )
360
361 s.afterTokenTransfer(zeroAddress, to, tid, 1)
362
363 return nil
364}
365
366func (s *BasicNFT) isApprovedOrOwner(addr address, tid TokenID) bool {
367 owner := s.owners.Get(tid.String())
368 if owner == nil {
369 return false
370 }
371
372 ownerAddr := owner.(address)
373 if addr == ownerAddr || s.IsApprovedForAll(ownerAddr, addr) {
374 return true
375 }
376
377 approved, err := s.GetApproved(tid)
378 if err != nil {
379 return false
380 }
381
382 return approved == addr
383}
384
385func (s *BasicNFT) exists(tid TokenID) bool {
386 return s.owners.Has(tid.String())
387}
388
389func (s *BasicNFT) beforeTokenTransfer(from, to address, firstTokenId TokenID, batchSize int64) {
390}
391
392func (s *BasicNFT) afterTokenTransfer(from, to address, firstTokenId TokenID, batchSize int64) {
393}
394
395func (s *BasicNFT) checkOnGRC721Received(from, to address, tid TokenID) bool {
396 return true
397}
398
399func (s *BasicNFT) RenderHome() (str string) {
400 str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol)
401 str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount())
402 str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size())
403
404 return
405}
406
407func (n *BasicNFT) Getter() NFTGetter {
408 return func() IGRC721Reader {
409 return n
410 }
411}