Skip to main content

How to Create a GRC20 Token

Overview

This guide shows you how to write a simple GRC20 a Realm, in Gno. For actually deploying the Realm, please see the deployment 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.

1. Importing token package

For this realm, we import the grc20 package, as this includes the main functionality of our token realm. The package can be found the gno.land/p/demo/grc/grc20 path.

package mytoken

import (
"std"
"strings"

"gno.land/p/demo/grc/grc20"
"gno.land/p/demo/ufmt"
)

var (
mytoken *grc20.AdminToken
admin std.Address
)

// init is called once at time of deployment
func init() {
// Set deployer of Realm to admin
admin = std.PrevRealm().Addr()

// Set token name, symbol and number of decimals
mytoken = grc20.NewAdminToken("My Token", "TKN", 4)

// Mint 1 million tokens to admin
mytoken.Mint(admin, 1000000*10000)
}

The code snippet above does the following:

  • Defines a new token variable, mytoken, and assigns it to a pointer to the GRC20 token type, grc20.AdminToken,
  • Defines and sets the value of admin with a type of std.Address to contain the address of the deployer
  • Initializes mytoken as a new GRC20 token, and set its name, symbol, and decimal values,
  • Mint 1 million units of My Token and assign them to the admin's address.

2. Adding token functionality

In order to call exported functions from the grc20 package, we also need to expose them in the realm. Let's go through all functions in the GRC20 package, one by one:

// TotalSupply returns the total supply of mytoken
func TotalSupply() uint64 {
return mytoken.TotalSupply()
}

Calling the TotalSupply method would return the total number of tokens minted.

// Decimals returns the number of decimals of mytoken
func Decimals() uint {
return mytoken.GetDecimals()
}

Calling the Decimals method would return number of decimals of the token.

// BalanceOf returns the balance mytoken for `account`
func BalanceOf(account std.Address) uint64 {
balance, err := mytoken.BalanceOf(account)
if err != nil {
panic(err)
}

return balance
}

Calling the BalanceOf method would return the total balance of an account.

// Allowance returns the allowance of spender on owner's balance
func Allowance(owner, spender std.Address) uint64 {
allowance, err := mytoken.Allowance(owner, spender)
if err != nil {
panic(err)
}

return allowance
}

Calling the Allowance method will return the amount spender is allowed to spend from owner's balance.

// Transfer transfers amount from caller to recipient
func Transfer(recipient std.Address, amount uint64) {
caller := std.PrevRealm().Addr()
if err := mytoken.Transfer(caller, recipient, amount); err != nil {
panic(err)
}
}

Calling the Transfer method transfers amount of token from the calling account to the recipient account.

func Approve(spender std.Address, amount uint64) {
caller := std.PrevRealm().Addr()
if err := mytoken.Approve(caller, spender, amount); err != nil {
panic(err)
}
}

Calling the Approve method approves spender to spend amount from the caller's balance of tokens.

// TransferFrom transfers `amount` of tokens from `from` to `to` 
func TransferFrom(sender, recipient std.Address, amount uint64) {
caller := std.PrevRealm().Addr()

if amount <= 0 {
panic("transfer amount must be greater than zero")
}

if err := mytoken.TransferFrom(caller, from, to, amount); err != nil {
panic(err)
}
}

Calling the TransferFrom method moves amount of tokens from sender to recipient using the allowance mechanism. amount is then deducted from the caller’s allowance.

// Mint mints amount of tokens to address. Callable only by admin of token
func Mint(address std.Address, amount uint64) {
assertIsAdmin(std.PrevRealm().Addr())

if amount <= 0 {
panic("mint amount must be greater than zero")
}

if err := mytoken.Mint(address, amount); err != nil {
panic(err)
}
}

Calling the Mint method creates amount of tokens and assigns them to address, increasing the total supply.

// Burn burns amount of tokens from address. Callable only by admin of token
func Burn(address std.Address, amount uint64) {
assertIsAdmin(std.PrevRealm().Addr())

if amount <= 0 {
panic("burn amount must be greater than zero")
}

if err := mytoken.Burn(address, amount); err != nil {
panic(err)
}
}

Calling the Mint method burns amount of tokens from the balance of address, decreasing the total supply.

// assertIsAdmin asserts the address is the admin of token
func assertIsAdmin(address std.Address) {
if address != admin {
panic("restricted access")
}
}

Calling the assertIsAdmin method checks if address is equal to the package-level admin variable.

// Render renders the state of the realm
func Render(path string) string {
parts := strings.Split(path, "/")
c := len(parts)

switch {
case path == "":
// Default GRC20 render
return mytoken.RenderHome()
case c == 2 && parts[0] == "balance":
// Render balance of specific address
owner := std.Address(parts[1])
balance, _ := mytoken.BalanceOf(owner)
return ufmt.Sprintf("%d\n", balance)
default:
return "404\n"
}
}

Calling the Render method returns a general render of the GRC20 realm, or if given a specific address, the user's balance as a formatted string.

You can view the full code on this Playground link.

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.