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:
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:
1go 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.
After the library has been installed you can import it.
1import "github.com/onflow/flow-go-sdk"
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:
1// common client interface2var flowClient client.Client34// initialize an http emulator client5flowClient, err := http.NewClient(http.EmulatorHost)67// initialize a gPRC emulator client8flowClient, 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:
1// initialize http specific client2httpClient, err := http.NewHTTPClient(http.EMULATOR_URL)34// initialize grpc specific client5grpcClient, err := grpc.NewGRPCClient(6grpc.EMULATOR_URL,7grpcOpts.WithTransportCredentials(insecure.NewCredentials()),8)
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.
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.
This example depicts ways to get the latest block as well as any other block by height or ID:
1func demo() {2ctx := context.Background()3flowClient := examples.NewFlowClient()45// get the latest sealed block6isSealed := true7latestBlock, err := flowClient.GetLatestBlock(ctx, isSealed)8printBlock(latestBlock, err)910// get the block by ID11blockID := latestBlock.ID.String()12blockByID, err := flowClient.GetBlockByID(ctx, flow.HexToID(blockID))13printBlock(blockByID, err)1415// get block by height16blockByHeight, err := flowClient.GetBlockByHeight(ctx, 0)17printBlock(blockByHeight, err)18}1920func printBlock(block *flow.Block, err error) {21examples.Handle(err)2223fmt.Printf("\nID: %s\n", block.ID)24fmt.Printf("height: %d\n", block.Height)25fmt.Printf("timestamp: %s\n\n", block.Timestamp)26}
Result output:
1ID: 835dc83939141097aa4297aa6cf69fc600863e3b5f9241a0d7feac1868adfa4f2height: 103timestamp: 2021-10-06 15:06:07.105382 +0000 UTC456ID: 835dc83939141097aa4297aa6cf69fc600863e3b5f9241a0d7feac1868adfa4f7height: 108timestamp: 2021-10-06 15:06:07.105382 +0000 UTC91011ID: 7bc42fe85d32ca513769a74f97f7e1a7bad6c9407f0d934c2aa645ef9cf613c712height: 013timestamp: 2018-12-19 22:32:30.000000042 +0000 UTC
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.
Example depicts ways to get an account at the latest block and at a specific block height:
1func demo() {2ctx := context.Background()3flowClient := examples.NewFlowClient()45// get account from the latest block6address := flow.HexToAddress("f8d6e0586b0a20c7")7account, err := flowClient.GetAccount(ctx, address)8printAccount(account, err)910// get account from the block by height 011account, err = flowClient.GetAccountAtBlockHeight(ctx, address, 0)12printAccount(account, err)13}1415func printAccount(account *flow.Account, err error) {16examples.Handle(err)1718fmt.Printf("\nAddress: %s", account.Address.String())19fmt.Printf("\nBalance: %d", account.Balance)20fmt.Printf("\nContracts: %d", len(account.Contracts))21fmt.Printf("\nKeys: %d\n", len(account.Keys))22}
Result output:
1Address: f8d6e0586b0a20c72Balance: 9999999999996000003Contracts: 24Keys: 156Address: f8d6e0586b0a20c77Balance: 9999999999996000008Contracts: 29Keys: 1
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.
Status | Final | Description |
---|---|---|
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 |
EXPIRED | โ | The transaction reference block is outdated before being executed |
1func demo(txID flow.Identifier) {2ctx := context.Background()3flowClient := examples.NewFlowClient()45tx, err := flowClient.GetTransaction(ctx, txID)6printTransaction(tx, err)78txr, err := flowClient.GetTransactionResult(ctx, txID)9printTransactionResult(txr, err)10}1112func printTransaction(tx *flow.Transaction, err error) {13examples.Handle(err)1415fmt.Printf("\nID: %s", tx.ID().String())16fmt.Printf("\nPayer: %s", tx.Payer.String())17fmt.Printf("\nProposer: %s", tx.ProposalKey.Address.String())18fmt.Printf("\nAuthorizers: %s", tx.Authorizers)19}2021func printTransactionResult(txr *flow.TransactionResult, err error) {22examples.Handle(err)2324fmt.Printf("\nStatus: %s", txr.Status.String())25fmt.Printf("\nError: %v", txr.Error)26}
Example output:
1ID: fb1272c57cdad79acf2fcf37576d82bf760e3008de66aa32a900c8cd16174e1c2Payer: f8d6e0586b0a20c73Proposer: f8d6e0586b0a20c74Authorizers: []5Status: SEALED6Error: <nil>
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:
1A.{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.
Example depicts ways to get events within block range or by block IDs:
1func demo(deployedContract *flow.Account, runScriptTx *flow.Transaction) {2ctx := context.Background()3flowClient := examples.NewFlowClient()45// Query for account creation events by type6result, err := flowClient.GetEventsForHeightRange(ctx, "flow.AccountCreated", 0, 30)7printEvents(result, err)89// Query for our custom event by type10customType := fmt.Sprintf("AC.%s.EventDemo.EventDemo.Add", deployedContract.Address.Hex())11result, err = flowClient.GetEventsForHeightRange(ctx, customType, 0, 10)12printEvents(result, err)1314// Get events directly from transaction result15txResult, err := flowClient.GetTransactionResult(ctx, runScriptTx.ID())16examples.Handle(err)17printEvent(txResult.Events)18}1920func printEvents(result []client.BlockEvents, err error) {21examples.Handle(err)2223for _, block := range result {24printEvent(block.Events)25}26}2728func printEvent(events []flow.Event) {29for _, event := range events {30fmt.Printf("\n\nType: %s", event.Type)31fmt.Printf("\nValues: %v", event.Value)32fmt.Printf("\nTransaction ID: %s", event.TransactionID)33}34}
Example output:
1Type: flow.AccountCreated2Values: flow.AccountCreated(address: 0xfd43f9148d4b725d)3Transaction ID: ba9d53c8dcb0f9c2f854f93da8467a22d053eab0c540bde0b9ca2f7ad95eb78e45Type: flow.AccountCreated6Values: flow.AccountCreated(address: 0xeb179c27144f783c)7Transaction ID: 8ab7bfef3de1cf8b2ffb36559446100bf4129a9aa88d6bc59f72a467acf0c80189...1011Type: A.eb179c27144f783c.EventDemo.Add12Values: A.eb179c27144f783c.EventDemo.Add(x: 2, y: 3, sum: 5)13Transaction ID: f3a2e33687ad23b0e02644ebbdcd74a7cd8ea7214065410a8007811d0bcbd353
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:
1func demo(exampleCollectionID flow.Identifier) {2ctx := context.Background()3flowClient := examples.NewFlowClient()45// get collection by ID6collection, err := flowClient.GetCollection(ctx, exampleCollectionID)7printCollection(collection, err)8}910func printCollection(collection *flow.Collection, err error) {11examples.Handle(err)1213fmt.Printf("\nID: %s", collection.ID().String())14fmt.Printf("\nTransactions: %s", collection.TransactionIDs)15}
Example output:
1ID: 3d7b8037381f2497d83f2f9e09422c036aae2a59d01a7693fb6003b4d0bc35952Transactions: [cf1184e3de4bd9a7232ca3d0b9dd2cfbf96c97888298b81a05c086451fa52ec1]
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.
1func demo() {2ctx := context.Background()3flowClient := examples.NewFlowClient()45script := []byte(`6pub fun main(a: Int): Int {7return a + 108}9`)10args := []cadence.Value{ cadence.NewInt(5) }11value, err := flowClient.ExecuteScriptAtLatestBlock(ctx, script, args)1213examples.Handle(err)14fmt.Printf("\nValue: %s", value.String())1516complexScript := []byte(`17pub struct User {18pub var balance: UFix6419pub var address: Address20pub var name: String2122init(name: String, address: Address, balance: UFix64) {23self.name = name24self.address = address25self.balance = balance26}27}2829pub fun main(name: String): User {30return User(31name: name,32address: 0x1,33balance: 10.034)35}36`)37args = []cadence.Value{ cadence.NewString("Dete") }38value, err = flowClient.ExecuteScriptAtLatestBlock(ctx, complexScript, args)39printComplexScript(value, err)40}4142type User struct {43balance uint6444address flow.Address45name string46}4748func printComplexScript(value cadence.Value, err error) {49examples.Handle(err)50fmt.Printf("\nString value: %s", value.String())5152s := value.(cadence.Struct)53u := User{54balance: s.Fields[0].ToGoValue().(uint64),55address: s.Fields[1].ToGoValue().([flow.AddressLength]byte),56name: s.Fields[2].ToGoValue().(string),57}5859fmt.Printf("\nName: %s", u.name)60fmt.Printf("\nAddress: %s", u.address.String())61fmt.Printf("\nBalance: %d", u.balance)62}
Example output:
1Value: 152String value: s.34a17571e1505cf6770e6ef16ca387e345e9d54d71909f23a7ec0d671cd2faf5.User(balance: 10.00000000, address: 0x1, name: "Dete")3Name: Dete4Address: 00000000000000015Balance: 1000000000
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:
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:
1transaction(greeting: String) {2execute {3log(greeting.concat(", World!"))4}5}
๐ 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:
1transaction {2prepare(authorizer1: AuthAccount, authorizer2: AuthAccount) { }3}
๐ 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.
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.
1transaction(greeting: String) {23let guest: Address45prepare(authorizer: AuthAccount) {6self.guest = authorizer.address7}89execute {10log(greeting.concat(",").concat(self.guest.toString()))11}12}
1import (2"context"3"ioutil"4"github.com/onflow/flow-go-sdk"5"github.com/onflow/flow-go-sdk/client"6)78func main() {910greeting, err := outil.ReadFile("Greeting2.cdc")11if err != nil {12panic("failed to load Cadence script")13}1415proposerAddress := flow.HexToAddress("9a0766d93b6608b7")16proposerKeyIndex := 31718payerAddress := flow.HexToAddress("631e88ae7f1d7c20")19authorizerAddress := flow.HexToAddress("7aad92e5a0715d21")2021var accessAPIHost string2223// Establish a connection with an access node24flowClient := examples.NewFlowClient()2526// Get the latest sealed block to use as a reference block27latestBlock, err := flowClient.GetLatestBlockHeader(context.Background(), true)28if err != nil {29panic("failed to fetch latest block")30}3132// Get the latest account info for this address33proposerAccount, err := flowClient.GetAccountAtLatestBlock(context.Background(), proposerAddress)34if err != nil {35panic("failed to fetch proposer account")36}3738// Get the latest sequence number for this key39sequenceNumber := proposerAccount.Keys[proposerKeyIndex].SequenceNumber4041tx := flow.NewTransaction().42SetScript(greeting).43SetGasLimit(100).44SetReferenceBlockID(latestBlock.ID).45SetProposalKey(proposerAddress, proposerKeyIndex, sequenceNumber).46SetPayer(payerAddress).47AddAuthorizer(authorizerAddress)4849// Add arguments last5051hello := cadence.NewString("Hello")5253err = tx.AddArgument(hello)54if err != nil {55panic("invalid argument")56}57}
After you have successfully built a transaction the next step in the process is to sign it.
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.
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:
1import (2"github.com/onflow/flow-go-sdk"3"github.com/onflow/flow-go-sdk/crypto"4)56var (7myAddress flow.Address8myAccountKey flow.AccountKey9myPrivateKey crypto.PrivateKey10)1112tx := flow.NewTransaction().13SetScript([]byte("transaction { execute { log(\"Hello, World!\") } }")).14SetGasLimit(100).15SetProposalKey(myAddress, myAccountKey.Index, myAccountKey.SequenceNumber).16SetPayer(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:
1// construct a signer from your private key and configured hash algorithm2mySigner, err := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)3if err != nil {4panic("failed to create a signer")5}67err = tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)8if err != nil {9panic("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.
- 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 |
1account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))23key1 := account1.Keys[0]45// create signer from securely-stored private key6key1Signer := getSignerForKey1()78referenceBlock, _ := flow.GetLatestBlock(ctx, true)9tx := flow.NewTransaction().10SetScript([]byte(`11transaction {12prepare(signer: AuthAccount) { log(signer.address) }13}14`)).15SetGasLimit(100).16SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).17SetReferenceBlockID(referenceBlock.ID).18SetPayer(account1.Address).19AddAuthorizer(account1.Address)2021// account 1 signs the envelope with key 122err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)
- 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 |
1account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))23key1 := account1.Keys[0]4key2 := account1.Keys[1]56// create signers from securely-stored private keys7key1Signer := getSignerForKey1()8key2Signer := getSignerForKey2()910referenceBlock, _ := flow.GetLatestBlock(ctx, true)11tx := flow.NewTransaction().12SetScript([]byte(`13transaction {14prepare(signer: AuthAccount) { log(signer.address) }15}16`)).17SetGasLimit(100).18SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).19SetReferenceBlockID(referenceBlock.ID).20SetPayer(account1.Address).21AddAuthorizer(account1.Address)2223// account 1 signs the envelope with key 124err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)2526// account 1 signs the envelope with key 227err = tx.SignEnvelope(account1.Address, key2.Index, key2Signer)
- 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
Account | Key ID | Weight |
---|---|---|
0x01 | 1 | 1000 |
0x02 | 3 | 1000 |
1account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))2account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))34key1 := account1.Keys[0]5key3 := account2.Keys[0]67// create signers from securely-stored private keys8key1Signer := getSignerForKey1()9key3Signer := getSignerForKey3()1011referenceBlock, _ := flow.GetLatestBlock(ctx, true)12tx := flow.NewTransaction().13SetScript([]byte(`14transaction {15prepare(signer: AuthAccount) { log(signer.address) }16}17`)).18SetGasLimit(100).19SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).20SetReferenceBlockID(referenceBlock.ID).21SetPayer(account2.Address).22AddAuthorizer(account1.Address)2324// account 1 signs the payload with key 125err := tx.SignPayload(account1.Address, key1.Index, key1Signer)2627// account 2 signs the envelope with key 328// note: payer always signs last29err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
- 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
- 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 |
1account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))2account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))34key1 := account1.Keys[0]5key3 := account2.Keys[0]67// create signers from securely-stored private keys8key1Signer := getSignerForKey1()9key3Signer := getSignerForKey3()1011referenceBlock, _ := flow.GetLatestBlock(ctx, true)12tx := flow.NewTransaction().13SetScript([]byte(`14transaction {15prepare(signer1: AuthAccount, signer2: AuthAccount) {16log(signer.address)17log(signer2.address)18}19}20`)).21SetGasLimit(100).22SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).23SetReferenceBlockID(referenceBlock.ID).24SetPayer(account2.Address).25AddAuthorizer(account1.Address).26AddAuthorizer(account2.Address)2728// account 1 signs the payload with key 129err := tx.SignPayload(account1.Address, key1.Index, key1Signer)3031// account 2 signs the envelope with key 332// note: payer always signs last33err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
- 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
- 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 |
1account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))2account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))34key1 := account1.Keys[0]5key2 := account1.Keys[1]6key3 := account2.Keys[0]7key4 := account2.Keys[1]89// create signers from securely-stored private keys10key1Signer := getSignerForKey1()11key2Signer := getSignerForKey1()12key3Signer := getSignerForKey3()13key4Signer := getSignerForKey4()1415referenceBlock, _ := flow.GetLatestBlock(ctx, true)16tx := flow.NewTransaction().17SetScript([]byte(`18transaction {19prepare(signer: AuthAccount) { log(signer.address) }20}21`)).22SetGasLimit(100).23SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).24SetReferenceBlockID(referenceBlock.ID).25SetPayer(account2.Address).26AddAuthorizer(account1.Address)2728// account 1 signs the payload with key 129err := tx.SignPayload(account1.Address, key1.Index, key1Signer)3031// account 1 signs the payload with key 232err = tx.SignPayload(account1.Address, key2.Index, key2Signer)3334// account 2 signs the envelope with key 335// note: payer always signs last36err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)3738// account 2 signs the envelope with key 439// note: payer always signs last40err = tx.SignEnvelope(account2.Address, key4.Index, key4Signer)
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.
1func demo(tx *flow.Transaction) {2ctx := context.Background()3flowClient := examples.NewFlowClient()45err := flowClient.SendTransaction(ctx, *tx)6if err != nil {7fmt.Println("error sending transaction", err)8}9}
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.
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.
1var (2creatorAddress flow.Address3creatorAccountKey *flow.AccountKey4creatorSigner crypto.Signer5)67var accessAPIHost string89// Establish a connection with an access node10flowClient := examples.NewFlowClient()1112// Use the templates package to create a new account creation transaction13tx := templates.CreateAccount([]*flow.AccountKey{accountKey}, nil, creatorAddress)1415// Set the transaction payer and proposal key16tx.SetPayer(creatorAddress)17tx.SetProposalKey(18creatorAddress,19creatorAccountKey.Index,20creatorAccountKey.SequenceNumber,21)2223// Get the latest sealed block to use as a reference block24latestBlock, err := flowClient.GetLatestBlockHeader(context.Background(), true)25if err != nil {26panic("failed to fetch latest block")27}2829tx.SetReferenceBlockID(latestBlock.ID)3031// Sign and submit the transaction32err = tx.SignEnvelope(creatorAddress, creatorAccountKey.Index, creatorSigner)33if err != nil {34panic("failed to sign transaction envelope")35}3637err = flowClient.SendTransaction(context.Background(), *tx)38if err != nil {39panic("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.
1result, err := flowClient.GetTransactionResult(ctx, tx.ID())2if err != nil {3panic("failed to get transaction result")4}56var newAddress flow.Address78if result.Status != flow.TransactionStatusSealed {9panic("address not known until transaction is sealed")10}1112for _, event := range result.Events {13if event.Type == flow.EventAccountCreated {14newAddress = flow.AccountCreatedEvent(event).Address()15break16}17}
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.
1import "github.com/onflow/flow-go-sdk/crypto"23// deterministic seed phrase4// note: this is only an example, please use a secure random generator for the key seed5seed := []byte("elephant ears space cowboy octopus rodeo potato cannon pineapple")67privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)89// the private key can then be encoded as bytes (i.e. for storage)10encPrivateKey := privateKey.Encode()11// the private key has an accompanying public key12publicKey := 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.
This is an example of how to construct a FLOW token transfer transaction with the Flow Go SDK.
The following Cadence script will transfer FLOW tokens from a sender to a recipient.
Note: this transaction is only compatible with Flow Mainnet.
1import FungibleToken from 0xf233dcee88fe0abe2import FlowToken from 0x1654653399040a6134transaction(amount: UFix64, recipient: Address) {5let sentVault: @FungibleToken.Vault6prepare(signer: AuthAccount) {7let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)8?? panic("failed to borrow reference to sender vault")910self.sentVault <- vaultRef.withdraw(amount: amount)11}1213execute {14let receiverRef = getAccount(recipient)15.getCapability(/public/flowTokenReceiver)16.borrow<&{FungibleToken.Receiver}>()17?? panic("failed to borrow reference to recipient vault")1819receiverRef.deposit(from: <-self.sentVault)20}21}
1import (2"github.com/onflow/cadence"3"github.com/onflow/flow-go-sdk"4)56// Replace with script above7const transferScript string = TOKEN_TRANSFER_CADENCE_SCRIPT89var (10senderAddress flow.Address11senderAccountKey flow.AccountKey12senderPrivateKey crypto.PrivateKey13)1415func main() {16tx := flow.NewTransaction().17SetScript([]byte(transferScript)).18SetGasLimit(100).19SetPayer(senderAddress).20SetAuthorizer(senderAddress).21SetProposalKey(senderAddress, senderAccountKey.Index, senderAccountKey.SequenceNumber)2223amount, err := cadence.NewUFix64("123.4")24if err != nil {25panic(err)26}2728recipient := cadence.NewAddress(flow.HexToAddress("0xabc..."))2930err = tx.AddArgument(amount)31if err != nil {32panic(err)33}3435err = tx.AddArgument(recipient)36if err != nil {37panic(err)38}39}