1package grc721
2
3import (
4 "std"
5
6 "gno.land/p/demo/avl"
7 "gno.land/p/demo/ufmt"
8)
9
10type basicNFT struct {
11 name string
12 symbol string
13 owners avl.Tree // tokenId -> OwnerAddress
14 balances avl.Tree // OwnerAddress -> TokenCount
15 tokenApprovals avl.Tree // TokenId -> ApprovedAddress
16 tokenURIs avl.Tree // TokenId -> URIs
17 operatorApprovals avl.Tree // "OwnerAddress:OperatorAddress" -> bool
18}
19
20// Returns new basic NFT
21func NewBasicNFT(name string, symbol string) *basicNFT {
22 return &basicNFT{
23 name: name,
24 symbol: symbol,
25
26 owners: avl.Tree{},
27 balances: avl.Tree{},
28 tokenApprovals: avl.Tree{},
29 tokenURIs: avl.Tree{},
30 operatorApprovals: avl.Tree{},
31 }
32}
33
34func (s *basicNFT) Name() string { return s.name }
35func (s *basicNFT) Symbol() string { return s.symbol }
36func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) }
37
38// BalanceOf returns balance of input address
39func (s *basicNFT) BalanceOf(addr std.Address) (uint64, error) {
40 if err := isValidAddress(addr); err != nil {
41 return 0, err
42 }
43
44 balance, found := s.balances.Get(addr.String())
45 if !found {
46 return 0, nil
47 }
48
49 return balance.(uint64), nil
50}
51
52// OwnerOf returns owner of input token id
53func (s *basicNFT) OwnerOf(tid TokenID) (std.Address, error) {
54 owner, found := s.owners.Get(string(tid))
55 if !found {
56 return "", ErrInvalidTokenId
57 }
58
59 return owner.(std.Address), nil
60}
61
62// TokenURI returns the URI of input token id
63func (s *basicNFT) TokenURI(tid TokenID) (string, error) {
64 uri, found := s.tokenURIs.Get(string(tid))
65 if !found {
66 return "", ErrInvalidTokenId
67 }
68
69 return uri.(string), nil
70}
71
72func (s *basicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {
73 // check for invalid TokenID
74 if !s.exists(tid) {
75 return false, ErrInvalidTokenId
76 }
77
78 // check for the right owner
79 owner, err := s.OwnerOf(tid)
80 if err != nil {
81 return false, err
82 }
83 caller := std.PrevRealm().Addr()
84 if caller != owner {
85 return false, ErrCallerIsNotOwner
86 }
87 s.tokenURIs.Set(string(tid), string(tURI))
88 return true, nil
89}
90
91// IsApprovedForAll returns true if operator is approved for all by the owner.
92// Otherwise, returns false
93func (s *basicNFT) IsApprovedForAll(owner, operator std.Address) bool {
94 key := owner.String() + ":" + operator.String()
95 _, found := s.operatorApprovals.Get(key)
96 if !found {
97 return false
98 }
99
100 return true
101}
102
103// Approve approves the input address for particular token
104func (s *basicNFT) Approve(to std.Address, tid TokenID) error {
105 if err := isValidAddress(to); err != nil {
106 return err
107 }
108
109 owner, err := s.OwnerOf(tid)
110 if err != nil {
111 return err
112 }
113 if owner == to {
114 return ErrApprovalToCurrentOwner
115 }
116
117 caller := std.PrevRealm().Addr()
118 if caller != owner && !s.IsApprovedForAll(owner, caller) {
119 return ErrCallerIsNotOwnerOrApproved
120 }
121
122 s.tokenApprovals.Set(string(tid), to.String())
123 event := ApprovalEvent{owner, to, tid}
124 emit(&event)
125
126 return nil
127}
128
129// GetApproved return the approved address for token
130func (s *basicNFT) GetApproved(tid TokenID) (std.Address, error) {
131 addr, found := s.tokenApprovals.Get(string(tid))
132 if !found {
133 return zeroAddress, ErrTokenIdNotHasApproved
134 }
135
136 return std.Address(addr.(string)), nil
137}
138
139// SetApprovalForAll can approve the operator to operate on all tokens
140func (s *basicNFT) SetApprovalForAll(operator std.Address, approved bool) error {
141 if err := isValidAddress(operator); err != nil {
142 return ErrInvalidAddress
143 }
144
145 caller := std.PrevRealm().Addr()
146 return s.setApprovalForAll(caller, operator, approved)
147}
148
149// Safely transfers `tokenId` token from `from` to `to`, checking that
150// contract recipients are aware of the GRC721 protocol to prevent
151// tokens from being forever locked.
152func (s *basicNFT) SafeTransferFrom(from, to std.Address, tid TokenID) error {
153 caller := std.PrevRealm().Addr()
154 if !s.isApprovedOrOwner(caller, tid) {
155 return ErrCallerIsNotOwnerOrApproved
156 }
157
158 err := s.transfer(from, to, tid)
159 if err != nil {
160 return err
161 }
162
163 if !s.checkOnGRC721Received(from, to, tid) {
164 return ErrTransferToNonGRC721Receiver
165 }
166
167 return nil
168}
169
170// Transfers `tokenId` token from `from` to `to`.
171func (s *basicNFT) TransferFrom(from, to std.Address, tid TokenID) error {
172 caller := std.PrevRealm().Addr()
173 if !s.isApprovedOrOwner(caller, tid) {
174 return ErrCallerIsNotOwnerOrApproved
175 }
176
177 err := s.transfer(from, to, tid)
178 if err != nil {
179 return err
180 }
181
182 return nil
183}
184
185// Mints `tokenId` and transfers it to `to`.
186func (s *basicNFT) Mint(to std.Address, tid TokenID) error {
187 return s.mint(to, tid)
188}
189
190// Mints `tokenId` and transfers it to `to`. Also checks that
191// contract recipients are using GRC721 protocol
192func (s *basicNFT) SafeMint(to std.Address, tid TokenID) error {
193 err := s.mint(to, tid)
194 if err != nil {
195 return err
196 }
197
198 if !s.checkOnGRC721Received(zeroAddress, to, tid) {
199 return ErrTransferToNonGRC721Receiver
200 }
201
202 return nil
203}
204
205func (s *basicNFT) Burn(tid TokenID) error {
206 owner, err := s.OwnerOf(tid)
207 if err != nil {
208 return err
209 }
210
211 s.beforeTokenTransfer(owner, zeroAddress, tid, 1)
212
213 s.tokenApprovals.Remove(string(tid))
214 balance, err := s.BalanceOf(owner)
215 if err != nil {
216 return err
217 }
218 balance -= 1
219 s.balances.Set(owner.String(), balance)
220 s.owners.Remove(string(tid))
221
222 event := TransferEvent{owner, zeroAddress, tid}
223 emit(&event)
224
225 s.afterTokenTransfer(owner, zeroAddress, tid, 1)
226
227 return nil
228}
229
230/* Helper methods */
231
232// Helper for SetApprovalForAll()
233func (s *basicNFT) setApprovalForAll(owner, operator std.Address, approved bool) error {
234 if owner == operator {
235 return ErrApprovalToCurrentOwner
236 }
237
238 key := owner.String() + ":" + operator.String()
239 s.operatorApprovals.Set(key, approved)
240
241 event := ApprovalForAllEvent{owner, operator, approved}
242 emit(&event)
243
244 return nil
245}
246
247// Helper for TransferFrom() and SafeTransferFrom()
248func (s *basicNFT) transfer(from, to std.Address, tid TokenID) error {
249 if err := isValidAddress(from); err != nil {
250 return ErrInvalidAddress
251 }
252 if err := isValidAddress(to); err != nil {
253 return ErrInvalidAddress
254 }
255
256 if from == to {
257 return ErrCannotTransferToSelf
258 }
259
260 owner, err := s.OwnerOf(tid)
261 if err != nil {
262 return err
263 }
264 if owner != from {
265 return ErrTransferFromIncorrectOwner
266 }
267
268 s.beforeTokenTransfer(from, to, tid, 1)
269
270 // Check that tokenId was not transferred by `beforeTokenTransfer`
271 owner, err = s.OwnerOf(tid)
272 if err != nil {
273 return err
274 }
275 if owner != from {
276 return ErrTransferFromIncorrectOwner
277 }
278
279 s.tokenApprovals.Remove(string(tid))
280 fromBalance, err := s.BalanceOf(from)
281 if err != nil {
282 return err
283 }
284 toBalance, err := s.BalanceOf(to)
285 if err != nil {
286 return err
287 }
288 fromBalance -= 1
289 toBalance += 1
290 s.balances.Set(from.String(), fromBalance)
291 s.balances.Set(to.String(), toBalance)
292 s.owners.Set(string(tid), to)
293
294 event := TransferEvent{from, to, tid}
295 emit(&event)
296
297 s.afterTokenTransfer(from, to, tid, 1)
298
299 return nil
300}
301
302// Helper for Mint() and SafeMint()
303func (s *basicNFT) mint(to std.Address, tid TokenID) error {
304 if err := isValidAddress(to); err != nil {
305 return err
306 }
307
308 if s.exists(tid) {
309 return ErrTokenIdAlreadyExists
310 }
311
312 s.beforeTokenTransfer(zeroAddress, to, tid, 1)
313
314 // Check that tokenId was not minted by `beforeTokenTransfer`
315 if s.exists(tid) {
316 return ErrTokenIdAlreadyExists
317 }
318
319 toBalance, err := s.BalanceOf(to)
320 if err != nil {
321 return err
322 }
323 toBalance += 1
324 s.balances.Set(to.String(), toBalance)
325 s.owners.Set(string(tid), to)
326
327 event := TransferEvent{zeroAddress, to, tid}
328 emit(&event)
329
330 s.afterTokenTransfer(zeroAddress, to, tid, 1)
331
332 return nil
333}
334
335func (s *basicNFT) isApprovedOrOwner(addr std.Address, tid TokenID) bool {
336 owner, found := s.owners.Get(string(tid))
337 if !found {
338 return false
339 }
340
341 if addr == owner.(std.Address) || s.IsApprovedForAll(owner.(std.Address), addr) {
342 return true
343 }
344
345 _, err := s.GetApproved(tid)
346 if err != nil {
347 return false
348 }
349
350 return true
351}
352
353// Checks if token id already exists
354func (s *basicNFT) exists(tid TokenID) bool {
355 _, found := s.owners.Get(string(tid))
356 return found
357}
358
359func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {
360 // TODO: Implementation
361}
362
363func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) {
364 // TODO: Implementation
365}
366
367func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool {
368 // TODO: Implementation
369 return true
370}
371
372func (s *basicNFT) RenderHome() (str string) {
373 str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol)
374 str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount())
375 str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size())
376
377 return
378}
basic_nft.gno
8.51 Kb ยท 378 lines