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:
git clone https://github.com/Evan-Dapplica/FlowCppSDK.git
cd FlowCppSDK
mkdir -p cmake/build
cd cmake/build
cmake ../..
make
The library uses gRPC to communicate with the access nodes and it must be configured with correct access node API URL.
📖 Access API URLs can be found here. An error will be returned if the host is unreachable. The Access Nodes APIs hosted by DapperLabs are accessible at:
-
Testnet
access.devnet.nodes.onflow.org:9000
-
Mainnet
access.mainnet.nodes.onflow.org:9000
-
Local Emulator
127.0.0.1:3569
Example:
std::string target_address("access.mainnet.nodes.onflow.org:9000");
FlowClient client(grpc::CreateChannel(target_address, grpc::InsecureChannelCredentials()));
client.Ping();
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:
BlockResponse block_reply;
if(!(client.GetLatestBlock(true, block_reply).ok()))
{
std::cout << "Error reading latest block";
return;
}
id = block_reply.block().id();
height = block_reply.block().height()
client.GetBlockByHeight(height,block_reply) //.....
client.GetBlockByID(id, block_reply )//....
Retrieve any account from Flow network's latest block or from a specified block height.
📖 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:
GetAccountResponse account_reply;
if(!client.GetAccount("0x877d3e50c611fc87",account_reply).ok())
{
std::cout << "Error reading account by address" << std::endl;
}
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.
📖 Transaction status represents the state of transaction in the blockchain. Status can change until is finalized.
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 |
TransactionResponse transaction_reply;
if(!client.GetTransaction("4aca30e1bf4eb6cd5c1bf48bbcd69a66de63e54f954092e6ec51bc95c6fed7f6",transaction_reply).ok())
{
std::cout << "Error reading transaction" << std::endl;
}
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:
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.
Example depicts ways to get events within block range or by block IDs:
EventsResponse events_reply;
if(!client.GetEventsForHeightRange("A.7e60df042a9c0868.FlowToken.TokensWithdrawn", 50157100, 50157101,events_reply).ok())
{
std::cout << "Error reading events" << std::endl;
}
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:
CollectionResponse collection_reply;
if(!client.GetCollectionByID("f2a15028f4502c088d5460f1f086b65c0a71bb3da44bde6c6c5dcce254ddf849",collection_reply).ok())
{
std::cout << "Error reading collection" << std::endl;
}
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.
ExecuteScriptResponse executing_reply;
const char* script = "pub fun main(): Int {return 1 + 2}";
if(!client.ExecuteScriptAtLatestBlock(script,{},executing_reply).ok())
{
std::cout << "Error executing script" << std::endl;
}
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:
transaction(greeting: String) {
execute {
log(greeting.concat(", World!"))
}
}
📖 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:
transaction {
prepare(authorizer1: AuthAccount, authorizer2: AuthAccount) { }
}
📖 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.
Note: this is not implement yet.