rustao 0.1.0

Rust SDK for the AO protocol on Arweave
Documentation

rustao – Rust SDK for AO

Crates.io docs.rs License

rustao is an asynchronous Rust library for interacting with the AO protocol on Arweave. It provides a clean, ergonomic API to send messages, spawn processes, perform dry runs, query compute endpoints, and handle hybrid encryption – matching the functionality of the official goao SDK.


πŸš€ Features

  • βœ… Async/Await – Built on tokio for high performance.
  • πŸ“¨ Send Messages – Sign and send messages to AO processes.
  • 🌱 Spawn Processes – Deploy new processes from module transactions.
  • πŸ§ͺ Dry Runs – Simulate message execution without committing.
  • πŸ“Š Compute Queries – Fetch process state via HTTP compute endpoints.
  • πŸ” Wait for Results – Poll until a message result is available.
  • πŸ” Hybrid Encryption – RSA‑OAEP + AES‑GCM for secure payloads.
  • 🧾 Arweave Wallet Support – Sign using JWK files or private key hex.

πŸ“¦ Installation

Add this to your Cargo.toml:

toml [dependencies] rustao = { git = "https://github.com/kimtony123/rustao" } tokio = { version = "1", features = ["full"] } serde_json = "1.0" Or using cargo add (if published):

bash cargo add rustao πŸš€ Quick Start rust use rustao::{Client, ARSigner}; use rustao::schema::Tag;

#[tokio::main] async fn main() -> Result<(), Box> { // Load your Arweave wallet (JWK file) let signer = ARSigner::from_file("wallet.json")?;

// Create a client (uses default gateways)
let client = Client::new().with_signer(signer);

// The process you want to interact with
let process_id = "6wqH8ue2-bnJG7j--FV0KGYzSs53ObFDofDITb7qtxI";

// Send a simple message
let msg_id = client.send_message(
    process_id,
    b"{\"action\": \"hello\"}",
    vec![Tag::new("Action", "Message")],
    None, // no encryption options
).await?;
println!("Message sent: {}", msg_id);

// Wait for the result (max 30 seconds, poll every 2 seconds)
let result = client.wait_for_result(&msg_id, process_id, 30, 2).await?;
println!("Result: {}", String::from_utf8_lossy(&result.output));

// Query a compute endpoint
let counter = client.get_compute_string(process_id, "counter").await?;
println!("Counter: {}", counter);

Ok(())

}

🧠 Core Concepts

πŸ”‘ Signer

The ARSigner implements the Signer trait, which provides the cryptographic operations required by the client.

Create from JWK file: ARSigner::from_file("wallet.json")?

Create from private key hex: ARSigner::from_private_key_hex(hex)?

Sign: async fn sign(&self, data: &[u8]) -> Result<Vec>

Public key: fn public_key(&self) -> Vec (uncompressed)

Address: fn address(&self) -> String

🧩 Client

The Client is the main entry point. It holds the signer and the URLs for the MU (message uploader), CU (compute unit), SU (scheduler), and compute gateway. You can customise these using builder methods:

rust let client = Client::new() .with_signer(signer) .with_mu("https://custom-mu.example.com") .with_cu("https://custom-cu.example.com") .with_su("https://custom-su.example.com") .with_compute_gateway("https://custom-gateway.example.com");

πŸ“¨ Messages

Send a message to a process using send_message. Tags are key‑value pairs that provide metadata. The method returns the data item ID (the ID you use to retrieve the result).

rust let tags = vec![ Tag::new("Action", "Transfer"), Tag::new("Recipient", "some-address"), ]; let msg_id = client.send_message(process_id, b"1000", tags, None).await?; 🌱 Spawn a Process To create a new process from a module transaction ID:

rust let process_id = client.spawn_process( "module-tx-id", b"{"initial": "state"}", vec![Tag::new("App-Name", "MyApp")], ).await?;

πŸ§ͺ Dry Run

Simulate a message without committing it – useful for checking balances or evaluating expressions:

rust let result = client.dry_run( process_id, b"{"action": "Balance"}", vec![Tag::new("Action", "Balance")], None, ).await?; println!("Output: {}", String::from_utf8_lossy(&result.output));

πŸ“Š Compute Queries

AO processes expose state via HTTP compute endpoints. Three helpers are provided:

get_compute(process_id, path) β†’ Vec

get_compute_string(process_id, path) β†’ String

get_compute_json(process_id, path) β†’ serde_json::Value

rust let raw = client.get_compute(process_id, "counter").await?; let str = client.get_compute_string(process_id, "status").await?; let json = client.get_compute_json(process_id, "state").await?;

πŸ” Encryption

The SDK supports hybrid encryption (RSA‑OAEP + AES‑GCM) for message payloads. To send an encrypted message, provide a recipient's RSA public key via SendMessageOptions:

rust use rustao::encrypt::{SendMessageOptions, EncryptWithRSA};

let pub_key = ...; // RSA public key of the recipient process let opts = SendMessageOptions::new().encrypt_with_rsa(pub_key);

let msg_id = client.send_message(process_id, b"secret data", tags, Some(opts)).await?; The client automatically encrypts the data, attaches the required tags (Encrypted-Key, Nonce), and sends the encrypted payload. On the receiving side, you can decrypt the response using the DecryptResponse helper.

πŸ§ͺ Full Example

Here’s a complete example that interacts with a live process, adds a product, and fetches all apps:

rust use rustao::{Client, ARSigner}; use rustao::schema::Tag; use serde_json::json;

#[tokio::main] async fn main() -> Result<(), Box> { let signer = ARSigner::from_file("wallet.json")?; let client = Client::new().with_signer(signer);

let process_id = "6wqH8ue2-bnJG7j--FV0KGYzSs53ObFDofDITb7qtxI";

// 1. Add a product
let product = json!({
    "product_type": "Website DApp",
    "category": "Infrastructure",
    "name": "Aostore",
    "description": "Aostore serves as the Playstore...",
    "website_url": "https://aostore-orpin.vercel.app/",
    "logo_url": "https://pbs.twimg.com/profile_images/...",
    "blockchain": "Arweave",
    "referral_fee": 0.02
}).to_string();

let tags = vec![Tag::new("Action", "AddProduct")];
let msg_id = client.send_message(process_id, product.as_bytes(), tags, None).await?;
let result = client.wait_for_result(&msg_id, process_id, 30, 2).await?;
println!("AddProduct result: {}", String::from_utf8_lossy(&result.output));

// 2. Fetch all apps
let tags = vec![Tag::new("Action", "FetchAllApps")];
let msg_id = client.send_message(process_id, &[], tags, None).await?;
let result = client.wait_for_result(&msg_id, process_id, 30, 2).await?;
let apps: serde_json::Value = serde_json::from_slice(&result.output)?;
println!("Total apps: {}", apps.as_array().unwrap().len());

Ok(())

}

πŸ§‘β€πŸ’» Contributing

Contributions are welcome! Please open an issue or pull request. Make sure to:

Run cargo fmt to format code.

Run cargo test to ensure tests pass.

Add tests for new functionality.

License

This project is licensed under the MIT License. See the LICENSE file for details.

πŸ™ Acknowledgements

Built on top of arweave-rs for Arweave wallet operations.