Rust client
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 types | Params enums |
---|---|---|
null | Option<T> = None | Params::Null |
integer | bool | Params::Boolean(bool) |
integer | i64 | Params::Integer(i64) |
bigInteger | i128 | Params::BigInteger(num_bigint::BigIn) |
decimal | f64 | Params::Decimal(f64) |
string | String | Params::Text(String) |
array | Vec<T> | Params::Array(Vec<Params> ) |
dict | BTreeMap<K, V> | Params::Dict(BTreeMap<String, Params> ) |
byteArray | Vec<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