Skip to content

Arete documentation

Rust SDK

For the complete documentation index optimized for AI agents, see llms.txt or llms-full.txt. A markdown version of this page is available by appending .md to the URL or sending Accept: text/markdown.

For AI agents: the documentation index is at llms.txt (full corpus: llms-full.txt). A markdown source for this page is /sdks/rust.md.

The arete-a4-sdk crate is the Rust client for generated Program SDKs and stacks. It mirrors the core TypeScript model with native Rust idioms: generated structs, builder chains, and Stream implementations.

Use a4 install protocol <package> --rust for a package whose descriptor verifies Rust. Its generated README gives the crate import, typed steps, rejection codes, and client construction. Protocol clients support instances, participants, wallet actions, metadata, event streams, submission tracking, and execution receipts.

actions(ProtocolWalletActionsRequest::new(wallet)) discovers the wallet’s create steps and related instances, one page at a time. instances_for_wallet(wallet, Some("creator")) reads every page and returns the instances the wallet created (Some("role:<name>") for a role it holds, None for every related instance). Handle the typed prepare result, sign the returned transactions locally without rebuilding messages, submit, and follow the execution id. Receipt verification uses verify_receipt and verify_receipt_on_chain.

Protocol integration · Execution lifecycle · Protocol reference

[dependencies]
arete-sdk = { package = "arete-a4-sdk", version = "0.51.2" }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
futures = "0.3"
anyhow = "1"

The default TLS backend is rustls. To use native TLS:

arete-sdk = { package = "arete-a4-sdk", version = "0.51.2", default-features = false, features = ["native-tls"] }

Find a Rust-ready program or stack and install it into the project:

Terminal window
a4 explore catalog --query "<intent>" --target rust --json
a4 explore catalog stack <slug> --json
a4 install stack <slug> --rust

Use a4 install program <slug> --rust for a standalone Program SDK. Read the CLI output for the generated crate name and path; those depend on the project configuration and dependency alias.

use arete_sdk::prelude::*;
use my_stack::MyStack;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Arete::<MyStack>::builder()
.secret_key(std::env::var("ARETE_API_KEY")?)
.connect()
.await?;
let positions = client.views.position.list().get().await;
println!("{positions:?}");
client.disconnect().await;
Ok(())
}

MyStack, position, and the generated crate path are placeholders. Use the types and view accessors emitted for the installed package.

secret_key takes an agent key (a4_ak_...) or secret key (a4_sk_...) for servers, agents and local scripts. With no auth option set, connect() uses ARETE_API_KEY if it is set, and otherwise the key from your a4 login, so a program on a machine logged in with a4 needs no key setup. Anything shipped to a browser uses an origin-bound publishable key (publishable_key) instead, created with a4 auth keys create-publishable --origin <scheme://host[:port]>.

Every generated entity has its declared keyed and collection views. Derived views can add package-specific filters, sorting, and bounded windows.

listen() yields current entity values with patches applied and removals filtered out:

let mut positions = client.views.position.list()
.listen()
.take(10);
while let Some(position) = positions.next().await {
println!("{position:?}");
}

Use watch() when the application must distinguish a row changing, leaving one query, or being deleted globally:

let mut updates = client.views.position.list()
.watch()
.filter("state.status", "open")
.take(20)
.with_snapshot_limit(100);
while let Some(update) = updates.next().await {
match update {
Update::Upsert { key, data } => println!("upserted {key}: {data:?}"),
Update::Patch { key, data } => println!("patched {key}: {data:?}"),
Update::Remove { key } => println!("{key} left this query"),
Update::Delete { key } => println!("{key} was deleted globally"),
}
}

Use watch_rich() when the generated view supports before/after diffs.

Subscriptions are lazy and reference-counted. Equivalent normalized queries share a wire subscription and unsubscribe when the final stream is dropped.

Generated program modules expose pure instruction builders and PDA helpers. The output is an Arete BuiltInstruction; no wallet or network is required:

use my_program::{self, DepositParams};
let instruction = my_program::deposit(DepositParams {
owner: owner.clone(),
amount: 1_000_000,
..Default::default()
})?;
let (position, bump) = my_program::pdas::position(&owner)?;

Connected stack clients expose the same generated program under client.programs. Exact method and parameter names come from the generated crate.

PDA derivation fills supported literal, argument, and account-reference seeds. Unknown parameters and missing required values fail closed.

Generated program clients expose account readers tied to the exact installed release:

let position = client
.programs
.my_program
.position_accounts()?
.fetch(&position_address)
.await?;

Use the generated reader method rather than the placeholder above. Program Read is independent of the live view endpoint.

let clock = client.chain().clock().await?;
let balance = client
.chain()
.native_balance(&address, Default::default())
.await?;

Use the chain surface for generic Solana state and the generated program reader for typed program accounts.

The core SDK builds portable instructions but does not choose a Solana signing implementation. Attach a wallet adapter, then execute instructions or prepared operations:

let client = Arete::<MyStack>::builder()
.secret_key(secret_key)
.wallet(wallet)
.connect()
.await?;
let receipt = client
.transaction(&[instruction], TransactionOptions::default())
.await?;
let receipt = client
.execute(&prepared, ExecuteOptions::default())
.await?;

The wallet compiles and signs locally. Execution classifies outcomes as confirmed, definitely not submitted, submitted with unknown outcome, or failed on chain. Do not automatically resend a submitted-unknown transaction.

Enable the first-party adapter only when the application wants the SDK to build legacy, v0, or transaction-V1 messages:

arete-sdk = { package = "arete-a4-sdk", version = "0.51.2", features = ["solana-adapter"] }

The adapter submits a signed transaction at most once. One deadline covers submission and confirmation. Direct RPC is an explicit transport selection; there is no silent hosted-to-direct fallback after an operation begins.

Use a session when an application composes several generated stacks:

let session = Session::builder()
.stack::<MarketsStack>("markets")
.stack::<PositionsStack>("positions")
.wallet(wallet)
.connect()
.await?;
let positions = session.stack::<PositionsStack>("positions")?;
session.execute(&prepared, Default::default()).await?;
session.close().await;

Sessions share wallet and execution configuration while keeping each stack’s exact programs, views, and hosted bindings explicit.

HTTP-only transport keeps Program Read, chain reads, point-in-time stack queries, and transaction execution available. Live view subscriptions fail fast because they require WebSocket transport.

client.connection_state().await;
let mut issues = client.subscribe_socket_issues();
client.disconnect().await;

Reconnects replay active subscriptions and apply an authoritative snapshot to replace local state. Preserve socket and schema-validation diagnostics in application logs.