grc721_basic.gno
7.77 Kb · 320 lines
1package gingernft
2import (
3 "chain"
4 "chain/runtime"
5 "math/overflow"
6 "strconv"
7 "gno.land/p/nt/avl/v0"
8 "gno.land/p/nt/ufmt/v0"
9)
10type BasicNFT struct {
11 name string
12 symbol string
13 owners avl.Tree
14 balances avl.Tree
15 tokenApprovals avl.Tree
16 tokenURIs avl.Tree
17 operatorApprovals avl.Tree
18}
19func NewBasicNFT(name, symbol string) *BasicNFT {
20 return &BasicNFT{
21 name: name,
22 symbol: symbol,
23 owners: avl.Tree{},
24 balances: avl.Tree{},
25 tokenApprovals: avl.Tree{},
26 tokenURIs: avl.Tree{},
27 operatorApprovals: avl.Tree{},
28 }
29}
30func (s *BasicNFT) Name() string { return s.name }
31func (s *BasicNFT) Symbol() string { return s.symbol }
32func (s *BasicNFT) TokenCount() int64 { return int64(s.owners.Size()) }
33func (s *BasicNFT) BalanceOf(addr address) (int64, error) {
34 if err := isValidAddress(addr); err != nil {
35 return 0, err
36 }
37 balance, found := s.balances.Get(addr.String())
38 if !found {
39 return 0, nil
40 }
41 return balance.(int64), nil
42}
43func (s *BasicNFT) OwnerOf(tid TokenID) (address, error) {
44 owner, found := s.owners.Get(string(tid))
45 if !found {
46 return "", ErrInvalidTokenId
47 }
48 return owner.(address), nil
49}
50func (s *BasicNFT) TokenURI(tid TokenID) (string, error) {
51 uri, found := s.tokenURIs.Get(tid.String())
52 if !found {
53 return "", ErrInvalidTokenId
54 }
55 return uri.(string), nil
56}
57func (s *BasicNFT) SetTokenURI(tid TokenID, tURI TokenURI) (bool, error) {
58 if !s.exists(tid) {
59 return false, ErrInvalidTokenId
60 }
61 owner, err := s.OwnerOf(tid)
62 if err != nil {
63 return false, err
64 }
65 caller := runtime.PreviousRealm().Address()
66 if caller != owner {
67 return false, ErrCallerIsNotOwner
68 }
69 s.tokenURIs.Set(tid.String(), tURI.String())
70 chain.Emit(
71 TokenURIUpdateEvent,
72 "token", s.symbol,
73 "tokenId", tid.String(),
74 )
75 return true, nil
76}
77func (s *BasicNFT) IsApprovedForAll(owner, operator address) bool {
78 key := owner.String() + ":" + operator.String()
79 approved, found := s.operatorApprovals.Get(key)
80 if !found {
81 return false
82 }
83 return approved.(bool)
84}
85func (s *BasicNFT) Approve(to address, tid TokenID) error {
86 if err := isValidAddress(to); err != nil {
87 return err
88 }
89 owner, err := s.OwnerOf(tid)
90 if err != nil {
91 return err
92 }
93 if owner == to {
94 return ErrApprovalToCurrentOwner
95 }
96 caller := runtime.PreviousRealm().Address()
97 if caller != owner && !s.IsApprovedForAll(owner, caller) {
98 return ErrCallerIsNotOwnerOrApproved
99 }
100 tidStr := tid.String()
101 s.tokenApprovals.Set(tidStr, to)
102 chain.Emit(
103 ApprovalEvent,
104 "token", s.symbol,
105 "owner", owner.String(),
106 "to", to.String(),
107 "tokenId", tidStr,
108 )
109 return nil
110}
111func (s *BasicNFT) GetApproved(tid TokenID) (address, error) {
112 addr, found := s.tokenApprovals.Get(tid.String())
113 if !found {
114 return zeroAddress, ErrTokenIdNotHasApproved
115 }
116 return addr.(address), nil
117}
118func (s *BasicNFT) SetApprovalForAll(operator address, approved bool) error {
119 if err := isValidAddress(operator); err != nil {
120 return ErrInvalidAddress
121 }
122 caller := runtime.PreviousRealm().Address()
123 return s.setApprovalForAll(caller, operator, approved)
124}
125func (s *BasicNFT) SafeTransferFrom(from, to address, tid TokenID) error {
126 caller := runtime.PreviousRealm().Address()
127 if !s.isApprovedOrOwner(caller, tid) {
128 return ErrCallerIsNotOwnerOrApproved
129 }
130 err := s.transfer(from, to, tid)
131 if err != nil {
132 return err
133 }
134 if !s.checkOnGRC721Received(from, to, tid) {
135 return ErrTransferToNonGRC721Receiver
136 }
137 return nil
138}
139func (s *BasicNFT) TransferFrom(from, to address, tid TokenID) error {
140 caller := runtime.PreviousRealm().Address()
141 if !s.isApprovedOrOwner(caller, tid) {
142 return ErrCallerIsNotOwnerOrApproved
143 }
144 err := s.transfer(from, to, tid)
145 if err != nil {
146 return err
147 }
148 return nil
149}
150func (s *BasicNFT) Mint(to address, tid TokenID) error {
151 return s.mint(to, tid)
152}
153func (s *BasicNFT) SafeMint(to address, tid TokenID) error {
154 err := s.mint(to, tid)
155 if err != nil {
156 return err
157 }
158 if !s.checkOnGRC721Received(zeroAddress, to, tid) {
159 return ErrTransferToNonGRC721Receiver
160 }
161 return nil
162}
163func (s *BasicNFT) Burn(tid TokenID) error {
164 owner, err := s.OwnerOf(tid)
165 if err != nil {
166 return err
167 }
168 s.beforeTokenTransfer(owner, zeroAddress, tid, 1)
169 tidStr := tid.String()
170 s.tokenApprovals.Remove(tidStr)
171 balance, err := s.BalanceOf(owner)
172 if err != nil {
173 return err
174 }
175 balance = overflow.Sub64p(balance, 1)
176 ownerStr := owner.String()
177 s.balances.Set(ownerStr, balance)
178 s.owners.Remove(tidStr)
179 chain.Emit(
180 BurnEvent,
181 "token", s.symbol,
182 "from", ownerStr,
183 "tokenId", tidStr,
184 )
185 s.afterTokenTransfer(owner, zeroAddress, tid, 1)
186 return nil
187}
188func (s *BasicNFT) setApprovalForAll(owner, operator address, approved bool) error {
189 if owner == operator {
190 return ErrApprovalToCurrentOwner
191 }
192 key := owner.String() + ":" + operator.String()
193 s.operatorApprovals.Set(key, approved)
194 chain.Emit(
195 ApprovalForAllEvent,
196 "token", s.symbol,
197 "owner", owner.String(),
198 "to", operator.String(),
199 "approved", strconv.FormatBool(approved),
200 )
201 return nil
202}
203func (s *BasicNFT) transfer(from, to address, tid TokenID) error {
204 if err := isValidAddress(from); err != nil {
205 return ErrInvalidAddress
206 }
207 if err := isValidAddress(to); err != nil {
208 return ErrInvalidAddress
209 }
210 if from == to {
211 return ErrCannotTransferToSelf
212 }
213 owner, err := s.OwnerOf(tid)
214 if err != nil {
215 return err
216 }
217 if owner != from {
218 return ErrTransferFromIncorrectOwner
219 }
220 s.beforeTokenTransfer(from, to, tid, 1)
221 owner, err = s.OwnerOf(tid)
222 if err != nil {
223 return err
224 }
225 if owner != from {
226 return ErrTransferFromIncorrectOwner
227 }
228 tidStr := tid.String()
229 s.tokenApprovals.Remove(tidStr)
230 fromBalance, err := s.BalanceOf(from)
231 if err != nil {
232 return err
233 }
234 toBalance, err := s.BalanceOf(to)
235 if err != nil {
236 return err
237 }
238 fromBalance = overflow.Sub64p(fromBalance, 1)
239 toBalance = overflow.Add64p(toBalance, 1)
240 fromStr := from.String()
241 toStr := to.String()
242 s.balances.Set(fromStr, fromBalance)
243 s.balances.Set(toStr, toBalance)
244 s.owners.Set(tidStr, to)
245 chain.Emit(
246 TransferEvent,
247 "token", s.symbol,
248 "from", fromStr,
249 "to", toStr,
250 "tokenId", tidStr,
251 )
252 s.afterTokenTransfer(from, to, tid, 1)
253 return nil
254}
255func (s *BasicNFT) mint(to address, tid TokenID) error {
256 if err := isValidAddress(to); err != nil {
257 return err
258 }
259 if s.exists(tid) {
260 return ErrTokenIdAlreadyExists
261 }
262 s.beforeTokenTransfer(zeroAddress, to, tid, 1)
263 if s.exists(tid) {
264 return ErrTokenIdAlreadyExists
265 }
266 toBalance, err := s.BalanceOf(to)
267 if err != nil {
268 return err
269 }
270 toBalance = overflow.Add64p(toBalance, 1)
271 toStr := to.String()
272 tidStr := tid.String()
273 s.balances.Set(toStr, toBalance)
274 s.owners.Set(tidStr, to)
275 chain.Emit(
276 MintEvent,
277 "token", s.symbol,
278 "to", toStr,
279 "tokenId", tidStr,
280 )
281 s.afterTokenTransfer(zeroAddress, to, tid, 1)
282 return nil
283}
284func (s *BasicNFT) isApprovedOrOwner(addr address, tid TokenID) bool {
285 owner, found := s.owners.Get(tid.String())
286 if !found {
287 return false
288 }
289 ownerAddr := owner.(address)
290 if addr == ownerAddr || s.IsApprovedForAll(ownerAddr, addr) {
291 return true
292 }
293 approved, err := s.GetApproved(tid)
294 if err != nil {
295 return false
296 }
297 return approved == addr
298}
299func (s *BasicNFT) exists(tid TokenID) bool {
300 _, found := s.owners.Get(tid.String())
301 return found
302}
303func (s *BasicNFT) beforeTokenTransfer(from, to address, firstTokenId TokenID, batchSize int64) {
304}
305func (s *BasicNFT) afterTokenTransfer(from, to address, firstTokenId TokenID, batchSize int64) {
306}
307func (s *BasicNFT) checkOnGRC721Received(from, to address, tid TokenID) bool {
308 return true
309}
310func (s *BasicNFT) RenderHome() (str string) {
311 str += ufmt.Sprintf("# %s ($%s)\n\n", s.name, s.symbol)
312 str += ufmt.Sprintf("* **Total supply**: %d\n", s.TokenCount())
313 str += ufmt.Sprintf("* **Known accounts**: %d\n", s.balances.Size())
314 return
315}
316func (n *BasicNFT) Getter() NFTGetter {
317 return func() IGRC721 {
318 return n
319 }
320}