Transfer funds with Golang

This document gives instructions and examples on how to use our pkg/client package to broadcast transactions and query the Coreum blockchain using Go programming language.

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/v3/pkg/client"
    coreumconfig "github.com/CoreumFoundation/coreum/v3/pkg/config"
	"github.com/CoreumFoundation/coreum/v3/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: run node keyring and crypto keyring.

To get funded account, go to our faucet website 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(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12}))` with `grpc.WithInsecure()`
grpcClient, err := grpc.Dial(nodeAddress, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{MinVersion: tls.VersionTLS12})))
if err != nil {
    panic(err)
}

encodingConfig := coreumconfig.NewEncodingConfig(modules)

clientCtx := client.NewContext(client.DefaultContextConfig(), modules).
    WithChainID(string(chainID)).
    WithGRPCClient(grpcClient).
    WithKeyring(keyring.NewInMemory(encodingConfig.Codec)).
	WithBroadcastMode(flags.BroadcastSync)

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: run node keyring and crypto keyring.

senderInfo, err := clientCtx.Keyring().NewAccount(
    "key-name",
    senderMnemonic,
    "",
    sdk.GetConfig().GetFullBIP44Path(),
    hd.Secp256k1,
)
if err != nil {
    panic(err)
}

Getting the account address

Account address is the part of senderInfo structure. It might be accessed using this code:

senderAddress, err := senderInfo.GetAddress()
if err != nil {
	panic(err)
}

fmt.Println(senderAddress.String())

Output:

testcore1zuelfk5fz02v9x7gnsy2t7ps83m8vljx5wqdfq

Validating the address

Address may be validated using this code:

_, err = sdk.AccAddressFromBech32(senderInfo.GetAddress().String())
if err != nil {
    panic(err)
}

Broadcasting transaction

Now we are ready to broadcast transaction. As an example we send 9000000utestcore tokens from your wallet to recipientAddress:

msg := &banktypes.MsgSend{
    FromAddress: senderInfo.GetAddress().String(),
    ToAddress:   recipientAddress,
    Amount:      sdk.NewCoins(sdk.NewInt64Coin(denom, 9_000_000)),
}

ctx := context.Background()
result, err := client.BroadcastTx(
    ctx,
    clientCtx.WithFromAddress(senderInfo.GetAddress()),
    txFactory,
    msg,
)
if err != nil {
    panic(err)
}
fmt.Printf("Tx hash: %s\n", result.TxHash)

After executing this code, you will see output like this:

Tx hash: 8C694A92A2208DB8CE733D54C22A3C7F945D54867B9078D08686DC7DBF0F44DC

You may copy transaction hash and paste it in the search box of our block explorer to confirm the transaction execution and check its properties.

Querying the balance

Now you may query the balance of your account:

bankClient := banktypes.NewQueryClient(clientCtx)
balances, err := bankClient.AllBalances(ctx, &banktypes.QueryAllBalancesRequest{
    Address: recipientAddress,
})
if err != nil {
    panic(err)
}
fmt.Printf("Balances: %s\n", balances.Balances)

After executing it should produce output like:

Balances: 9000000utestcore