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.jsonconfiguration file 
Install the Package
Add Flowkit to your Go module:
_10go get github.com/onflow/flowkit/v2
This will install Flowkit v2 and all its dependencies.
Import in Your Code
_10import (_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
_18package main_18_18import (_18    "log"_18    "github.com/onflow/flowkit/v2"_18    "github.com/spf13/afero"_18)_18_18func 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_10state, err := flowkit.Init(afero.NewOsFs())_10if 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_14contracts := state.Contracts()_14_14// Get all networks_14networks := state.Networks()_14_14// Get all accounts_14accounts := state.Accounts()_14_14// Get all deployments_14deployments := state.Deployments()_14_14// Get the underlying config_14config := state.Config()
Working with Contracts
Contracts are Cadence smart contracts defined in your project.
Getting Contract Information
_14// Get a contract by name_14contract, err := state.Contracts().ByName("MyContract")_14if err != nil {_14    log.Fatalf("Contract not found: %v", err)_14}_14_14log.Printf("Contract: %s\n", contract.Name)_14log.Printf("Location: %s\n", contract.Location)_14_14// Get all contract names_14names := state.Contracts().Names()_14for _, 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:
_16import "github.com/onflow/flowkit/v2/config"_16_16// Get all contracts configured for deployment on testnet_16contracts, err := state.DeploymentContractsByNetwork(config.TestnetNetwork)_16if err != nil {_16    log.Fatalf("Failed to get deployment contracts: %v", err)_16}_16_16// Each contract includes deployment information_16for _, 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
_10import "github.com/onflow/flowkit/v2/config"_10_10// Predefined networks_10emulator := config.EmulatorNetwork   // Local emulator_10testnet := config.TestnetNetwork     // Flow testnet_10mainnet := config.MainnetNetwork     // Flow mainnet_10_10log.Printf("Emulator: %s\n", emulator.Host)_10log.Printf("Testnet: %s\n", testnet.Host)_10log.Printf("Mainnet: %s\n", mainnet.Host)
Getting Networks from State
_11// Get all networks defined in flow.json_11networks := state.Networks()_11_11// Get a specific network by name_11testnet, err := networks.ByName("testnet")_11if err != nil {_11    log.Fatalf("Network not found: %v", err)_11}_11_11log.Printf("Network: %s\n", testnet.Name)_11log.Printf("Host: %s\n", testnet.Host)
Adding or Updating Networks
_12// Add a custom network_12networks := state.Networks()_12networks.AddOrUpdate(config.Network{_12    Name: "custom-network",_12    Host: "localhost:3570",_12})_12_12// Save the updated configuration_12err := state.SaveDefault()_12if 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_10aliases := state.AliasesForNetwork(config.TestnetNetwork)_10_10// aliases is a map[string]string of location/name -> address_10for 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:
_33import "github.com/onflow/flowkit/v2/project"_33_33// Get contracts for your target network_33contracts, err := state.DeploymentContractsByNetwork(config.TestnetNetwork)_33if err != nil {_33    log.Fatal(err)_33}_33_33// Create an import replacer with your project's contracts_33importReplacer := project.NewImportReplacer(contracts, nil)_33_33// Parse your Cadence program_33code := []byte(`_33import "Kibble"_33import "FungibleToken"_33_33transaction {_33    prepare(signer: &Account) {_33        // ..._33    }_33}_33`)_33_33program := project.NewProgram(code, []string{}, "")_33_33// Replace imports with deployed addresses_33resolvedProgram, err := importReplacer.Replace(program)_33if err != nil {_33    log.Fatalf("Failed to resolve imports: %v", err)_33}_33_33// The resolved program now has addresses instead of file paths_33log.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_30state, err := flowkit.Load([]string{"flow.json"}, afero.NewOsFs())_30if err != nil {_30    log.Fatal(err)_30}_30_30// Choose your target network_30network := config.TestnetNetwork_30_30// Get contracts for this network_30contracts, err := state.DeploymentContractsByNetwork(network)_30if err != nil {_30    log.Fatal(err)_30}_30_30// Use network-specific aliases for address mapping_30importReplacer := project.NewImportReplacer(_30    contracts,_30    state.AliasesForNetwork(network),_30)_30_30// Parse and resolve your program_30program := project.NewProgram(scriptCode, []string{}, "script.cdc")_30resolvedProgram, err := importReplacer.Replace(program)_30if err != nil {_30    log.Fatalf("Failed to resolve imports: %v", err)_30}_30_30// Use the resolved program for execution_30log.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
_20accounts := state.Accounts()_20_20// Get account by name_20account, err := accounts.ByName("emulator-account")_20if err != nil {_20    log.Fatalf("Account not found: %v", err)_20}_20_20log.Printf("Account: %s\n", account.Name)_20log.Printf("Address: %s\n", account.Address)_20_20// Get all account names_20names := accounts.Names()_20for _, name := range names {_20    log.Printf("Available account: %s\n", name)_20}_20_20// Get account by address_20addr := flow.HexToAddress("0xf8d6e0586b0a20c7")_20account, err = accounts.ByAddress(addr)
Getting the Emulator Service Account
_10// Get the emulator's default service account_10serviceAccount, err := state.EmulatorServiceAccount()_10if err != nil {_10    log.Fatalf("Failed to get service account: %v", err)_10}_10_10log.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
_20deployments := state.Deployments()_20_20// Get all deployments for a network_20testnetDeployments := deployments.ByNetwork("testnet")_20_20for _, 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_20deployment := deployments.ByAccountAndNetwork("my-account", "testnet")_20if 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:
_69package main_69_69import (_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_69func 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.