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 at 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 (
banker *grc20.Banker
mytoken grc20.Token
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
banker = grc20.NewBanker("My Token", "TKN", 4)
// Mint 1 million tokens to admin
banker.Mint(admin, 1_000_000*10_000) // 1M
// Get the GRC20 compatible safe object
mytoken = banker.Token()
}
The code snippet above does the following:
- Defines a new token variable,
banker
, and assigns it to a pointer to the GRC20 banker type,*grc20.Banker
, - Defines and sets the value of
admin
with a type ofstd.Address
to contain the address of the deployer - Initializes
mytoken
as a GRC20-compatible token, and sets 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 {
return mytoken.BalanceOf(account)
}
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 {
return mytoken.Allowance(owner, spender)
}
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) {
checkErr(mytoken.Transfer(recipient, amount))
}
Calling the Transfer
method transfers amount of token from the calling account
to the recipient account.
// Approve approves amount of caller's tokens to be spent by spender
func Approve(spender std.Address, amount uint64) {
checkErr(mytoken.Approve(spender, amount))
}
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(from, to std.Address, amount uint64) {
checkErr(mytoken.TransferFrom(from, to, amount))
}
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())
checkErr(banker.Mint(address, amount))
}
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())
checkErr(banker.Burn(address, amount))
}
Calling the Mint
method burns amount
of tokens from the balance of address
,
decreasing the total supply.
assertIsAdmin(std.PrevRealm().Addr())
checkErr(banker.Mint(address, amount))
}
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. If you want to deploy it, do so on the Portal Loop.
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.