github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/examples/gno.land/p/demo/grc/grc721/basic_nft.gno (about) 1 package grc721 2 3 import ( 4 "std" 5 6 "gno.land/p/demo/avl" 7 "gno.land/p/demo/ufmt" 8 ) 9 10 type 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 21 func 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 34 func (s *basicNFT) Name() string { return s.name } 35 func (s *basicNFT) Symbol() string { return s.symbol } 36 func (s *basicNFT) TokenCount() uint64 { return uint64(s.owners.Size()) } 37 38 // BalanceOf returns balance of input address 39 func (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 53 func (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 63 func (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 72 func (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 93 func (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 104 func (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 130 func (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 140 func (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. 152 func (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`. 171 func (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`. 186 func (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 192 func (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 205 func (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() 233 func (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() 248 func (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() 303 func (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 335 func (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 354 func (s *basicNFT) exists(tid TokenID) bool { 355 _, found := s.owners.Get(string(tid)) 356 return found 357 } 358 359 func (s *basicNFT) beforeTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { 360 // TODO: Implementation 361 } 362 363 func (s *basicNFT) afterTokenTransfer(from, to std.Address, firstTokenId TokenID, batchSize uint64) { 364 // TODO: Implementation 365 } 366 367 func (s *basicNFT) checkOnGRC721Received(from, to std.Address, tid TokenID) bool { 368 // TODO: Implementation 369 return true 370 } 371 372 func (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 }