Categorygithub.com/portto/blocto-flow-go-sdk
modulepackage
0.4.4
Repository: https://github.com/portto/blocto-flow-go-sdk.git
Documentation: pkg.go.dev

# README

Flow Go SDK GoDoc

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/portto/blocto-flow-go-sdk

Generating Keys

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.

Here's how to generate an ECDSA private key on the P-256 curve:

import "github.com/portto/blocto-flow-go-sdk/crypto"

// deterministic seed phrase
// note: this is only an example, please use a secure random generator for the key seed
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 elliptic curve. Flow also supports the secp256k1 curve used by Bitcoin and Ethereum.

Here's how to generate an ECDSA private key on 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

Creating an Account

Once you have generated a key pair, you can create a new account using its public key.

import (
    "github.com/portto/blocto-flow-go-sdk"
    "github.com/portto/blocto-flow-go-sdk/crypto"
    "github.com/portto/blocto-flow-go-sdk/templates"
)

ctx := context.Background()

// generate a new private key for the account
// note: this is only an example, please use a secure random generator for the key seed
seed := []byte("elephant ears space cowboy octopus rodeo potato cannon pineapple")
privateKey, _ := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)

// get the public key
publicKey := privateKey.PublicKey()

// construct an account key from the public key
accountKey := flow.NewAccountKey().
    SetPublicKey(publicKey).
    SetHashAlgo(crypto.SHA3_256).             // pair this key with the SHA3_256 hashing algorithm
    SetWeight(flow.AccountKeyWeightThreshold) // give this key full signing weight

// generate an account creation script
// this creates an account with a single public key and no code
script, _ := templates.CreateAccount([]*flow.AccountKey{accountKey}, nil)

// connect to an emulator running locally
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.ID, payerKey.SequenceNumber).
    SetPayer(payer)

err = tx.SignEnvelope(payer, payerKey.ID, 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/portto/blocto-flow-go-sdk"
    "github.com/portto/blocto-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.ID, 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 hardware keys stored in a 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.

// construct a signer from your private key and configured hash algorithm
mySigner := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)

err := tx.SignEnvelope(myAddress, myAccountKey.ID, 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.


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
0x0111.0
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))

key1 := account1.Keys[0]

// create signer from securely-stored private key
key1Signer := getSignerForKey1()

tx := flow.NewTransaction().
    SetScript([]byte(`
        transaction { 
            prepare(signer: AuthAccount) { log(signer.address) }
        }
    `)).
    SetGasLimit(100).
    SetProposalKey(account1.Address, key1.ID, key1.SequenceNumber).
    SetPayer(account1.Address).
    AddAuthorizer(account1.Address)

// account 1 signs the envelope with key 1
err := tx.SignEnvelope(account1.Address, key1.ID, key1Signer)

Full Runnable Example


Single party, multiple signatures
  • Proposer, payer and authorizer are the same account (0x01).
  • Only the envelope must be signed.
  • Each key has weight 0.5, so two signatures are required.
AccountKey IDWeight
0x0110.5
0x0120.5
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))

key1 := account1.Keys[0]
key2 := account1.Keys[1]

// create signers from securely-stored private keys
key1Signer := getSignerForKey1()
key2Signer := getSignerForKey2()

tx := flow.NewTransaction().
    SetScript([]byte(`
        transaction { 
            prepare(signer: AuthAccount) { log(signer.address) }
        }
    `)).
    SetGasLimit(100).
    SetProposalKey(account1.Address, key1.ID, key1.SequenceNumber).
    SetPayer(account1.Address).
    AddAuthorizer(account1.Address)

// account 1 signs the envelope with key 1
err := tx.SignEnvelope(account1.Address, key1.ID, key1Signer)

// account 1 signs the envelope with key 2
err = tx.SignEnvelope(account1.Address, key2.ID, key2Signer)

Full Runnable Example


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
0x0111.0
0x0231.0
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))

key1 := account1.Keys[0]
key3 := account2.Keys[0]

// create signers from securely-stored private keys
key1Signer := getSignerForKey1()
key3Signer := getSignerForKey3()

tx := flow.NewTransaction().
    SetScript([]byte(`
        transaction { 
            prepare(signer: AuthAccount) { log(signer.address) }
        }
    `)).
    SetGasLimit(100).
    SetProposalKey(account1.Address, key1.ID, key1.SequenceNumber).
    SetPayer(account2.Address).
    AddAuthorizer(account1.Address)

// account 1 signs the payload with key 1
err := tx.SignPayload(account1.Address, key1.ID, key1Signer)

// account 2 signs the envelope with key 3
// note: payer always signs last
err = tx.SignEnvelope(account2.Address, key3.ID, key3Signer)

Full Runnable Example


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
0x0110.5
0x0120.5
0x0230.5
0x0240.5
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]

// create signers from securely-stored private keys
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.ID, key1.SequenceNumber).
    SetPayer(account2.Address).
    AddAuthorizer(account1.Address)

// account 1 signs the payload with key 1
err := tx.SignPayload(account1.Address, key1.ID, key1Signer)

// account 1 signs the payload with key 2
err = tx.SignPayload(account1.Address, key2.ID, key2Signer)

// account 2 signs the envelope with key 3
// note: payer always signs last
err = tx.SignEnvelope(account2.Address, key3.ID, key3Signer)

// account 2 signs the envelope with key 4
// note: payer always signs last
err = tx.SignEnvelope(account2.Address, key4.ID, key4Signer)

Full Runnable Example

Sending a Transaction

You can submit a transaction to the network using the Access API client.

import "github.com/portto/blocto-flow-go-sdk/client"

// connect to an emulator running locally
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:

// fetch the latest sealed block
isSealed := true
latestBlock, err := c.GetLatestBlock(ctx, isSealed)
if err != nil {
    panic("failed to fetch latest sealed block")
}

// fetch the latest unsealed 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.ExecuteScript(ctx, script)
if err != nil {
    panic("failed to execute script")
}

ID := value.(cadence.Int)

// convert to Go int type
myID := ID.Int()

Querying Events

You can query events with the GetEventsForHeightRange function:

import "github.com/portto/blocto-flow-go-sdk/client"

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/portto/blocto-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.

# Packages

Package client provides a Go client for the Flow Access gRPC API.
No description provided by the author
No description provided by the author
Package templates provides functions for generating common Cadence scripts.
No description provided by the author

# Functions

BytesToAddress returns Address with value b.
BytesToID constructs an identifier from a byte slice.
DecodeAccountKey decodes the RLP byte representation of an account key.
No description provided by the author
HexToAddress converts a hex string to an Address.
HexToID constructs an identifier from a hexadecimal string.
NewAccountKey returns an empty account key.
NewAddressGenerator creates a new address generator for the given chain ID, starting from the zero address state.
NewTransaction initializes and returns an empty transaction.
ServiceAddress is the first generated account address.

# Constants

AccountKeyWeightThreshold is the total key weight required to authorize access to an account.
AddressLength is the size of an account address in bytes.
Emulator is the chain ID for the emulated node chain.
List of built-in account event types.
List of built-in account event types.
Mainnet is the chain ID for the mainnet node chain.
Testnet is the chain ID for the testnet node chain.
TransactionStatusExecuted is the status of an executed transaction.
TransactionStatusFinalized is the status of a finalized transaction.
TransactionStatusPending is the status of a pending transaction.
TransactionStatusSealed is the status of a sealed transaction.
TransactionStatusUnknown indicates that the transaction status is not known.

# Variables

DefaultHasher is the default hasher used by Flow.
EmptyAddress is the empty address (0x0000000000000000).
EmptyID is the empty identifier.

# Structs

An Account is an account on the Flow network.
An AccountKey is a public key associated with an account.
An AddressGenerator uses a deterministic algorithm to generate Flow addresses.
Block is a set of state mutations applied to the Flow blockchain.
BlockHeader is a summary of a full block.
BlockPayload is the full contents of a block.
TODO: define block seal struct.
A Collection is a list of transactions bundled together for inclusion in a block.
A CollectionGuarantee is an attestation signed by the nodes that have guaranteed a collection.
No description provided by the author
A ProposalKey is the key that specifies the proposal key and sequence number for a transaction.
A Transaction is a full transaction object containing a payload and signatures.
No description provided by the author
A TransactionSignature is a signature associated with a specific account key.

# Type aliases

An AccountCreatedEvent is emitted when a transaction creates a new Flow account.
Address represents the 8 byte address of an account.
A ChainID is a unique identifier for a specific Flow network instance.
An Identifier is a 32-byte unique identifier for an entity.
TransactionStatus represents the status of a transaction.