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  }