Flare for Go Devs
This guide is for developers who want to interact with Flare using Go. In this guide, using Go, you will learn how to:
- Query a contract on Flare using the Go API for Geth, a client that implements the full Ethereum JSON-RPC API.
- Compile a Solidity contract using the CLI interface of solc, the Solidity compiler.
- Deploy your compiled contract on Flare.
All examples in this guide are available at developer-hub/examples.
Getting started
Install Geth by following the instructions in the Geth documentation. Also install the Solidity compiler by following the instructions in the Solidity documentation. The main commands are provided here:
- MacOS via Homebrew
- Ubuntu via PPAs
brew tap ethereum/ethereum
brew install ethereum solidity
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt update
sudo apt install ethereum solc
Add the ethclient
and keystore
packages to your Go project:
go get github.com/ethereum/go-ethereum/ethclient
go get github.com/ethereum/go-ethereum/accounts/keystore
The folder structure of your Go project should look like:
developer-hub-go/
├── coston2/
│ └── *.go
├── flare/
│ └── *.go
├── main.go
├── go.mod
└── go.sum
Usage
You need to connect to testnet or mainnet via an RPC, any RPC listed on the Network Configuration page will work. For this guide, you can use the Public RPC.
- Flare Testnet Coston2
- Flare Mainnet
package coston2
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
)
func ChainId() *big.Int {
cl, err := ethclient.Dial("https://coston2-api.flare.network/ext/C/rpc")
if err != nil {
panic(err)
}
chainid, err := cl.ChainID(context.Background())
if err != nil {
panic(err)
}
fmt.Println("Chain ID is", chainid)
// Chain ID is 114
return chainid
}
package flare
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
)
func ChainId() *big.Int {
cl, err := ethclient.Dial("https://flare-api.flare.network/ext/C/rpc")
if err != nil {
panic(err)
}
chainid, err := cl.ChainID(context.Background())
if err != nil {
panic(err)
}
fmt.Println("Chain ID is", chainid)
// Chain ID is 14
return chainid
}
Querying a contract
To query a contract, two pieces of information are required:
- Contract address
- Contract ABI (Application Binary Interface)
For this example, you can use the FlareContractRegistry
contract which has the same address 0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019
across both testnet and mainnet.
Fetch ABI
To fetch a contract's ABI, copy the FlareContractRegistry ABI, and paste it into a file named FlareContractRegistry.abi
, located in the root of your project, i.e. same level as go.mod
.
To generate the ABI bindings, which will be saved to FlareContractRegistry.go
:
- Flare Testnet Coston2
- Flare Mainnet
abigen --abi FlareContractRegistry.abi --pkg coston2 --type FlareContractRegistry --out coston2/FlareContractRegistry.go
abigen --abi FlareContractRegistry.abi --pkg flare --type FlareContractRegistry --out flare/FlareContractRegistry.go
Make query
You can now query the FlareContractRegistry
contract to get the addresses of other Flare contracts.
For example, querying it for the address of the WNat
contract:
- Flare Testnet Coston2
- Flare Mainnet
package coston2
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func MakeQuery() []common.Address {
client, err := ethclient.Dial("https://coston2-api.flare.network/ext/C/rpc")
if err != nil {
panic(err)
}
registryAddr := common.HexToAddress("0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019")
registry, err := NewFlareContractRegistry(registryAddr, client)
if err != nil {
panic(err)
}
callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
addr, err := registry.GetContractAddressesByName(callOpts, []string{"WNat"})
if err != nil {
panic(err)
}
fmt.Println("WNat contract address is", addr)
// WNat contract address is [0xC67DCE33D7A8efA5FfEB961899C73fe01bCe9273]
return addr
}
package flare
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
)
func MakeQuery() []common.Address {
client, err := ethclient.Dial("https://flare-api.flare.network/ext/C/rpc")
if err != nil {
panic(err)
}
registryAddr := common.HexToAddress("0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019")
registry, err := NewFlareContractRegistry(registryAddr, client)
if err != nil {
panic(err)
}
callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
addr, err := registry.GetContractAddressesByName(callOpts, []string{"WNat"})
if err != nil {
panic(err)
}
fmt.Println("WNat contract address is", addr)
// WNat contract address is [0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d]
return addr
}
Compiling with solc
For this example, you can use the FtsoV2FeedConsumer
contract to query the FTSOv2 feeds. Copy the FtsoV2FeedConsumer
sample contract code given below, and save the .sol
file in the same folder as your go.mod
.
FtsoV2FeedConsumer
sample contract
Note that the contract interface dependencies have been inlined to avoid any import issues.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
interface IFlareContractRegistry {
function getContractAddressByName(
string calldata _name
) external view returns (address);
}
/**
* THIS IS A TEST INTERFACE.
* Functions are payable in the production interface.
*/
interface TestFtsoV2Interface {
function getFeedsById(
bytes21[] calldata _feedIds
)
external
view
returns (
uint256[] memory _values,
int8[] memory _decimals,
uint64 _timestamp
);
}
/**
* THIS IS AN EXAMPLE CONTRACT.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract FtsoV2FeedConsumer {
IFlareContractRegistry internal contractRegistry;
TestFtsoV2Interface internal ftsoV2;
// Feed IDs, see https://dev.flare.network/ftso/feeds for full list
bytes21[] public feedIds = [
bytes21(0x01464c522f55534400000000000000000000000000), // FLR/USD
bytes21(0x014254432f55534400000000000000000000000000), // BTC/USD
bytes21(0x014554482f55534400000000000000000000000000) // ETH/USD
];
/**
* Constructor initializes the FTSOv2 contract.
* The contract registry is used to fetch the FtsoV2 contract address.
*/
constructor() {
contractRegistry = IFlareContractRegistry(
0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019
);
ftsoV2 = TestFtsoV2Interface(
contractRegistry.getContractAddressByName("FtsoV2")
);
}
/**
* Get the current value of the feeds.
*/
function getFtsoV2CurrentFeedValues()
external
view
returns (
uint256[] memory _feedValues,
int8[] memory _decimals,
uint64 _timestamp
)
{
/* Your custom feed consumption logic. In this example the values are just returned. */
return ftsoV2.getFeedsById(feedIds);
}
}
To compile the contract, use the Solidity compiler:
solc --evm-version london --abi --bin FtsoV2FeedConsumer.sol -o build
The compiled contract will be saved in the build/
folder.
Create account
Before deploying a contract, you need an account. You can create an account using the following code:
package main
import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/keystore"
)
func CreateAccount() {
ks := keystore.NewKeyStore(".", keystore.StandardScryptN, keystore.StandardScryptP)
account, err := ks.NewAccount("Creation password")
if err != nil {
panic(err)
}
fmt.Println("Account: ", account.Address.Hex())
}
This will generate a new account and save the keystore file in the current directory. The account address will be printed to the console.
- Never share your private keys.
- Never put your private keys in source code.
- Never commit private keys to a Git repository.
- For testnet, you can get free testnet C2FLR on the Coston2 Faucet.
- For mainnet you will need to fund the account with FLR. You can buy FLR with regular currency in places like centralized exchanges, crypto on-ramps, or swap other tokens for FLR on decentralized exchanges.
Deploying with geth
To deploy the contract, you need to first generate the contract bindings:
- Flare Testnet Coston2
- Flare Mainnet
abigen --bin=build/FtsoV2FeedConsumer.bin --abi=build/FtsoV2FeedConsumer.abi --pkg coston2 --type FtsoV2FeedConsumer --out coston2/FtsoV2FeedConsumer.go
abigen --bin=build/FtsoV2FeedConsumer.bin --abi=build/FtsoV2FeedConsumer.abi --pkg flare --type FtsoV2FeedConsumer --out flare/FtsoV2FeedConsumer.go
This will generate the FtsoV2FeedConsumer.go
file, with the function DeployFtsoV2FeedConsumer
.
Copy the contents of the generated keystore file into the key
constant in the following code:
- Flare Testnet Coston2
- Flare Mainnet
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
package coston2
import (
"context"
"fmt"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
)
// Paste the contents of the generated keystore file here
// DO NOT USE THIS CODE IN PRODUCTION.
// NEVER COMMIT PRIVATE KEYS TO A GIT REPOSITORY.
const key = ``
func DeployContract() {
conn, err := ethclient.Dial("https://coston2-api.flare.network/ext/C/rpc")
if err != nil {
panic(err)
}
ctx := context.Background()
chainId, err := conn.ChainID(ctx)
if err != nil {
panic(err)
}
auth, err := bind.NewTransactorWithChainID(strings.NewReader(key), "Creation password", chainId)
if err != nil {
panic(err)
}
address, tx, ftsoV2FeedConsumer, err := DeployFtsoV2FeedConsumer(auth, conn)
if err != nil {
panic(err)
}
fmt.Printf("Contract pending deploy: 0x%x\n", address)
fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
time.Sleep(2000 * time.Millisecond)
feeds, err := ftsoV2FeedConsumer.GetFtsoV2CurrentFeedValues(&bind.CallOpts{Context: ctx, Pending: true})
if err != nil {
panic(err)
}
fmt.Println("Feeds values and decimals:", feeds)
}
// THIS IS EXAMPLE CODE. DO NOT USE THIS CODE IN PRODUCTION.
package flare
import (
"context"
"fmt"
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/ethclient"
)
// Paste the contents of the generated keystore file here
// DO NOT USE THIS CODE IN PRODUCTION.
// NEVER COMMIT PRIVATE KEYS TO A GIT REPOSITORY.
const key = ``
func DeployContract() {
conn, err := ethclient.Dial("https://flare-api.flare.network/ext/C/rpc")
if err != nil {
panic(err)
}
ctx := context.Background()
chainId, err := conn.ChainID(ctx)
if err != nil {
panic(err)
}
auth, err := bind.NewTransactorWithChainID(strings.NewReader(key), "Creation password", chainId)
if err != nil {
panic(err)
}
address, tx, ftsoV2FeedConsumer, err := DeployFtsoV2FeedConsumer(auth, conn)
if err != nil {
panic(err)
}
fmt.Printf("Contract pending deploy: 0x%x\n", address)
fmt.Printf("Transaction waiting to be mined: 0x%x\n\n", tx.Hash())
time.Sleep(2000 * time.Millisecond)
feeds, err := ftsoV2FeedConsumer.GetFtsoV2CurrentFeedValues(&bind.CallOpts{Context: ctx, Pending: true})
if err != nil {
panic(err)
}
fmt.Println("Feeds values and decimals:", feeds)
}
Congratulations! You have now successfully deployed a contract on Flare using Go.
Learn how to interact with Flare's enshrined oracle FTSOv2 using Go.