Skip to main content

Rust client

note

We are currently updating this documentation. While it offers a general overview, some details may be outdated. Please check back soon for the latest version.

The Rust client is used for interacting with the Chromia blockchain deployed to a Postchain single node (manual mode) or multi-nodes managed by the Directory Chain (managed mode).

This library provides functionality for executing queries, creating and signing transactions, and managing blockchain operations.

Installation

Add this to your Cargo.toml:

This is for using only the postchain_client::utils::operation::Params enum to construct data for queries and transactions.

[dependencies]
postchain-client = "0.0.1"
tokio = { version = "1.42.0", features = ["rt"] }

For both, use the postchain_client::utils::operation::Params enum and the Rust's struct to serialize and deserialize with serde:

[dependencies]
postchain-client = "0.0.1"
tokio = { version = "1.42.0", features = ["rt"] }
serde = { version = "1.0.216", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }

1. Setting up the client

use postchain_client::transport::client::RestClient;

let client = RestClient {
node_url: vec!["http://localhost:7740", "http://localhost:7741"],
request_time_out: 30,
poll_attemps: 5,
poll_attemp_interval_time: 5
};

2. Executing queries

Queries allow us to fetch data from the blockchain:

use postchain_client::utils::operation::Params;

async fn execute_query_with_params(client: &RestClient<'_>) -> Result<(), Box<dyn std::error::Error>> {
let query_type = "<query_name>";
let mut query_arguments = vec![
("arg1", Params::Text("value1".to_string())),
("arg2", Params::Text("value2".to_string())),
];

let result = client.query(
"<BLOCKCHAIN_RID>",
None,
query_type,
None,
Some(&mut query_arguments)
).await?;

Ok(())
}

async fn execute_query_with_struct(client: &RestClient<'_>) -> Result<(), Box<dyn std::error::Error>> {
let query_type = "<query_name>";

#[derive(Debug, Default, serde::Serialize)]
struct QueryArguments {
arg1: String,
arg2: String
}

let query_arguments = Params::from_struct_to_vec(&QueryArguments {
arg1: "value1".to_string(), arg2: "value2".to_string()
});

let mut query_arguments_ref: Vec<(&str, Params)> = query_arguments.iter().map(|v| (v.0.as_str(), v.1.clone())).collect();

let result = client.query(
"<BLOCKCHAIN_RID>",
None,
query_type,
None,
Some(&mut query_arguments_ref)
).await?;

if let RestResponse::Bytes(val1) = result {
println!("{:?}", gtv::decode(&val1));
}

Ok(())
}

3. Creating and sending transactions

3.1 Creating operations

use postchain_client::utils::operation::{Operation, Params};

// Create operation with named parameters (dictionary)
let operation = Operation::from_dict(
"operation_name",
vec![
("param1", Params::Text("value1".to_string())),
("param2", Params::Integer(42)),
]
);

// Or create an operation with unnamed parameters (list)
let operation = Operation::from_list(
"operation_name",
vec![
Params::Text("value1".to_string()),
Params::Integer(42),
]
);

3.2 Creating and signing transactions

use postchain_client::utils::transaction::Transaction;

// Create a new transaction
let mut tx = Transaction::new(
brid_hex_decoded.to_vec(), // blockchain RID in hex decode to vec
Some(vec![operation]), // operations
None, // signers (optional)
None // signatures (optional)
);

// Sign transaction with private key
let private_key = [0u8; 64]; // Your private key bytes
tx.sign(&private_key)?;

// Or sign with multiple private keys
let private_keys = vec![&private_key1, &private_key2];
tx.multi_sign(&private_keys)?;

3.3 Sending transactions

async fn send_transaction(client: &RestClient<'_>, tx: &Transaction<'_>) -> Result<(), Box<dyn std::error::Error>> {
// Send transaction
let response = client.send_transaction(tx).await?;

// Get transaction RID (for status checking)
let tx_rid = tx.tx_rid_hex();

// Check transaction status
let status = client.get_transaction_status("<blockchain RID>", &tx_rid).await?;

Ok(())
}

4. Error and response handling

The response from client.query and client.send_transaction is a postchain_client::transport::client::RestResponse enum if success or a postchain_client::transport::client::RestError enum if failed.

We can handle it as follows:

let result = client.query(/* ... */).await;

match result {
Ok(resp: RestResponse) => {
if let RestResponse::Bytes(val1) = resp {
let params = gtv::decode(&val1);
/// Do whatever we want with the decoded params
}
},
Error(error: RestError) => {
/// Do whatever we want with the error
}
}
let result = client.send_transaction(&tx).await;

match result {
Ok(resp: RestResponse) => {
println!("Transaction sent successfully: {:?}", resp);
},
Err(error: ) => {
eprintln!("Error sending transaction: {:?}", err);
}
}

The response from client.get_transaction_status is a postchain_client::utils::transaction::TransactionStatus enum if success or a postchain_client::transport::client::RestError enum if failed.

We can handle it as follows:

let result = client.get_transaction_status("<blockchain RID>", &tx_rid).await;

match result {
Ok(resp: TransactionStatus) => {
/// Do anything else here
},
Err(error: RestError) => {
/// Do whatever we want with the error
}
}

5. Use serde for serialize or deserialize a struct to Params::Dict or vice versa

Please look at all tests in the operation.rs source here: https://github.com/cuonglb/postchain-client-rust/blob/dev/src/utils/operation.rs

6. Logging

The postchain-client uses tracing crate for logging. You can use tracing-subscriber crate to enable all logs.

use tracing_subscriber;

#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
...
}

7. Parameter types

The library supports various parameter types through the Params enum and Rust struct :

GTV(*)Rust typesParams enums
nullOption<T> = NoneParams::Null
integerboolParams::Boolean(bool)
integeri64Params::Integer(i64)
bigIntegeri128Params::BigInteger(num_bigint::BigIn)
decimalf64Params::Decimal(f64)
stringStringParams::Text(String)
arrayVec<T>Params::Array(Vec<Params>)
dictBTreeMap<K, V>Params::Dict(BTreeMap<String, Params>)
byteArrayVec<u8>Params:: ByteArray(Vec<u8>)

(*) GTV gets converted to ASN.1 DER when it's sent. See more : https://docs.chromia.com/intro/architecture/generic-transaction-protocol#generic-transfer-value-gtv

Examples

Book review application example

Here's a real-world example from a book review application that demonstrates querying, creating transactions, and handling structured data: https://github.com/cuonglb/postchain-client-rust/tree/dev/examples/book-review

This example demonstrates:

  • Defining and using structured data with Serde
  • Querying the blockchain and handling responses
  • Creating and sending transactions
  • Signing transactions with a private key
  • Transaction error handling and status checking

How to run

Install Rust (https://www.rust-lang.org/tools/install) and Docker with compose.

Start a Postchain single node with the book-review Rell dapp:

$ cd examples/book-review/rell-dapp/
$ sudo docker compose up -d

Start a simple Rust application to interact with the book-review blockchain:

$ cd examples/book-review/client
$ cargo run