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.