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