Create Fungible Token with Golang
This document gives instructions and examples on how to use our pkg/client
package to create and manage fungible token.
Complete code
Complete code with go.mod
file you can find here
P.S. If you have issues with go mod tidy
command, just copy go.mod
file from the example above.
Go code skeleton
Imports and main function
Create standard main.go
file containing this skeleton importing pkg/client
:
package main
import (
"github.com/CoreumFoundation/coreum/v2/pkg/client"
"github.com/CoreumFoundation/coreum/v2/pkg/config/constant"
)
const (
senderMnemonic = "" // put mnemonic here
chainID = constant.ChainIDTest
addressPrefix = constant.AddressPrefixTest
denom = constant.DenomTest
recipientAddress = "testcore1534s8rz2e36lwycr6gkm9vpfe5yf67wkuca7zs"
nodeAddress = "full-node.testnet-1.coreum.dev:9090"
)
func main() {
}
Preparing test account
Before you may broadcast transactions, you need to have access to a funded account. Normally you would create a private key stored securely in local keystore. Here, for simplicity, we will use private key generated by our faucet. Never ever use mnemonic directly in code and never ever use key generated by faucet in production. It might cause complete funds loss! Please reference keyring documentation to learn on using keyring: https://docs.cosmos.network/v0.45/run-node/keyring.html and https://pkg.go.dev/github.com/cosmos/cosmos-sdk/crypto/keyring.
To get funded account, go to our faucet website: https://docs.coreum.dev/tools-ecosystem/faucet and click on "Generate Funded Wallet" button in "Testnet" section.
In response, you get your wallet address on our testnet chain and mnemonic used to generate the private key. Assign mnemonic to the constant senderMnemonic
in the code snippet above.
Setting Cosmos SDK configuration
First we need to configure Cosmos SDK:
config := sdk.GetConfig()
config.SetBech32PrefixForAccount(addressPrefix, addressPrefix+"pub")
config.SetCoinType(constant.CoinType)
config.Seal()
Preparing client context and tx factory
Before we are able to broadcast transaction, we must create and configure client context and tx factory:
modules := module.NewBasicManager(
auth.AppModuleBasic{},
)
// If you don't use TLS then replace `grpc.WithTransportCredentials()` with `grpc.WithInsecure()`
grpcClient, err := grpc.Dial(nodeAddress, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))
if err != nil {
panic(err)
}
clientCtx := client.NewContext(client.DefaultContextConfig(), modules).
WithChainID(string(chainID)).
WithGRPCClient(grpcClient).
WithKeyring(keyring.NewInMemory()).
WithBroadcastMode(flags.BroadcastBlock)
txFactory := client.Factory{}.
WithKeybase(clientCtx.Keyring()).
WithChainID(clientCtx.ChainID()).
WithTxConfig(clientCtx.TxConfig()).
WithSimulateAndExecute(true)
Generate private key
To sign a transaction, private key generated from mnemonic stored in senderMnemonic
is required. We store that key in the temporary keystore. In production you should use any keyring other than memory
or test
. Good choice might be os
or file
. For more details, refer keyring documentation: https://docs.cosmos.network/v0.45/run-node/keyring.html and https://pkg.go.dev/github.com/cosmos/cosmos-sdk/crypto/keyring.
senderInfo, err := clientCtx.Keyring().NewAccount(
"key-name",
senderMnemonic,
"",
sdk.GetConfig().GetFullBIP44Path(),
hd.Secp256k1,
)
if err != nil {
panic(err)
}
Creating fungible token
Here is the example of creating new token uabc
with initial supply of 100 000 000:
const subunit = "uabc"
msgIssue := &assetfttypes.MsgIssue{
Issuer: senderInfo.GetAddress().String(),
Symbol: "ABC",
Subunit: subunit,
Precision: 6,
InitialAmount: sdk.NewInt(100_000_000),
Description: "ABC coin",
Features: []assetfttypes.Feature{assetfttypes.Feature_freezing},
}
ctx := context.Background()
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderInfo.GetAddress()),
txFactory,
msgIssue,
)
if err != nil {
panic(err)
}
Querying the initial supply
After creating the token, initial supply is available on the issuer's account:
denom := subunit + "-" + senderInfo.GetAddress().String()
bankClient := banktypes.NewQueryClient(clientCtx)
resp, err := bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{
Address: senderInfo.GetAddress().String(),
Denom: denom,
})
if err != nil {
panic(err)
}
fmt.Printf("Issuer's balance: %s\n", resp.Balance)
Sending tokens
Now issuer may send those tokens to someone:
recipientInfo, _, err := clientCtx.Keyring().NewMnemonic(
"recipient",
keyring.English,
sdk.GetConfig().GetFullBIP44Path(),
"",
hd.Secp256k1,
)
if err != nil {
panic(err)
}
msgSend := &banktypes.MsgSend{
FromAddress: senderInfo.GetAddress().String(),
ToAddress: recipientInfo.GetAddress().String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin(denom, 1_000_000)),
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderInfo.GetAddress()),
txFactory,
msgSend,
)
if err != nil {
panic(err)
}
resp, err = bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{
Address: recipientInfo.GetAddress().String(),
Denom: denom,
})
if err != nil {
panic(err)
}
fmt.Printf("Recipient's balance: %s\n", resp.Balance)
Freezing
Because issuer enabled freezing
feature during token issuance, he/she might freeze the portion of the recipient's balance:
msgFreeze := &assetfttypes.MsgFreeze{
Sender: senderInfo.GetAddress().String(),
Account: recipientInfo.GetAddress().String(),
Coin: sdk.NewInt64Coin(denom, 500_000),
}
_, err = client.BroadcastTx(
ctx,
clientCtx.WithFromAddress(senderInfo.GetAddress()),
txFactory,
msgFreeze,
)
if err != nil {
panic(err)
}
After doing this, recipient is not allowed to transfer 500_000uabc from its account.
All the other features may be used in a similar fashion. More info is available in the documentation