From da1fe7bc37b6ef19280aca15fecb3cd0fafd3c3f Mon Sep 17 00:00:00 2001 From: jon roethke Date: Sun, 8 Oct 2023 11:05:53 -0700 Subject: [PATCH] feat: add grc20 + grc721 guides (#16) * add content for grc20 + init grc721 guide * fix header titles * add token functionality + description --- .../docs/how-to-guides/creating-grc20.md | 161 +++++++++++++++- .../docs/how-to-guides/creating-grc721.md | 175 +++++++++++++++++- 2 files changed, 334 insertions(+), 2 deletions(-) diff --git a/docs/gno-docs/docs/how-to-guides/creating-grc20.md b/docs/gno-docs/docs/how-to-guides/creating-grc20.md index 85db43dfee4..cd1674d09c5 100644 --- a/docs/gno-docs/docs/how-to-guides/creating-grc20.md +++ b/docs/gno-docs/docs/how-to-guides/creating-grc20.md @@ -4,4 +4,163 @@ id: creating-grc20 # How to create a GRC20 Token -TODO \ No newline at end of file +## Overview + +This guide shows you how to write a simple _GRC20_ Smart Contract, or rather a [Realm](../explanation/realms.md), in [Gnolang (Gno)](../explanation/gno-language.md). For actually deploying the Realm, please see the [deployment](deploy.md) guide. + +Our _GRC20_ Realm will have the following functionality: + +- Minting a configurable amount of token. +- Keeping track of total token supply. +- Fetching the balance of an account. + +## Prerequisites + +We will proceed using the typical directory structure for a Realm found within the [simple-contract guide](simple-contract.md). It is also worthwhile to consult the [GRC20 interface](../../../../examples/gno.land/p/demo/grc/grc20/igrc20.gno) which we will be importing and utilizing within this guide. + +## 1. Importing token package +For this realm, we'll want to import the `grc20` package as this will include the main functionality of our token factory realm. + +mytoken.gno +```go +package mytoken + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" +) + +var ( + mytoken *grc20.AdminToken + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // set admin account +) + +// init is a constructor function that runs only once (at time of deployment) +func init() { + // provision the token's name, symbol and number of decimals + mytoken = grc20.NewAdminToken("Mytoken", "MTKN", 4) + + // set the total supply + mytoken.Mint(admin, 1000000*10000) // @administrator (supply = 1 million) +} +``` + +In this code preview, we have: +- Defined a new local variable `mytoken` and assigned that the type of pointer to `grc20.AdminToken`. +- Defined and set the value of local variable `admin` to point to a specific gno.land address of type `std.Address`. +- Set the value of `mytoken` (type `*AdminToken`) to equal the result of creating a new token and configuring its name, symbol + decimal representation. +- Minted 1 million `Mytoken` and set the administrator as the owner of these tokens. + +## 2. Adding token functionality + +The following section will be about introducing Public functions to expose functionality imported from the [grc20 package](../../../../examples/gno.land/p/demo/grc/grc20). + +```go +func TotalSupply() uint64 { + return mytoken.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := mytoken.BalanceOf(owner.Resolve()) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := mytoken.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err) + } + return allowance +} + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.Transfer(caller, to.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.Approve(caller, spender.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := mytoken.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mytoken.Mint(address.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mytoken.Burn(address.Resolve(), amount) + if err != nil { + panic(err) + } +} + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return mytoken.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := mytoken.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} +``` + +Detailing what is happening in the above code: +- Calling the `TotalSupply` method would return the total number of tokens minted. +- Calling the `BalanceOf` method would return the total balance of an account. +- Calling the `Allowance` method would set an account as an allowed spender to serve on behalf of the owner. +- Calling the `transfer` method would transfer a configurable amount of token from the calling account to another account, either owned or unowned. +- Calling the `Approve` method would approve a calling account to spend a configurable amount of token on behalf of the token owner. +- Calling the `TransferFrom` method would transfer a configurable amount of token from an account that granted approval to another account, either owned or unowned. +- Calling the `Mint` method would create a configurable number of tokens by the administrator. +- Calling the `Burn` method would destroy a configurable number of tokens by the administrator. +- Calling the `Render` method would return a user's `balance` as a formatted string. Learn more about the `Render` + method and how it's used [here](../explanation/realms.md). +- Finally, we provide a local function to assert that the calling account is in fact the owner, otherwise panic. This is a very important function that serves to prevent abuse by non-administrators. + +## Conclusion + +That's it 🎉 + +You have successfully built a simple GRC20 Realm that is ready to be deployed on the Gno chain and called by users. +In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside tools like a wallet application. diff --git a/docs/gno-docs/docs/how-to-guides/creating-grc721.md b/docs/gno-docs/docs/how-to-guides/creating-grc721.md index df577eb36f1..b8b9540cfbf 100644 --- a/docs/gno-docs/docs/how-to-guides/creating-grc721.md +++ b/docs/gno-docs/docs/how-to-guides/creating-grc721.md @@ -4,4 +4,177 @@ id: creating-grc721 # How to create a GRC721 Token (NFT) -TODO \ No newline at end of file +## Overview + +This guide shows you how to write a simple _GRC721_ Smart Contract, or rather a [Realm](../explanation/realms.md), in [Gnolang (Gno)](../explanation/gno-language.md). For actually deploying the Realm, please see the [deployment](deploy.md) guide. + +Our _GRC721_ Realm will have the following functionality: + +- Minting a configurable amount of token. +- Keeping track of total token supply. +- Fetching the balance of an account. + +## Prerequisites + +We will proceed using the typical directory structure for a Realm found within the [simple-contract guide](simple-contract.md). It is also worthwhile to consult the [GRC721 interface](../../../../examples/gno.land/p/demo/grc/grc721/igrc721.gno) which we will be borrowing from within this guide. + +## 1. Importing token package +For this realm, we'll want to import the `grc20` package as this will include the main functionality of our token factory realm. + +mynonfungibletoken.gno +```go +package mynonfungibletoken + +import ( + "std" + + "gno.land/p/demo/grc/grc721" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" +) + +var ( + admin std.Address = "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj" // set admin account + // provision the token's name and symbol + mynonfungibletoken = grc721.NewBasicNFT("mynonfungibletoken", "MNFT") +) + +func init() { + mintNNFT(admin, 10) // @administrator (supply = 10) +} +``` + +In this code preview, we have: +- Defined and set the value of `mynonfungibletoken` (type `*grc721.basicNFT`) to equal the result of creating a new token and configuring its name and symbol. +- Defined and set the value of local variable `admin` to point to a specific gno.land address of type `std.Address`. +- Minted 5 `mynonfungibletoken (MNFT)` and set the administrator as the owner of these tokens + +## 2. Adding token functionality + +The following section will be about introducing Public functions to expose functionality imported from the [grc721 package](../../../../examples/gno.land/p/demo/grc/grc721). + +```go +func mintNNFT(owner std.Address, n uint64) { + count := my.TokenCount() + for i := count; i < count+n; i++ { + tid := grc721.TokenID(ufmt.Sprintf("%d", i)) + mynonfungibletoken.Mint(owner, tid) + } +} + +// Getters + +func BalanceOf(user users.AddressOrName) uint64 { + balance, err := mynonfungibletoken.BalanceOf(user.Resolve()) + if err != nil { + panic(err) + } + + return balance +} + +func OwnerOf(tid grc721.TokenID) std.Address { + owner, err := mynonfungibletoken.OwnerOf(tid) + if err != nil { + panic(err) + } + + return owner +} + +func IsApprovedForAll(owner, user users.AddressOrName) bool { + return mynonfungibletoken.IsApprovedForAll(owner.Resolve(), user.Resolve()) +} + +func GetApproved(tid grc721.TokenID) std.Address { + addr, err := mynonfungibletoken.GetApproved(tid) + if err != nil { + panic(err) + } + + return addr +} + +// Setters + +func Approve(user users.AddressOrName, tid grc721.TokenID) { + err := mynonfungibletoken.Approve(user.Resolve(), tid) + if err != nil { + panic(err) + } +} + +func SetApprovalForAll(user users.AddressOrName, approved bool) { + err := mynonfungibletoken.SetApprovalForAll(user.Resolve(), approved) + if err != nil { + panic(err) + } +} + +func TransferFrom(from, to users.AddressOrName, tid grc721.TokenID) { + err := mynonfungibletoken.TransferFrom(from.Resolve(), to.Resolve(), tid) + if err != nil { + panic(err) + } +} + +// Admin + +func Mint(to users.AddressOrName, tid grc721.TokenID) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mynonfungibletoken.Mint(to.Resolve(), tid) + if err != nil { + panic(err) + } +} + +func Burn(tid grc721.TokenID) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + err := mynonfungibletoken.Burn(tid) + if err != nil { + panic(err) + } +} + +// Render + +func Render(path string) string { + switch { + case path == "": + return mynonfungibletoken.RenderHome() + default: + return "404\n" + } +} + +// Util + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} +``` + +Detailing what is happening in the above code: +- Calling the **local** `mintNNFT` method would mint a configurable number of tokens to the provided owner's account. +- Calling the `BalanceOf` method would return the total balance of an account. +- Calling the `OwnerOf` method would return the owner of the token based on the ID that is passed into the method. +- Calling the `IsApprovedByAll` method will return true if an operator is approved for all operations by the owner; otherwise, returns false. +- Calling the `GetApproved` method will return the address approved to operate on the token. +- Calling the `Approve` method would approve the input address for a particular token. +- Calling the `SetApprovalForAll` method would approve an operating account to operate on all tokens. +- Calling the `TransferFrom` method would transfer a configurable amount of token from an account that granted approval to another account, either owned or unowned. +- Calling the `Mint` method would create a configurable number of tokens by the administrator. +- Calling the `Burn` method would destroy a configurable number of tokens by the administrator. +- Calling the `Render` method on success would invoke a [`RenderHome`](https://github.com/gnolang/gno/blob/master/examples/gno.land/p/demo/grc/grc721/basic_nft.gno#L353) method on the `grc721` instance we instantiated at the top of the file; this method returns a formatted string that includes the token: symbol, supply and account balances (`balances avl.Tree`) which is a mapping denoted as: `OwnerAddress -> TokenCount`; otherwise returns false and renders a `404`; you can find more information about this `Render` method and how it's used [here](../explanation/realms.md). +- Finally, we provide a local function to assert that the calling account is in fact the owner, otherwise panic. This is a very important function that serves to prevent abuse by non-administrators. + +## Conclusion + +That's it 🎉 + +You have successfully built a simple GRC721 Realm that is ready to be deployed on the Gno chain and called by users. +In the upcoming guides, we will see how we can develop more complex Realm logic and have them interact with outside tools like a wallet application.