github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/docs/how-to-guides/creating-grc721.md (about)

     1  ---
     2  id: creating-grc721
     3  ---
     4  
     5  # How to create a GRC721 Token (NFT)
     6  
     7  ## Overview
     8  
     9  This guide shows you how to write a simple **GRC721** Smart Contract, or rather
    10  a [Realm](../concepts/realms.md), in [Gno](../concepts/gno-language.md). 
    11  For actually deploying the Realm, please see the [deployment](deploy.md) guide.
    12  
    13  Our **GRC721** Realm will have the following functionality:
    14  
    15  - Minting a configurable amount of tokens.
    16  - Keeping track of total token supply.
    17  - Fetching the balance of an account.
    18  
    19  ## 1. Importing token package
    20  
    21  For this realm, we'll want to import the `grc721` package as this will include 
    22  the main functionality of our NFT realm. The package can be found the
    23  `gno.land/p/demo/grc/grc721` path.
    24  
    25  [embedmd]:# (../assets/how-to-guides/creating-grc721/mynonfungibletoken-1.gno go)
    26  ```go
    27  package mynft
    28  
    29  import (
    30  	"std"
    31  
    32  	"gno.land/p/demo/grc/grc721"
    33  )
    34  
    35  var (
    36    mytoken *grc20.AdminToken
    37    admin   std.Address
    38  )
    39  
    40  // init is called once at time of deployment
    41  func init() {
    42    // Set deployer of Realm to admin
    43    admin = std.PrevRealm().Addr()
    44  
    45    // Set token name, symbol and number of decimals
    46    mynft = grc721.NewBasicNFT("My NFT", "MNFT")
    47  
    48    // Mint 1 million tokens to admin
    49    mytoken.Mint(admin, 1000000*10000)
    50  }
    51  ```
    52  
    53  In this code preview, we have:
    54  
    55  - Defined and set the value of `mynonfungibletoken` (type `*grc721.basicNFT`) to equal the result of creating a new
    56    token and configuring its name and symbol.
    57  - Defined and set the value of local variable `admin` to point to a specific gno.land address of type `std.Address`.
    58  - Minted 5 `mynonfungibletoken (MNFT)` and set the administrator as the owner of these tokens
    59  
    60  ## 2. Adding token functionality
    61  
    62  The following section will be about introducing Public functions to expose functionality imported from
    63  the [grc721 package](https://github.com/gnolang/gno/tree/master/examples/gno.land/p/demo/grc/grc721).
    64  
    65  [embedmd]:# (../assets/how-to-guides/creating-grc721/mynonfungibletoken-2.gno go)
    66  ```go
    67  func mintNNFT(owner std.Address, n uint64) {
    68  	count := my.TokenCount()
    69  	for i := count; i < count+n; i++ {
    70  		tid := grc721.TokenID(ufmt.Sprintf("%d", i))
    71  		mynonfungibletoken.Mint(owner, tid)
    72  	}
    73  }
    74  
    75  // Getters
    76  
    77  func BalanceOf(user users.AddressOrName) uint64 {
    78  	balance, err := mynonfungibletoken.BalanceOf(user.Resolve())
    79  	if err != nil {
    80  		panic(err)
    81  	}
    82  
    83  	return balance
    84  }
    85  
    86  func OwnerOf(tid grc721.TokenID) std.Address {
    87  	owner, err := mynonfungibletoken.OwnerOf(tid)
    88  	if err != nil {
    89  		panic(err)
    90  	}
    91  
    92  	return owner
    93  }
    94  
    95  func IsApprovedForAll(owner, user users.AddressOrName) bool {
    96  	return mynonfungibletoken.IsApprovedForAll(owner.Resolve(), user.Resolve())
    97  }
    98  
    99  func GetApproved(tid grc721.TokenID) std.Address {
   100  	addr, err := mynonfungibletoken.GetApproved(tid)
   101  	if err != nil {
   102  		panic(err)
   103  	}
   104  
   105  	return addr
   106  }
   107  
   108  // Setters
   109  
   110  func Approve(user users.AddressOrName, tid grc721.TokenID) {
   111  	err := mynonfungibletoken.Approve(user.Resolve(), tid)
   112  	if err != nil {
   113  		panic(err)
   114  	}
   115  }
   116  
   117  func SetApprovalForAll(user users.AddressOrName, approved bool) {
   118  	err := mynonfungibletoken.SetApprovalForAll(user.Resolve(), approved)
   119  	if err != nil {
   120  		panic(err)
   121  	}
   122  }
   123  
   124  func TransferFrom(from, to users.AddressOrName, tid grc721.TokenID) {
   125  	err := mynonfungibletoken.TransferFrom(from.Resolve(), to.Resolve(), tid)
   126  	if err != nil {
   127  		panic(err)
   128  	}
   129  }
   130  
   131  // Admin
   132  
   133  func Mint(to users.AddressOrName, tid grc721.TokenID) {
   134  	caller := std.PrevRealm().Addr()
   135  	assertIsAdmin(caller)
   136  	err := mynonfungibletoken.Mint(to.Resolve(), tid)
   137  	if err != nil {
   138  		panic(err)
   139  	}
   140  }
   141  
   142  func Burn(tid grc721.TokenID) {
   143  	caller := std.PrevRealm().Addr()
   144  	assertIsAdmin(caller)
   145  	err := mynonfungibletoken.Burn(tid)
   146  	if err != nil {
   147  		panic(err)
   148  	}
   149  }
   150  
   151  // Render
   152  
   153  func Render(path string) string {
   154  	switch {
   155  	case path == "":
   156  		return mynonfungibletoken.RenderHome()
   157  	default:
   158  		return "404\n"
   159  	}
   160  }
   161  
   162  // Util
   163  
   164  func assertIsAdmin(address std.Address) {
   165  	if address != admin {
   166  		panic("restricted access")
   167  	}
   168  }
   169  ```
   170  
   171  Detailing what is happening in the above code:
   172  
   173  - Calling the **local** `mintNNFT` method would mint a configurable number of tokens to the provided owner's account.
   174  - Calling the `BalanceOf` method would return the total balance of an account.
   175  - Calling the `OwnerOf` method would return the owner of the token based on the ID that is passed into the method.
   176  - Calling the `IsApprovedByAll` method will return true if an operator is approved for all operations by the owner;
   177    otherwise, returns false.
   178  - Calling the `GetApproved` method will return the address approved to operate on the token.
   179  - Calling the `Approve` method would approve the input address for a particular token.
   180  - Calling the `SetApprovalForAll` method would approve an operating account to operate on all tokens.
   181  - Calling the `TransferFrom` method would transfer a configurable amount of token from an account that granted approval
   182    to another account, either owned or unowned.
   183  - Calling the `Mint` method would create a configurable number of tokens by the administrator.
   184  - Calling the `Burn` method would destroy a configurable number of tokens by the administrator.
   185  - Calling the `Render` method on success would invoke
   186    a [`RenderHome`](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic_nft.gno#L353)
   187    method on the `grc721` instance we instantiated at the top of the file; this method returns a formatted string that
   188    includes the token: symbol, supply and account balances (`balances avl.Tree`) which is a mapping denoted
   189    as: `OwnerAddress -> TokenCount`; otherwise returns false and renders a `404`; you can find more information about
   190    this `Render` method and how it's used [here](../concepts/realms.md).
   191  - Finally, we provide a local function to assert that the calling account is in fact the owner, otherwise panic. This is
   192    a very important function that serves to prevent abuse by non-administrators.
   193  
   194  ## Conclusion
   195  
   196  That's it 🎉
   197  
   198  You have successfully built a simple GRC721 Realm that is ready to be deployed on the Gno chain and called by users.
   199  In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside
   200  tools like a wallet application.