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(())
}