Skip to main content

Flow Go SDK


Overview

This reference documents all the methods available in the SDK, and explains in detail how these methods work. SDKs are open source, and you can use them according to the licence.

The library client specifications can be found here:

Getting Started

Installing

The recommended way to install Go Flow SDK is by using Go modules.

If you already initialized your Go project, you can run the following command in your terminal:


_10
go get github.com/onflow/flow-go-sdk

It's usually good practice to pin your dependencies to a specific version. Refer to the SDK releases page to identify the latest version.

Importing the Library

After the library has been installed you can import it.


_10
import "github.com/onflow/flow-go-sdk"

Connect

The Go SDK library uses HTTP or gRPC APIs to communicate with the access nodes and it must be configured with correct access node API URL. The library provides default factories for connecting to Flow AN APIs and you can easily switch between HTTP or gRPC if you use the provided client interface.

You can check more examples for creating clients in the examples:

Basic Example:


_10
// common client interface
_10
var flowClient client.Client
_10
_10
// initialize an http emulator client
_10
flowClient, err := http.NewClient(http.EmulatorHost)
_10
_10
// initialize a gPRC emulator client
_10
flowClient, err = grpc.NewClient(grpc.EmulatorHost)

You can also initialize an HTTP client or gRPC client directly which will offer you access to network specific options, but be aware you won't be able to easily switch between those since they don't implement a common interface. This is only advisable if the implementation needs the access to those advanced options. Advanced Example:


_10
// initialize http specific client
_10
httpClient, err := http.NewHTTPClient(http.EMULATOR_URL)
_10
_10
// initialize grpc specific client
_10
grpcClient, err := grpc.NewGRPCClient(
_10
grpc.EMULATOR_URL,
_10
grpcOpts.WithTransportCredentials(insecure.NewCredentials()),
_10
)

Querying the Flow Network

After you have established a connection with an access node, you can query the Flow network to retrieve data about blocks, accounts, events and transactions. We will explore how to retrieve each of these entities in the sections below.

Get Blocks

Query the network for block by id, height or get the latest block.

📖 Block ID is SHA3-256 hash of the entire block payload. This hash is stored as an ID field on any block response object (ie. response from GetLatestBlock).

📖 Block height expresses the height of the block on the chain. The latest block height increases by one for every valid block produced.

Examples

This example depicts ways to get the latest block as well as any other block by height or ID:


_26
func demo() {
_26
ctx := context.Background()
_26
flowClient := examples.NewFlowClient()
_26
_26
// get the latest sealed block
_26
isSealed := true
_26
latestBlock, err := flowClient.GetLatestBlock(ctx, isSealed)
_26
printBlock(latestBlock, err)
_26
_26
// get the block by ID
_26
blockID := latestBlock.ID.String()
_26
blockByID, err := flowClient.GetBlockByID(ctx, flow.HexToID(blockID))
_26
printBlock(blockByID, err)
_26
_26
// get block by height
_26
blockByHeight, err := flowClient.GetBlockByHeight(ctx, 0)
_26
printBlock(blockByHeight, err)
_26
}
_26
_26
func printBlock(block *flow.Block, err error) {
_26
examples.Handle(err)
_26
_26
fmt.Printf("\nID: %s\n", block.ID)
_26
fmt.Printf("height: %d\n", block.Height)
_26
fmt.Printf("timestamp: %s\n\n", block.Timestamp)
_26
}

Result output:


_13
ID: 835dc83939141097aa4297aa6cf69fc600863e3b5f9241a0d7feac1868adfa4f
_13
height: 10
_13
timestamp: 2021-10-06 15:06:07.105382 +0000 UTC
_13
_13
_13
ID: 835dc83939141097aa4297aa6cf69fc600863e3b5f9241a0d7feac1868adfa4f
_13
height: 10
_13
timestamp: 2021-10-06 15:06:07.105382 +0000 UTC
_13
_13
_13
ID: 7bc42fe85d32ca513769a74f97f7e1a7bad6c9407f0d934c2aa645ef9cf613c7
_13
height: 0
_13
timestamp: 2018-12-19 22:32:30.000000042 +0000 UTC

Get Account

Retrieve any account from Flow network's latest block or from a specified block height. The GetAccount method is actually an alias for the get account at latest block method.

📖 Account address is a unique account identifier. Be mindful about the 0x prefix, you should use the prefix as a default representation but be careful and safely handle user inputs without the prefix.

An account includes the following data:

  • Address: the account address.
  • Balance: balance of the account.
  • Contracts: list of contracts deployed to the account.
  • Keys: list of keys associated with the account.

Examples

Example depicts ways to get an account at the latest block and at a specific block height:


_22
func demo() {
_22
ctx := context.Background()
_22
flowClient := examples.NewFlowClient()
_22
_22
// get account from the latest block
_22
address := flow.HexToAddress("f8d6e0586b0a20c7")
_22
account, err := flowClient.GetAccount(ctx, address)
_22
printAccount(account, err)
_22
_22
// get account from the block by height 0
_22
account, err = flowClient.GetAccountAtBlockHeight(ctx, address, 0)
_22
printAccount(account, err)
_22
}
_22
_22
func printAccount(account *flow.Account, err error) {
_22
examples.Handle(err)
_22
_22
fmt.Printf("\nAddress: %s", account.Address.String())
_22
fmt.Printf("\nBalance: %d", account.Balance)
_22
fmt.Printf("\nContracts: %d", len(account.Contracts))
_22
fmt.Printf("\nKeys: %d\n", len(account.Keys))
_22
}

Result output:


_10
Address: f8d6e0586b0a20c7
_10
Balance: 999999999999600000
_10
Contracts: 2
_10
Keys: 1
_10
_10
Address: f8d6e0586b0a20c7
_10
Balance: 999999999999600000
_10
Contracts: 2
_10
Keys: 1

Get Transactions

Retrieve transactions from the network by providing a transaction ID. After a transaction has been submitted, you can also get the transaction result to check the status.

📖 Transaction ID is a hash of the encoded transaction payload and can be calculated before submitting the transaction to the network.

⚠️ The transaction ID provided must be from the current spork.

📖 Transaction status represents the state of transaction in the blockchain. Status can change until it is sealed.

StatusFinalDescription
UNKNOWNThe transaction has not yet been seen by the network
PENDINGThe transaction has not yet been included in a block
FINALIZEDThe transaction has been included in a block
EXECUTEDThe transaction has been executed but the result has not yet been sealed
SEALEDThe transaction has been executed and the result is sealed in a block
EXPIREDThe transaction reference block is outdated before being executed


_26
func demo(txID flow.Identifier) {
_26
ctx := context.Background()
_26
flowClient := examples.NewFlowClient()
_26
_26
tx, err := flowClient.GetTransaction(ctx, txID)
_26
printTransaction(tx, err)
_26
_26
txr, err := flowClient.GetTransactionResult(ctx, txID)
_26
printTransactionResult(txr, err)
_26
}
_26
_26
func printTransaction(tx *flow.Transaction, err error) {
_26
examples.Handle(err)
_26
_26
fmt.Printf("\nID: %s", tx.ID().String())
_26
fmt.Printf("\nPayer: %s", tx.Payer.String())
_26
fmt.Printf("\nProposer: %s", tx.ProposalKey.Address.String())
_26
fmt.Printf("\nAuthorizers: %s", tx.Authorizers)
_26
}
_26
_26
func printTransactionResult(txr *flow.TransactionResult, err error) {
_26
examples.Handle(err)
_26
_26
fmt.Printf("\nStatus: %s", txr.Status.String())
_26
fmt.Printf("\nError: %v", txr.Error)
_26
}

Example output:


_10
ID: fb1272c57cdad79acf2fcf37576d82bf760e3008de66aa32a900c8cd16174e1c
_10
Payer: f8d6e0586b0a20c7
_10
Proposer: f8d6e0586b0a20c7
_10
Authorizers: []
_10
Status: SEALED
_10
Error: <nil>

Get Events

Retrieve events by a given type in a specified block height range or through a list of block IDs.

📖 Event type is a string that follow a standard format:


_10
A.{contract address}.{contract name}.{event name}

Please read more about events in the documentation. The exception to this standard are core events, and you should read more about them in this document.

📖 Block height range expresses the height of the start and end block in the chain.

Examples

Example depicts ways to get events within block range or by block IDs:


_34
func demo(deployedContract *flow.Account, runScriptTx *flow.Transaction) {
_34
ctx := context.Background()
_34
flowClient := examples.NewFlowClient()
_34
_34
// Query for account creation events by type
_34
result, err := flowClient.GetEventsForHeightRange(ctx, "flow.AccountCreated", 0, 30)
_34
printEvents(result, err)
_34
_34
// Query for our custom event by type
_34
customType := fmt.Sprintf("AC.%s.EventDemo.EventDemo.Add", deployedContract.Address.Hex())
_34
result, err = flowClient.GetEventsForHeightRange(ctx, customType, 0, 10)
_34
printEvents(result, err)
_34
_34
// Get events directly from transaction result
_34
txResult, err := flowClient.GetTransactionResult(ctx, runScriptTx.ID())
_34
examples.Handle(err)
_34
printEvent(txResult.Events)
_34
}
_34
_34
func printEvents(result []client.BlockEvents, err error) {
_34
examples.Handle(err)
_34
_34
for _, block := range result {
_34
printEvent(block.Events)
_34
}
_34
}
_34
_34
func printEvent(events []flow.Event) {
_34
for _, event := range events {
_34
fmt.Printf("\n\nType: %s", event.Type)
_34
fmt.Printf("\nValues: %v", event.Value)
_34
fmt.Printf("\nTransaction ID: %s", event.TransactionID)
_34
}
_34
}

Example output:


_13
Type: flow.AccountCreated
_13
Values: flow.AccountCreated(address: 0xfd43f9148d4b725d)
_13
Transaction ID: ba9d53c8dcb0f9c2f854f93da8467a22d053eab0c540bde0b9ca2f7ad95eb78e
_13
_13
Type: flow.AccountCreated
_13
Values: flow.AccountCreated(address: 0xeb179c27144f783c)
_13
Transaction ID: 8ab7bfef3de1cf8b2ffb36559446100bf4129a9aa88d6bc59f72a467acf0c801
_13
_13
...
_13
_13
Type: A.eb179c27144f783c.EventDemo.Add
_13
Values: A.eb179c27144f783c.EventDemo.Add(x: 2, y: 3, sum: 5)
_13
Transaction ID: f3a2e33687ad23b0e02644ebbdcd74a7cd8ea7214065410a8007811d0bcbd353

Get Collections

Retrieve a batch of transactions that have been included in the same block, known as collections. Collections are used to improve consensus throughput by increasing the number of transactions per block and they act as a link between a block and a transaction.

📖 Collection ID is SHA3-256 hash of the collection payload.

Example retrieving a collection:


_15
func demo(exampleCollectionID flow.Identifier) {
_15
ctx := context.Background()
_15
flowClient := examples.NewFlowClient()
_15
_15
// get collection by ID
_15
collection, err := flowClient.GetCollection(ctx, exampleCollectionID)
_15
printCollection(collection, err)
_15
}
_15
_15
func printCollection(collection *flow.Collection, err error) {
_15
examples.Handle(err)
_15
_15
fmt.Printf("\nID: %s", collection.ID().String())
_15
fmt.Printf("\nTransactions: %s", collection.TransactionIDs)
_15
}

Example output:


_10
ID: 3d7b8037381f2497d83f2f9e09422c036aae2a59d01a7693fb6003b4d0bc3595
_10
Transactions: [cf1184e3de4bd9a7232ca3d0b9dd2cfbf96c97888298b81a05c086451fa52ec1]

Execute Scripts

Scripts allow you to write arbitrary non-mutating Cadence code on the Flow blockchain and return data. You can learn more about Cadence and scripts here, but we are now only interested in executing the script code and getting back the data.

We can execute a script using the latest state of the Flow blockchain or we can choose to execute the script at a specific time in history defined by a block height or block ID.

📖 Block ID is SHA3-256 hash of the entire block payload, but you can get that value from the block response properties.

📖 Block height expresses the height of the block in the chain.


_62
func demo() {
_62
ctx := context.Background()
_62
flowClient := examples.NewFlowClient()
_62
_62
script := []byte(`
_62
pub fun main(a: Int): Int {
_62
return a + 10
_62
}
_62
`)
_62
args := []cadence.Value{ cadence.NewInt(5) }
_62
value, err := flowClient.ExecuteScriptAtLatestBlock(ctx, script, args)
_62
_62
examples.Handle(err)
_62
fmt.Printf("\nValue: %s", value.String())
_62
_62
complexScript := []byte(`
_62
pub struct User {
_62
pub var balance: UFix64
_62
pub var address: Address
_62
pub var name: String
_62
_62
init(name: String, address: Address, balance: UFix64) {
_62
self.name = name
_62
self.address = address
_62
self.balance = balance
_62
}
_62
}
_62
_62
pub fun main(name: String): User {
_62
return User(
_62
name: name,
_62
address: 0x1,
_62
balance: 10.0
_62
)
_62
}
_62
`)
_62
args = []cadence.Value{ cadence.NewString("Dete") }
_62
value, err = flowClient.ExecuteScriptAtLatestBlock(ctx, complexScript, args)
_62
printComplexScript(value, err)
_62
}
_62
_62
type User struct {
_62
balance uint64
_62
address flow.Address
_62
name string
_62
}
_62
_62
func printComplexScript(value cadence.Value, err error) {
_62
examples.Handle(err)
_62
fmt.Printf("\nString value: %s", value.String())
_62
_62
s := value.(cadence.Struct)
_62
u := User{
_62
balance: s.Fields[0].ToGoValue().(uint64),
_62
address: s.Fields[1].ToGoValue().([flow.AddressLength]byte),
_62
name: s.Fields[2].ToGoValue().(string),
_62
}
_62
_62
fmt.Printf("\nName: %s", u.name)
_62
fmt.Printf("\nAddress: %s", u.address.String())
_62
fmt.Printf("\nBalance: %d", u.balance)
_62
}

Example output:


_10
Value: 15
_10
String value: s.34a17571e1505cf6770e6ef16ca387e345e9d54d71909f23a7ec0d671cd2faf5.User(balance: 10.00000000, address: 0x1, name: "Dete")
_10
Name: Dete
_10
Address: 0000000000000001
_10
Balance: 1000000000

Mutate Flow Network

Flow, like most blockchains, allows anybody to submit a transaction that mutates the shared global chain state. A transaction is an object that holds a payload, which describes the state mutation, and one or more authorizations that permit the transaction to mutate the state owned by specific accounts.

Transaction data is composed and signed with help of the SDK. The signed payload of transaction then gets submitted to the access node API. If a transaction is invalid or the correct number of authorizing signatures are not provided, it gets rejected.

Executing a transaction requires couple of steps:

Transactions

A transaction is nothing more than a signed set of data that includes script code which are instructions on how to mutate the network state and properties that define and limit it's execution. All these properties are explained bellow.

📖 Script field is the portion of the transaction that describes the state mutation logic. On Flow, transaction logic is written in Cadence. Here is an example transaction script:


_10
transaction(greeting: String) {
_10
execute {
_10
log(greeting.concat(", World!"))
_10
}
_10
}

📖 Arguments. A transaction can accept zero or more arguments that are passed into the Cadence script. The arguments on the transaction must match the number and order declared in the Cadence script. Sample script from above accepts a single String argument.

📖 Proposal key must be provided to act as a sequence number and prevent reply and other potential attacks.

Each account key maintains a separate transaction sequence counter; the key that lends its sequence number to a transaction is called the proposal key.

A proposal key contains three fields:

  • Account address
  • Key index
  • Sequence number

A transaction is only valid if its declared sequence number matches the current on-chain sequence number for that key. The sequence number increments by one after the transaction is executed.

📖 Payer is the account that pays the fees for the transaction. A transaction must specify exactly one payer. The payer is only responsible for paying the network and gas fees; the transaction is not authorized to access resources or code stored in the payer account.

📖 Authorizers are accounts that authorize a transaction to read and mutate their resources. A transaction can specify zero or more authorizers, depending on how many accounts the transaction needs to access.

The number of authorizers on the transaction must match the number of AuthAccount parameters declared in the prepare statement of the Cadence script.

Example transaction with multiple authorizers:


_10
transaction {
_10
prepare(authorizer1: AuthAccount, authorizer2: AuthAccount) { }
_10
}

📖 Gas limit is the limit on the amount of computation a transaction requires, and it will abort if it exceeds its gas limit. Cadence uses metering to measure the number of operations per transaction. You can read more about it in the Cadence documentation.

The gas limit depends on the complexity of the transaction script. Until dedicated gas estimation tooling exists, it's best to use the emulator to test complex transactions and determine a safe limit.

📖 Reference block specifies an expiration window (measured in blocks) during which a transaction is considered valid by the network. A transaction will be rejected if it is submitted past its expiry block. Flow calculates transaction expiry using the reference block field on a transaction. A transaction expires after 600 blocks are committed on top of the reference block, which takes about 10 minutes at average Mainnet block rates.

Build Transactions

Building a transaction involves setting the required properties explained above and producing a transaction object.

Here we define a simple transaction script that will be used to execute on the network and serve as a good learning example.


_12
transaction(greeting: String) {
_12
_12
let guest: Address
_12
_12
prepare(authorizer: AuthAccount) {
_12
self.guest = authorizer.address
_12
}
_12
_12
execute {
_12
log(greeting.concat(",").concat(self.guest.toString()))
_12
}
_12
}


_57
import (
_57
"context"
_57
"os"
_57
"github.com/onflow/flow-go-sdk"
_57
"github.com/onflow/flow-go-sdk/client"
_57
)
_57
_57
func main() {
_57
_57
greeting, err := os.ReadFile("Greeting2.cdc")
_57
if err != nil {
_57
panic("failed to load Cadence script")
_57
}
_57
_57
proposerAddress := flow.HexToAddress("9a0766d93b6608b7")
_57
proposerKeyIndex := 3
_57
_57
payerAddress := flow.HexToAddress("631e88ae7f1d7c20")
_57
authorizerAddress := flow.HexToAddress("7aad92e5a0715d21")
_57
_57
var accessAPIHost string
_57
_57
// Establish a connection with an access node
_57
flowClient := examples.NewFlowClient()
_57
_57
// Get the latest sealed block to use as a reference block
_57
latestBlock, err := flowClient.GetLatestBlockHeader(context.Background(), true)
_57
if err != nil {
_57
panic("failed to fetch latest block")
_57
}
_57
_57
// Get the latest account info for this address
_57
proposerAccount, err := flowClient.GetAccountAtLatestBlock(context.Background(), proposerAddress)
_57
if err != nil {
_57
panic("failed to fetch proposer account")
_57
}
_57
_57
// Get the latest sequence number for this key
_57
sequenceNumber := proposerAccount.Keys[proposerKeyIndex].SequenceNumber
_57
_57
tx := flow.NewTransaction().
_57
SetScript(greeting).
_57
SetComputeLimit(100).
_57
SetReferenceBlockID(latestBlock.ID).
_57
SetProposalKey(proposerAddress, proposerKeyIndex, sequenceNumber).
_57
SetPayer(payerAddress).
_57
AddAuthorizer(authorizerAddress)
_57
_57
// Add arguments last
_57
_57
hello := cadence.NewString("Hello")
_57
_57
err = tx.AddArgument(hello)
_57
if err != nil {
_57
panic("invalid argument")
_57
}
_57
}

After you have successfully built a transaction the next step in the process is to sign it.

Sign Transactions

Flow introduces new concepts that allow for more flexibility when creating and signing transactions. Before trying the examples below, we recommend that you read through the [transaction signature documentation](../../../build/basics/transactions.md.

After you have successfully built a transaction the next step in the process is to sign it. Flow transactions have envelope and payload signatures, and you should learn about each in the signature documentation.

Quick example of building a transaction:


_16
import (
_16
"github.com/onflow/flow-go-sdk"
_16
"github.com/onflow/flow-go-sdk/crypto"
_16
)
_16
_16
var (
_16
myAddress flow.Address
_16
myAccountKey flow.AccountKey
_16
myPrivateKey crypto.PrivateKey
_16
)
_16
_16
tx := flow.NewTransaction().
_16
SetScript([]byte("transaction { execute { log(\"Hello, World!\") } }")).
_16
SetComputeLimit(100).
_16
SetProposalKey(myAddress, myAccountKey.Index, myAccountKey.SequenceNumber).
_16
SetPayer(myAddress)

Transaction signing is done through the crypto.Signer interface. The simplest (and least secure) implementation of crypto.Signer is crypto.InMemorySigner.

Signatures can be generated more securely using keys stored in a hardware device such as an HSM. The crypto.Signer interface is intended to be flexible enough to support a variety of signer implementations and is not limited to in-memory implementations.

Simple signature example:


_10
// construct a signer from your private key and configured hash algorithm
_10
mySigner, err := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
_10
if err != nil {
_10
panic("failed to create a signer")
_10
}
_10
_10
err = tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
_10
if err != nil {
_10
panic("failed to sign transaction")
_10
}

Flow supports great flexibility when it comes to transaction signing, we can define multiple authorizers (multi-sig transactions) and have different payer account than proposer. We will explore advanced signing scenarios bellow.

Single party, single signature

  • Proposer, payer and authorizer are the same account (0x01).
  • Only the envelope must be signed.
  • Proposal key must have full signing weight.
AccountKey IDWeight
0x0111000


_22
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
_22
_22
key1 := account1.Keys[0]
_22
_22
// create signer from securely-stored private key
_22
key1Signer := getSignerForKey1()
_22
_22
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
_22
tx := flow.NewTransaction().
_22
SetScript([]byte(`
_22
transaction {
_22
prepare(signer: AuthAccount) { log(signer.address) }
_22
}
_22
`)).
_22
SetComputeLimit(100).
_22
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
_22
SetReferenceBlockID(referenceBlock.ID).
_22
SetPayer(account1.Address).
_22
AddAuthorizer(account1.Address)
_22
_22
// account 1 signs the envelope with key 1
_22
err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)

Single party, multiple signatures

  • Proposer, payer and authorizer are the same account (0x01).
  • Only the envelope must be signed.
  • Each key has weight 500, so two signatures are required.
AccountKey IDWeight
0x011500
0x012500


_27
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
_27
_27
key1 := account1.Keys[0]
_27
key2 := account1.Keys[1]
_27
_27
// create signers from securely-stored private keys
_27
key1Signer := getSignerForKey1()
_27
key2Signer := getSignerForKey2()
_27
_27
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
_27
tx := flow.NewTransaction().
_27
SetScript([]byte(`
_27
transaction {
_27
prepare(signer: AuthAccount) { log(signer.address) }
_27
}
_27
`)).
_27
SetComputeLimit(100).
_27
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
_27
SetReferenceBlockID(referenceBlock.ID).
_27
SetPayer(account1.Address).
_27
AddAuthorizer(account1.Address)
_27
_27
// account 1 signs the envelope with key 1
_27
err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)
_27
_27
// account 1 signs the envelope with key 2
_27
err = tx.SignEnvelope(account1.Address, key2.Index, key2Signer)

Multiple parties

  • Proposer and authorizer are the same account (0x01).
  • Payer is a separate account (0x02).
  • Account 0x01 signs the payload.
  • Account 0x02 signs the envelope.
    • Account 0x02 must sign last since it is the payer.
AccountKey IDWeight
0x0111000
0x0231000


_29
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
_29
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
_29
_29
key1 := account1.Keys[0]
_29
key3 := account2.Keys[0]
_29
_29
// create signers from securely-stored private keys
_29
key1Signer := getSignerForKey1()
_29
key3Signer := getSignerForKey3()
_29
_29
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
_29
tx := flow.NewTransaction().
_29
SetScript([]byte(`
_29
transaction {
_29
prepare(signer: AuthAccount) { log(signer.address) }
_29
}
_29
`)).
_29
SetComputeLimit(100).
_29
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
_29
SetReferenceBlockID(referenceBlock.ID).
_29
SetPayer(account2.Address).
_29
AddAuthorizer(account1.Address)
_29
_29
// account 1 signs the payload with key 1
_29
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
_29
_29
// account 2 signs the envelope with key 3
_29
// note: payer always signs last
_29
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)

Multiple parties, two authorizers

  • Proposer and authorizer are the same account (0x01).
  • Payer is a separate account (0x02).
  • Account 0x01 signs the payload.
  • Account 0x02 signs the envelope.
    • Account 0x02 must sign last since it is the payer.
  • Account 0x02 is also an authorizer to show how to include two AuthAccounts into an transaction
AccountKey IDWeight
0x0111000
0x0231000


_33
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
_33
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
_33
_33
key1 := account1.Keys[0]
_33
key3 := account2.Keys[0]
_33
_33
// create signers from securely-stored private keys
_33
key1Signer := getSignerForKey1()
_33
key3Signer := getSignerForKey3()
_33
_33
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
_33
tx := flow.NewTransaction().
_33
SetScript([]byte(`
_33
transaction {
_33
prepare(signer1: AuthAccount, signer2: AuthAccount) {
_33
log(signer.address)
_33
log(signer2.address)
_33
}
_33
}
_33
`)).
_33
SetComputeLimit(100).
_33
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
_33
SetReferenceBlockID(referenceBlock.ID).
_33
SetPayer(account2.Address).
_33
AddAuthorizer(account1.Address).
_33
AddAuthorizer(account2.Address)
_33
_33
// account 1 signs the payload with key 1
_33
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
_33
_33
// account 2 signs the envelope with key 3
_33
// note: payer always signs last
_33
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)

Multiple parties, multiple signatures

  • Proposer and authorizer are the same account (0x01).
  • Payer is a separate account (0x02).
  • Account 0x01 signs the payload.
  • Account 0x02 signs the envelope.
    • Account 0x02 must sign last since it is the payer.
  • Both accounts must sign twice (once with each of their keys).
AccountKey IDWeight
0x011500
0x012500
0x023500
0x024500


_40
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
_40
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
_40
_40
key1 := account1.Keys[0]
_40
key2 := account1.Keys[1]
_40
key3 := account2.Keys[0]
_40
key4 := account2.Keys[1]
_40
_40
// create signers from securely-stored private keys
_40
key1Signer := getSignerForKey1()
_40
key2Signer := getSignerForKey1()
_40
key3Signer := getSignerForKey3()
_40
key4Signer := getSignerForKey4()
_40
_40
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
_40
tx := flow.NewTransaction().
_40
SetScript([]byte(`
_40
transaction {
_40
prepare(signer: AuthAccount) { log(signer.address) }
_40
}
_40
`)).
_40
SetComputeLimit(100).
_40
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
_40
SetReferenceBlockID(referenceBlock.ID).
_40
SetPayer(account2.Address).
_40
AddAuthorizer(account1.Address)
_40
_40
// account 1 signs the payload with key 1
_40
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
_40
_40
// account 1 signs the payload with key 2
_40
err = tx.SignPayload(account1.Address, key2.Index, key2Signer)
_40
_40
// account 2 signs the envelope with key 3
_40
// note: payer always signs last
_40
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
_40
_40
// account 2 signs the envelope with key 4
_40
// note: payer always signs last
_40
err = tx.SignEnvelope(account2.Address, key4.Index, key4Signer)

Send Transactions

After a transaction has been built and signed, it can be sent to the Flow blockchain where it will be executed. If sending was successful you can then retrieve the transaction result.


_10
func demo(tx *flow.Transaction) {
_10
ctx := context.Background()
_10
flowClient := examples.NewFlowClient()
_10
_10
err := flowClient.SendTransaction(ctx, *tx)
_10
if err != nil {
_10
fmt.Println("error sending transaction", err)
_10
}
_10
}

Create Accounts

On Flow, account creation happens inside a transaction. Because the network allows for a many-to-many relationship between public keys and accounts, it's not possible to derive a new account address from a public key offline.

The Flow VM uses a deterministic address generation algorithm to assign account addresses on chain. You can find more details about address generation in the accounts & keys documentation.

Public Key

Flow uses ECDSA key pairs to control access to user accounts. Each key pair can be used in combination with the SHA2-256 or SHA3-256 hashing algorithms.

⚠️ You'll need to authorize at least one public key to control your new account.

Flow represents ECDSA public keys in raw form without additional metadata. Each key is a single byte slice containing a concatenation of its X and Y components in big-endian byte form.

A Flow account can contain zero (not possible to control) or more public keys, referred to as account keys. Read more about accounts in the documentation.

An account key contains the following data:

  • Raw public key (described above)
  • Signature algorithm
  • Hash algorithm
  • Weight (integer between 0-1000)

Account creation happens inside a transaction, which means that somebody must pay to submit that transaction to the network. We'll call this person the account creator. Make sure you have read sending a transaction section first.


_40
var (
_40
creatorAddress flow.Address
_40
creatorAccountKey *flow.AccountKey
_40
creatorSigner crypto.Signer
_40
)
_40
_40
var accessAPIHost string
_40
_40
// Establish a connection with an access node
_40
flowClient := examples.NewFlowClient()
_40
_40
// Use the templates package to create a new account creation transaction
_40
tx := templates.CreateAccount([]*flow.AccountKey{accountKey}, nil, creatorAddress)
_40
_40
// Set the transaction payer and proposal key
_40
tx.SetPayer(creatorAddress)
_40
tx.SetProposalKey(
_40
creatorAddress,
_40
creatorAccountKey.Index,
_40
creatorAccountKey.SequenceNumber,
_40
)
_40
_40
// Get the latest sealed block to use as a reference block
_40
latestBlock, err := flowClient.GetLatestBlockHeader(context.Background(), true)
_40
if err != nil {
_40
panic("failed to fetch latest block")
_40
}
_40
_40
tx.SetReferenceBlockID(latestBlock.ID)
_40
_40
// Sign and submit the transaction
_40
err = tx.SignEnvelope(creatorAddress, creatorAccountKey.Index, creatorSigner)
_40
if err != nil {
_40
panic("failed to sign transaction envelope")
_40
}
_40
_40
err = flowClient.SendTransaction(context.Background(), *tx)
_40
if err != nil {
_40
panic("failed to send transaction to network")
_40
}

After the account creation transaction has been submitted you can retrieve the new account address by getting the transaction result.

The new account address will be emitted in a system-level flow.AccountCreated event.


_17
result, err := flowClient.GetTransactionResult(ctx, tx.ID())
_17
if err != nil {
_17
panic("failed to get transaction result")
_17
}
_17
_17
var newAddress flow.Address
_17
_17
if result.Status != flow.TransactionStatusSealed {
_17
panic("address not known until transaction is sealed")
_17
}
_17
_17
for _, event := range result.Events {
_17
if event.Type == flow.EventAccountCreated {
_17
newAddress = flow.AccountCreatedEvent(event).Address()
_17
break
_17
}
_17
}

Generate Keys

Flow uses ECDSA signatures to control access to user accounts. Each key pair can be used in combination with the SHA2-256 or SHA3-256 hashing algorithms.

Here's how to generate an ECDSA private key for the P-256 (secp256r1) curve.


_12
import "github.com/onflow/flow-go-sdk/crypto"
_12
_12
// deterministic seed phrase
_12
// note: this is only an example, please use a secure random generator for the key seed
_12
seed := []byte("elephant ears space cowboy octopus rodeo potato cannon pineapple")
_12
_12
privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
_12
_12
// the private key can then be encoded as bytes (i.e. for storage)
_12
encPrivateKey := privateKey.Encode()
_12
// the private key has an accompanying public key
_12
publicKey := privateKey.PublicKey()

The example above uses an ECDSA key pair on the P-256 (secp256r1) elliptic curve. Flow also supports the secp256k1 curve used by Bitcoin and Ethereum. Read more about supported algorithms here.

Transfering Flow

This is an example of how to construct a FLOW token transfer transaction with the Flow Go SDK.

Cadence Script

The following Cadence script will transfer FLOW tokens from a sender to a recipient.

Note: this transaction is only compatible with Flow Mainnet.


_21
import FungibleToken from 0xf233dcee88fe0abe
_21
import FlowToken from 0x1654653399040a61
_21
_21
transaction(amount: UFix64, recipient: Address) {
_21
let sentVault: @FungibleToken.Vault
_21
prepare(signer: AuthAccount) {
_21
let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)
_21
?? panic("failed to borrow reference to sender vault")
_21
_21
self.sentVault <- vaultRef.withdraw(amount: amount)
_21
}
_21
_21
execute {
_21
let receiverRef = getAccount(recipient)
_21
.getCapability(/public/flowTokenReceiver)
_21
.borrow<&{FungibleToken.Receiver}>()
_21
?? panic("failed to borrow reference to recipient vault")
_21
_21
receiverRef.deposit(from: <-self.sentVault)
_21
}
_21
}

Build the Transaction


_39
import (
_39
"github.com/onflow/cadence"
_39
"github.com/onflow/flow-go-sdk"
_39
)
_39
_39
// Replace with script above
_39
const transferScript string = TOKEN_TRANSFER_CADENCE_SCRIPT
_39
_39
var (
_39
senderAddress flow.Address
_39
senderAccountKey flow.AccountKey
_39
senderPrivateKey crypto.PrivateKey
_39
)
_39
_39
func main() {
_39
tx := flow.NewTransaction().
_39
SetScript([]byte(transferScript)).
_39
SetComputeLimit(100).
_39
SetPayer(senderAddress).
_39
SetAuthorizer(senderAddress).
_39
SetProposalKey(senderAddress, senderAccountKey.Index, senderAccountKey.SequenceNumber)
_39
_39
amount, err := cadence.NewUFix64("123.4")
_39
if err != nil {
_39
panic(err)
_39
}
_39
_39
recipient := cadence.NewAddress(flow.HexToAddress("0xabc..."))
_39
_39
err = tx.AddArgument(amount)
_39
if err != nil {
_39
panic(err)
_39
}
_39
_39
err = tx.AddArgument(recipient)
_39
if err != nil {
_39
panic(err)
_39
}
_39
}