Transaction - 1
Every transaction consists of the following parts:
- Signature
- Payload
- Transaction Parameters
The Signature defines who is accountable and who's funds will be taken in order to pay for transaction execution.
The Payload is the function (together with the data) that will be executed.
The Transaction Parameters define additional information about our transaction. Here we would say how much tip we want to give, what nonce to use, etc.
In order for our transaction to be executed we need the following parts:
- Establish WebSocket or HTTP connection with a network
- Way to submit a transaction
- Way to check if that transaction was successfully included
Setting up the stage
Our initial setup will have nothing more than the bare minimum to compile our code.
Most of the types that we need are included in the prelude
import collection but because we are not going to use any of it (for now) we will have to manually import modules.
All the future code that we will write will go inside the main
function.
use avail_rust::error::ClientError;
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// Code goes here
Ok(())
}
Connection
The first thing that we need to do is to establish a connection with an existing network. For the sake of brevity, we will cover only how to do it using websockets but you can find in other examples on how to do it either using HTTP or a custom solution.
use avail_rust::{
subxt::backend::rpc::{
reconnecting_rpc_client::RpcClient as ReconnectingRpcClient, RpcClient,
},
AOnlineClient,
};
let endpoint = "ws://127.0.0.1:9944";
let rpc_client = ReconnectingRpcClient::builder().build(endpoint).await;
let rpc_client = rpc_client.map_err(|e| e.to_string())?;
let rpc_client = RpcClient::new(rpc_client);
let online_client = AOnlineClient::from_rpc_client(rpc_client.clone()).await?;
rpc_client
is a low level API that allows us to communicate with our network via rpc calls.
online_client
is a higher level API that provides many helper functions and abstractions.
Accounts
An account represents an identity—usually of a person or an organization—that is capable of making transactions or holding funds. In general, every account has an owner who possesses a public and private key pair. The private key is a cryptographically-secure sequence of randomly-generated numbers. For human readability, the private key generates a random sequence of words called a secret seed phrase or mnemonic.
Substrate - Accounts, Addresses, Keys
To create an account we paste our secret seed as an argument to SecretUri
and then pass that Keypair
. In this case, we will use the default development account named Alice
.
In production you would pass your secret seed via env variable or read it from file.
For Bob use //Bob
, for Eve use //Eve
, etc.
use avail_rust::subxt_signer::{sr25519::Keypair, SecretUri};
use std::str::FromStr;
let secret_uri = SecretUri::from_str("//Alice")?;
let account = Keypair::from_uri(&secret_uri)?;
let account_id = account.public_key().to_account_id();
// 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
let _account_address = account_id.to_string();
Payload
Payload defines what operation will be executed on the chain. Payload consists of three components:
- Pallet Name
- Call Name
- Call Data
What you need to know is that all the payloads are defines in the following path avail_rust::avail::*::calls::types::**;
where *
represents the pallet name and **
represents the call type.
For more examples go to the next page.
use avail_rust::{
avail::runtime_types::bounded_collections::bounded_vec::BoundedVec,
subxt::{blocks::StaticExtrinsic, ext::subxt_core::tx::payload::StaticPayload},
};
use avail_rust::avail::data_availability::calls::types::SubmitData;
let pallet_name = SubmitData::PALLET;
let call_name = SubmitData::CALL;
let data = String::from("My Data").into_bytes();
let data = BoundedVec(data);
let call_data = SubmitData { data };
let payload = StaticPayload::new(pallet_name, call_name, call_data);
Transaction Parameters
There are four transaction parameters:
- nonce
- app_id
- tip
- mortality
Manually building the transaction parameters is a tedious and convoluted job so here we are using a helper object to do that for us.
With the Options
object we can set what parameters we want to use and with calling build()
it populates all the non-set params with default values.
Here are the default values for all the parameters:
- nonce: It uses the best block nonce and it increments it if there are existing transaction in the tx pool with the same nonce
- app_id: 0
- tip: 0
- mortality: The transaction will be alive for 32 blocks starting from current best block hash(height)
use avail_rust::Options;
let options = Options::new()
.build(&online_client, &rpc_client, &account_id)
.await?;
let params = options.build().await?;
Signature
Adding signature to an existing payload and transaction params allows us to build an transaction that is ready to be submitted.
let submittable_tx = online_client
.tx()
.create_signed(&payload, &account, params)
.await?;
Submission
Submission is done by calling .submit()
. There is another method available as well, .submit_and_watch()
, but that one isn't working correctly.
Submitting a transaction yields back the transaction hash.
let tx_hash = submittable_tx.submit().await?;
Watcher
Just because we have submitted our transaction it doesn't mean it was successful or that it got executed at all.
We need to implement a watcher
that will check the next N blocks to see if our tx hash is included in the block.
Once found, we need to search for the ExtrinsicSuccess
event in order to determine if the transaction was successful or not.
use avail_rust::avail::system::events::ExtrinsicSuccess;
let mut block_sub = online_client.blocks().subscribe_all().await?;
while let Some(block) = block_sub.next().await {
let block = block?;
let block_txs = block.extrinsics().await?;
let tx = block_txs.iter().find(|tx| tx.hash() == tx_hash);
if let Some(tx) = tx {
println!("Transaction was found.");
println!("Block Hash: {:?}", block.hash()); // Block Hash: 0x61415b6012005665bac0cf8575a94e509d079a762be2ba6a71a04633efd01c1b
println!("Block Number: {:?}", block.number()); // Block Number: 200
println!("Tx Hash: {:?}", tx.hash()); // Tx Hash: 0x01651a93d55bde0f258504498d4f2164416df5331794d9c905d4c8711d9537ef
println!("Tx Index: {:?}", tx.index()); // Tx Index: 1
let events = tx.events().await?;
println!("Event count: {}", events.iter().count()); // Event count: 7
if events
.find_first::<ExtrinsicSuccess>()
.ok()
.flatten()
.is_some()
{
println!("Transaction was successful");
}
break;
}
}
Source Code
use avail_rust::error::ClientError;
#[tokio::main]
async fn main() -> Result<(), ClientError> {
// RPC Connection
// ANCHOR: connection
use avail_rust::{
subxt::backend::rpc::{
reconnecting_rpc_client::RpcClient as ReconnectingRpcClient, RpcClient,
},
AOnlineClient,
};
let endpoint = "ws://127.0.0.1:9944";
let rpc_client = ReconnectingRpcClient::builder().build(endpoint).await;
let rpc_client = rpc_client.map_err(|e| e.to_string())?;
let rpc_client = RpcClient::new(rpc_client);
let online_client = AOnlineClient::from_rpc_client(rpc_client.clone()).await?;
// ANCHOR_END: connection
// Accounts
// ANCHOR: accounts
use avail_rust::subxt_signer::{sr25519::Keypair, SecretUri};
use std::str::FromStr;
let secret_uri = SecretUri::from_str("//Alice")?;
let account = Keypair::from_uri(&secret_uri)?;
let account_id = account.public_key().to_account_id();
// 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
let _account_address = account_id.to_string();
// ANCHOR_END: accounts
// Payload
// ANCHOR: payload
use avail_rust::{
avail::runtime_types::bounded_collections::bounded_vec::BoundedVec,
subxt::{blocks::StaticExtrinsic, ext::subxt_core::tx::payload::StaticPayload},
};
use avail_rust::avail::data_availability::calls::types::SubmitData;
let pallet_name = SubmitData::PALLET;
let call_name = SubmitData::CALL;
let data = String::from("My Data").into_bytes();
let data = BoundedVec(data);
let call_data = SubmitData { data };
let payload = StaticPayload::new(pallet_name, call_name, call_data);
// ANCHOR_END: payload
// Transaction Parameters
// ANCHOR: params
use avail_rust::Options;
let options = Options::new()
.build(&online_client, &rpc_client, &account_id)
.await?;
let params = options.build().await?;
// ANCHOR_END: params
// Signature
// ANCHOR: signature
let submittable_tx = online_client
.tx()
.create_signed(&payload, &account, params)
.await?;
// ANCHOR_END: signature
// Submission
// ANCHOR: submission
let tx_hash = submittable_tx.submit().await?;
// ANCHOR_END: submission
// Watcher
// ANCHOR: watcher
use avail_rust::avail::system::events::ExtrinsicSuccess;
let mut block_sub = online_client.blocks().subscribe_all().await?;
while let Some(block) = block_sub.next().await {
let block = block?;
let block_txs = block.extrinsics().await?;
let tx = block_txs.iter().find(|tx| tx.hash() == tx_hash);
if let Some(tx) = tx {
println!("Transaction was found.");
println!("Block Hash: {:?}", block.hash()); // Block Hash: 0x61415b6012005665bac0cf8575a94e509d079a762be2ba6a71a04633efd01c1b
println!("Block Number: {:?}", block.number()); // Block Number: 200
println!("Tx Hash: {:?}", tx.hash()); // Tx Hash: 0x01651a93d55bde0f258504498d4f2164416df5331794d9c905d4c8711d9537ef
println!("Tx Index: {:?}", tx.index()); // Tx Index: 1
let events = tx.events().await?;
println!("Event count: {}", events.iter().count()); // Event count: 7
if events
.find_first::<ExtrinsicSuccess>()
.ok()
.flatten()
.is_some()
{
println!("Transaction was successful");
}
break;
}
}
// ANCHOR_END: watcher
Ok(())
}