Flow Go SDK
The Flow Go SDK provides a set of packages for Go developers to build applications that interact with the Flow network.
Note: This SDK is also fully compatible with the Flow Emulator and can be used for local development.
What is Flow?
Flow is a new blockchain for open worlds. Read more about it here.
Table of Contents
Getting Started
Installing
To start using the SDK, install Go 1.13 or above and run go get:
go get github.com/onflow/flow-go-sdk
Building and testing Go commands with the SDK require enabling cgo CGO_ENABLED=1
because of the underlying cryptography library.
Refer to the crypto repository build for more details.
Note that it is possible to build with cgo disabled CGO_ENABLED=0
, but this requires confirming the choice by using the Go build tag no-cgo
. This would disable the crypto BLS signature features of the SDK.
Any call of a BLS tool would result in a panic.
Generating Keys
Flow uses ECDSA
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:
import "github.com/onflow/flow-go-sdk/crypto"
seed := []byte("elephant ears space cowboy octopus rodeo potato cannon pineapple")
privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
The private key can then be encoded as bytes (i.e. for storage):
encPrivateKey := privateKey.Encode()
A private key has an accompanying public key:
publicKey := privateKey.PublicKey()
Supported Curves
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.
Here's how to generate an ECDSA private key for the secp256k1 curve:
privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_secp256k1, seed)
Here's a full list of the supported signature and hash algorithms: Flow Signature & Hash Algorithms
Accessing The Flow Network
You can communicate with any Flow Access Node using the Flow Go SDK. This includes official Access Nodes, nodes you run yourself, and hosted nodes. Flow Go SDK supports both gRPC and HTTP methods of communication with Access Nodes.
It's strongly recommended to use gRPC with Go SDK since it's more efficient and faster.
Here's how to create a new gRPC client for any network:
flowClient, _ := grpc.NewClient(grpc.MainnetHost)
Check out the http_grpc_clients example to learn more.
Creating an Account
Once you have generated a key pair, you can create a new account
using its public key.
import (
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
"github.com/onflow/flow-go-sdk/templates"
)
ctx := context.Background()
seed := []byte("elephant ears space cowboy octopus rodeo potato cannon pineapple")
privateKey, _ := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
publicKey := privateKey.PublicKey()
accountKey := flow.NewAccountKey().
SetPublicKey(publicKey).
SetHashAlgo(crypto.SHA3_256).
SetWeight(flow.AccountKeyWeightThreshold)
script, _ := templates.CreateAccount([]*flow.AccountKey{accountKey}, nil)
c, err := client.New("localhost:3569")
if err != nil {
panic("failed to connect to emulator")
}
payer, payerKey, payerSigner := examples.ServiceAccount(c)
tx := flow.NewTransaction().
SetScript(script).
SetGasLimit(100).
SetProposalKey(payer, payerKey.Index, payerKey.SequenceNumber).
SetPayer(payer)
err = tx.SignEnvelope(payer, payerKey.Index, payerSigner)
if err != nil {
panic("failed to sign transaction")
}
err = c.SendTransaction(ctx, *tx)
if err != nil {
panic("failed to send transaction")
}
result, err := c.GetTransactionResult(ctx, tx.ID())
if err != nil {
panic("failed to get transaction result")
}
var myAddress flow.Address
if result.Status == flow.TransactionStatusSealed {
for _, event := range result.Events {
if event.Type == flow.EventAccountCreated {
accountCreatedEvent := flow.AccountCreatedEvent(event)
myAddress = accountCreatedEvent.Address()
}
}
}
Signing a Transaction
Below is a simple example of how to sign a transaction using a crypto.PrivateKey
.
import (
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
)
var (
myAddress flow.Address
myAccountKey flow.AccountKey
myPrivateKey crypto.PrivateKey
)
tx := flow.NewTransaction().
SetScript([]byte("transaction { execute { log(\"Hello, World!\") } }")).
SetGasLimit(100).
SetProposalKey(myAddress, myAccountKey.Index, myAccountKey.SequenceNumber).
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.
mySigner, err := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
if err != nil {
panic("failed to create a signer")
}
err = tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
if err != nil {
panic("failed to sign transaction")
}
How Signatures Work in Flow
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.
- Proposer, payer and authorizer are the same account (
0x01
). - Only the envelope must be signed.
- Proposal key must have full signing weight.
Account | Key ID | Weight |
---|
0x01 | 1 | 1000 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
key1 := account1.Keys[0]
key1Signer := getSignerForKey1()
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: AuthAccount) { log(signer.address) }
}
`)).
SetGasLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetPayer(account1.Address).
AddAuthorizer(account1.Address)
err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)
Full Runnable Example
- 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.
Account | Key ID | Weight |
---|
0x01 | 1 | 500 |
0x01 | 2 | 500 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
key1 := account1.Keys[0]
key2 := account1.Keys[1]
key1Signer := getSignerForKey1()
key2Signer := getSignerForKey2()
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: AuthAccount) { log(signer.address) }
}
`)).
SetGasLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetPayer(account1.Address).
AddAuthorizer(account1.Address)
err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)
err = tx.SignEnvelope(account1.Address, key2.Index, key2Signer)
Full Runnable Example
- 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 | Key ID | Weight |
---|
0x01 | 1 | 1000 |
0x02 | 3 | 1000 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
key1 := account1.Keys[0]
key3 := account2.Keys[0]
key1Signer := getSignerForKey1()
key3Signer := getSignerForKey3()
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: AuthAccount) { log(signer.address) }
}
`)).
SetGasLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetPayer(account2.Address).
AddAuthorizer(account1.Address)
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
Full Runnable Example
- 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
Account | Key ID | Weight |
---|
0x01 | 1 | 1000 |
0x02 | 3 | 1000 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
key1 := account1.Keys[0]
key3 := account2.Keys[0]
key1Signer := getSignerForKey1()
key3Signer := getSignerForKey3()
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer1: AuthAccount, signer2: AuthAccount) {
log(signer.address)
log(signer2.address)
}
}
`)).
SetGasLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetPayer(account2.Address).
AddAuthorizer(account1.Address).
AddAuthorizer(account2.Address)
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
Full Runnable Example
- 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).
Account | Key ID | Weight |
---|
0x01 | 1 | 500 |
0x01 | 2 | 500 |
0x02 | 3 | 500 |
0x02 | 4 | 500 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
key1 := account1.Keys[0]
key2 := account1.Keys[1]
key3 := account2.Keys[0]
key4 := account2.Keys[1]
key1Signer := getSignerForKey1()
key2Signer := getSignerForKey1()
key3Signer := getSignerForKey3()
key4Signer := getSignerForKey4()
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: AuthAccount) { log(signer.address) }
}
`)).
SetGasLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetPayer(account2.Address).
AddAuthorizer(account1.Address)
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
err = tx.SignPayload(account1.Address, key2.Index, key2Signer)
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
err = tx.SignEnvelope(account2.Address, key4.Index, key4Signer)
Full Runnable Example
Sending a Transaction
You can submit a transaction to the network using the Access API client.
import "github.com/onflow/flow-go-sdk/access"
c, err := client.New("localhost:3569")
if err != nil {
panic("failed to connect to emulator")
}
ctx := context.Background()
err = c.SendTransaction(ctx, tx)
if err != nil {
panic("failed to send transaction")
}
Querying Transaction Results
After you have submitted a transaction, you can query its status by ID:
result, err := c.GetTransactionResult(ctx, tx.ID())
if err != nil {
panic("failed to fetch transaction result")
}
The result includes a Status
field that will be one of the following values:
UNKNOWN
- The transaction has not yet been seen by the network.PENDING
- The transaction has not yet been included in a block.FINALIZED
- The transaction has been included in a block.EXECUTED
- The transaction has been executed but the result has not yet been sealed.SEALED
- The transaction has been executed and the result is sealed in a block.
if result.Status == flow.TransactionStatusSealed {
fmt.Println("Transaction is sealed!")
}
The result also contains an Error
that holds the error information for a failed transaction.
if result.Error != nil {
fmt.Printf("Transaction failed with error: %v\n", result.Error)
}
Querying Blocks
You can use the GetLatestBlock
method to fetch the latest sealed or unsealed block:
isSealed := true
latestBlock, err := c.GetLatestBlock(ctx, isSealed)
if err != nil {
panic("failed to fetch latest sealed block")
}
isSealed := false
latestBlock, err := c.GetLatestBlock(ctx, isSealed)
if err != nil {
panic("failed to fetch latest unsealed block")
}
A block contains the following fields:
ID
- The ID (hash) of the block.ParentBlockID
- The ID of the previous block in the chain.Height
- The height of the block in the chain.CollectionGuarantees
- The list of collections included in the block.
Executing a Script
You can use the ExecuteScriptAtLatestBlock
method to execute a read-only script against the latest sealed execution state.
This functionality can be used to read state from the blockchain.
Scripts must be in the following form:
- A single
main
function with a single return value
This is an example of a valid script:
fun main(): Int { return 1 }
import "github.com/onflow/cadence"
script := []byte("fun main(): Int { return 1 }")
value, err := c.ExecuteScriptAtLatestBlock(ctx, script, nil)
if err != nil {
panic("failed to execute script")
}
ID := value.(cadence.Int)
myID := ID.Int()
Querying Events
You can query events with the GetEventsForHeightRange
function:
import "github.com/onflow/flow-go-sdk/access"
blocks, err := c.GetEventsForHeightRange(ctx, client.EventRangeQuery{
Type: "flow.AccountCreated",
StartHeight: 10,
EndHeight: 15,
})
if err != nil {
panic("failed to query events")
}
Event Query Format
An event query includes the following fields:
Type
The event type to filter by. Event types are namespaced by the account and contract in which they are declared.
For example, a Transfer
event that was defined in the Token
contract deployed at account 0x55555555555555555555
will have a type of A.0x55555555555555555555.Token.Transfer
.
Read the language documentation for more information on how to define and emit events in Cadence.
StartHeight, EndHeight
The blocks to filter by. Events will be returned from blocks in the range StartHeight
to EndHeight
, inclusive.
Event Results
The GetEventsForHeightRange
function returns events grouped by block. Each block contains a list of events matching the query in order of execution.
for _, block := range blocks {
fmt.Printf("Events for block %s:\n", block.BlockID)
for _, event := range block.Events {
fmt.Printf(" - %s", event)
}
}
Querying Accounts
You can query the state of an account with the GetAccount
function:
import "github.com/onflow/flow-go-sdk"
address := flow.HexToAddress("01")
account, err := c.GetAccount(ctx, address)
if err != nil {
panic("failed to fetch account")
}
A flow.Account
contains the following fields:
Address: flow.Address
- The account address.Balance: uint64
- The account balance.Code: []byte
- The code deployed at this account.Keys: []flow.AccountKey
- A list of the public keys associated with this account.
Examples
The examples directory contains code samples that use the SDK to interact with the Flow Emulator.