LLM Notice: This documentation site supports content negotiation for AI agents. Request any page with Accept: text/markdown or Accept: text/plain header to receive Markdown instead of HTML. Alternatively, append ?format=md to any URL. All markdown files are available at /md/ prefix paths. For all content in one file, visit /llms-full.txt
Skip to main content

Flowkit Go Tutorial: Working with Flow Project State

Introduction

Flowkit is a Go package for interacting with the Flow blockchain in the context of flow.json configuration files. It provides APIs for managing Flow projects, including:

  • Loading and managing project configuration (flow.json)
  • Resolving import statements in Cadence contracts, scripts, and transactions
  • Deploying contracts to different networks (emulator, testnet, mainnet)
  • Managing accounts, networks, and deployments
  • Executing scripts and building transactions with proper import resolution

Flowkit is the core package used by the Flow CLI and can be integrated into any Go application that needs to interact with Flow projects.

Installation

Prerequisites

  • Go 1.25.0 or higher
  • A Flow project with a flow.json configuration file

Install the Package

Add Flowkit to your Go module:


_10
go get github.com/onflow/flowkit/v2

This will install Flowkit v2 and all its dependencies.

Import in Your Code


_10
import (
_10
"github.com/onflow/flowkit/v2"
_10
"github.com/onflow/flowkit/v2/config"
_10
"github.com/onflow/flowkit/v2/project"
_10
"github.com/spf13/afero"
_10
)

Loading Project State

The first step when working with Flowkit is loading your project's state from flow.json.

Basic Usage


_18
package main
_18
_18
import (
_18
"log"
_18
"github.com/onflow/flowkit/v2"
_18
"github.com/spf13/afero"
_18
)
_18
_18
func main() {
_18
// Load flow.json from the current directory
_18
state, err := flowkit.Load([]string{"flow.json"}, afero.NewOsFs())
_18
if err != nil {
_18
log.Fatalf("Failed to load project state: %v", err)
_18
}
_18
_18
// Now you can work with the project state
_18
log.Println("Project state loaded successfully!")
_18
}

Creating a New Project State

If you need to create a new project from scratch:


_10
// Initialize an empty state
_10
state, err := flowkit.Init(afero.NewOsFs())
_10
if err != nil {
_10
log.Fatalf("Failed to initialize state: %v", err)
_10
}

Accessing State Components

The State object provides access to all project configuration:


_14
// Get all contracts
_14
contracts := state.Contracts()
_14
_14
// Get all networks
_14
networks := state.Networks()
_14
_14
// Get all accounts
_14
accounts := state.Accounts()
_14
_14
// Get all deployments
_14
deployments := state.Deployments()
_14
_14
// Get the underlying config
_14
config := state.Config()

Working with Contracts

Contracts are Cadence smart contracts defined in your project.

Getting Contract Information


_14
// Get a contract by name
_14
contract, err := state.Contracts().ByName("MyContract")
_14
if err != nil {
_14
log.Fatalf("Contract not found: %v", err)
_14
}
_14
_14
log.Printf("Contract: %s\n", contract.Name)
_14
log.Printf("Location: %s\n", contract.Location)
_14
_14
// Get all contract names
_14
names := state.Contracts().Names()
_14
for _, name := range names {
_14
log.Printf("Available contract: %s\n", name)
_14
}

Getting Deployment Contracts for a Network

When deploying or executing code, you often need contracts with their target addresses:


_16
import "github.com/onflow/flowkit/v2/config"
_16
_16
// Get all contracts configured for deployment on testnet
_16
contracts, err := state.DeploymentContractsByNetwork(config.TestnetNetwork)
_16
if err != nil {
_16
log.Fatalf("Failed to get deployment contracts: %v", err)
_16
}
_16
_16
// Each contract includes deployment information
_16
for _, contract := range contracts {
_16
log.Printf("Contract: %s\n", contract.Name)
_16
log.Printf(" Location: %s\n", contract.Location())
_16
log.Printf(" Target Account: %s\n", contract.AccountAddress)
_16
log.Printf(" Account Name: %s\n", contract.AccountName)
_16
log.Printf(" Code Size: %d bytes\n", len(contract.Code()))
_16
}

Working with Networks

Flowkit supports multiple networks including emulator, testnet, and mainnet.

Available Networks


_10
import "github.com/onflow/flowkit/v2/config"
_10
_10
// Predefined networks
_10
emulator := config.EmulatorNetwork // Local emulator
_10
testnet := config.TestnetNetwork // Flow testnet
_10
mainnet := config.MainnetNetwork // Flow mainnet
_10
_10
log.Printf("Emulator: %s\n", emulator.Host)
_10
log.Printf("Testnet: %s\n", testnet.Host)
_10
log.Printf("Mainnet: %s\n", mainnet.Host)

Getting Networks from State


_11
// Get all networks defined in flow.json
_11
networks := state.Networks()
_11
_11
// Get a specific network by name
_11
testnet, err := networks.ByName("testnet")
_11
if err != nil {
_11
log.Fatalf("Network not found: %v", err)
_11
}
_11
_11
log.Printf("Network: %s\n", testnet.Name)
_11
log.Printf("Host: %s\n", testnet.Host)

Adding or Updating Networks


_12
// Add a custom network
_12
networks := state.Networks()
_12
networks.AddOrUpdate(config.Network{
_12
Name: "custom-network",
_12
Host: "localhost:3570",
_12
})
_12
_12
// Save the updated configuration
_12
err := state.SaveDefault()
_12
if err != nil {
_12
log.Fatalf("Failed to save state: %v", err)
_12
}

Getting Network-Specific Aliases

Network aliases map contract names/locations to their deployed addresses on specific networks:


_10
// Get aliases for testnet
_10
aliases := state.AliasesForNetwork(config.TestnetNetwork)
_10
_10
// aliases is a map[string]string of location/name -> address
_10
for location, address := range aliases {
_10
log.Printf("%s deployed at %s on testnet\n", location, address)
_10
}

Resolving Imports with ImportReplacer

The ImportReplacer resolves import statements in Cadence contracts, scripts, and transactions by replacing relative file paths and contract names with their deployed blockchain addresses.

Basic Usage

When you have a Cadence program with imports like import "Kibble", you need to resolve these to blockchain addresses:


_33
import "github.com/onflow/flowkit/v2/project"
_33
_33
// Get contracts for your target network
_33
contracts, err := state.DeploymentContractsByNetwork(config.TestnetNetwork)
_33
if err != nil {
_33
log.Fatal(err)
_33
}
_33
_33
// Create an import replacer with your project's contracts
_33
importReplacer := project.NewImportReplacer(contracts, nil)
_33
_33
// Parse your Cadence program
_33
code := []byte(`
_33
import "Kibble"
_33
import "FungibleToken"
_33
_33
transaction {
_33
prepare(signer: &Account) {
_33
// ...
_33
}
_33
}
_33
`)
_33
_33
program := project.NewProgram(code, []string{}, "")
_33
_33
// Replace imports with deployed addresses
_33
resolvedProgram, err := importReplacer.Replace(program)
_33
if err != nil {
_33
log.Fatalf("Failed to resolve imports: %v", err)
_33
}
_33
_33
// The resolved program now has addresses instead of file paths
_33
log.Printf("Resolved code:\n%s", string(resolvedProgram.Code()))

Integration with Project State

The most common pattern is to use network-specific aliases from your project state:


_30
// Load project state and get network-specific contracts and aliases
_30
state, err := flowkit.Load([]string{"flow.json"}, afero.NewOsFs())
_30
if err != nil {
_30
log.Fatal(err)
_30
}
_30
_30
// Choose your target network
_30
network := config.TestnetNetwork
_30
_30
// Get contracts for this network
_30
contracts, err := state.DeploymentContractsByNetwork(network)
_30
if err != nil {
_30
log.Fatal(err)
_30
}
_30
_30
// Use network-specific aliases for address mapping
_30
importReplacer := project.NewImportReplacer(
_30
contracts,
_30
state.AliasesForNetwork(network),
_30
)
_30
_30
// Parse and resolve your program
_30
program := project.NewProgram(scriptCode, []string{}, "script.cdc")
_30
resolvedProgram, err := importReplacer.Replace(program)
_30
if err != nil {
_30
log.Fatalf("Failed to resolve imports: %v", err)
_30
}
_30
_30
// Use the resolved program for execution
_30
log.Printf("Ready to execute:\n%s", string(resolvedProgram.Code()))

Working with Accounts

Accounts represent Flow blockchain accounts used for signing transactions and deploying contracts.

Getting Account Information


_20
accounts := state.Accounts()
_20
_20
// Get account by name
_20
account, err := accounts.ByName("emulator-account")
_20
if err != nil {
_20
log.Fatalf("Account not found: %v", err)
_20
}
_20
_20
log.Printf("Account: %s\n", account.Name)
_20
log.Printf("Address: %s\n", account.Address)
_20
_20
// Get all account names
_20
names := accounts.Names()
_20
for _, name := range names {
_20
log.Printf("Available account: %s\n", name)
_20
}
_20
_20
// Get account by address
_20
addr := flow.HexToAddress("0xf8d6e0586b0a20c7")
_20
account, err = accounts.ByAddress(addr)

Getting the Emulator Service Account


_10
// Get the emulator's default service account
_10
serviceAccount, err := state.EmulatorServiceAccount()
_10
if err != nil {
_10
log.Fatalf("Failed to get service account: %v", err)
_10
}
_10
_10
log.Printf("Service account address: %s\n", serviceAccount.Address)

Working with Deployments

Deployments define which contracts should be deployed to which accounts on specific networks.

Getting Deployment Information


_20
deployments := state.Deployments()
_20
_20
// Get all deployments for a network
_20
testnetDeployments := deployments.ByNetwork("testnet")
_20
_20
for _, deployment := range testnetDeployments {
_20
log.Printf("Account: %s\n", deployment.Account)
_20
log.Printf("Network: %s\n", deployment.Network)
_20
log.Printf("Contracts:\n")
_20
_20
for _, contract := range deployment.Contracts {
_20
log.Printf(" - %s\n", contract.Name)
_20
}
_20
}
_20
_20
// Get deployment for specific account and network
_20
deployment := deployments.ByAccountAndNetwork("my-account", "testnet")
_20
if deployment != nil {
_20
log.Printf("Found deployment: %d contracts\n", len(deployment.Contracts))
_20
}

Complete Example

Here's a complete example that ties everything together:


_69
package main
_69
_69
import (
_69
"context"
_69
"log"
_69
_69
"github.com/onflow/flowkit/v2"
_69
"github.com/onflow/flowkit/v2/config"
_69
"github.com/onflow/flowkit/v2/project"
_69
"github.com/spf13/afero"
_69
)
_69
_69
func main() {
_69
// 1. Load project state
_69
state, err := flowkit.Load([]string{"flow.json"}, afero.NewOsFs())
_69
if err != nil {
_69
log.Fatalf("Failed to load state: %v", err)
_69
}
_69
_69
// 2. Choose target network
_69
network := config.TestnetNetwork
_69
log.Printf("Using network: %s\n", network.Name)
_69
_69
// 3. Get deployment contracts for the network
_69
contracts, err := state.DeploymentContractsByNetwork(network)
_69
if err != nil {
_69
log.Fatalf("Failed to get contracts: %v", err)
_69
}
_69
_69
log.Printf("Found %d contracts for deployment\n", len(contracts))
_69
for _, contract := range contracts {
_69
log.Printf(" - %s -> %s\n", contract.Name, contract.AccountAddress)
_69
}
_69
_69
// 4. Get network aliases
_69
aliases := state.AliasesForNetwork(network)
_69
log.Printf("Network has %d aliases\n", len(aliases))
_69
_69
// 5. Create import replacer
_69
importReplacer := project.NewImportReplacer(contracts, aliases)
_69
_69
// 6. Resolve imports in a script
_69
scriptCode := []byte(`
_69
import "Kibble"
_69
import "FungibleToken"
_69
_69
access(all) fun main(): String {
_69
return "Hello, Flow!"
_69
}
_69
`)
_69
_69
program := project.NewProgram(scriptCode, []string{}, "script.cdc")
_69
resolvedProgram, err := importReplacer.Replace(program)
_69
if err != nil {
_69
log.Fatalf("Failed to resolve imports: %v", err)
_69
}
_69
_69
log.Printf("Resolved script:\n%s\n", string(resolvedProgram.Code()))
_69
_69
// 7. Get account for signing
_69
account, err := state.Accounts().ByName("testnet-account")
_69
if err != nil {
_69
log.Fatalf("Failed to get account: %v", err)
_69
}
_69
_69
log.Printf("Using account: %s (%s)\n", account.Name, account.Address)
_69
_69
log.Println("Setup complete! Ready to interact with Flow.")
_69
}

Conclusion

Flowkit provides a powerful and flexible API for managing Flow projects in Go. By understanding how to work with project state, contracts, networks, and import resolution, you can build robust applications that interact with the Flow blockchain.

The import replacer is particularly critical for ensuring your Cadence code works correctly across different networks by automatically resolving contract imports to their deployed addresses.