Rust SDK
Installation, configuration, and full reference for the Rust SDK.
The Rust SDK (inndx-sdk) is async and runtime agnostic. It handles MPP payment automatically for both per-request and session endpoints.
Installation
The crate is not published to crates.io. Add it directly from git:
[dependencies]
inndx-sdk = { git = "https://github.com/inndx-io/rust-sdk" }Pin a specific tag or commit for reproducible builds:
inndx-sdk = { git = "https://github.com/inndx-io/rust-sdk", tag = "0.1.2" }
inndx-sdk = { git = "https://github.com/inndx-io/rust-sdk", rev = "<commit-sha>" }Configuration
use inndx_sdk::{Config, InndxClient, PrivateKeySigner};
let signer: PrivateKeySigner = std::env::var("WALLET_PRIVATE_KEY")?.parse()?;
let client = InndxClient::new(
Config::new().with_signer(signer),
)?;Config::new() returns a builder. Available methods:
| Method | Description |
|---|---|
.with_base_url(url) | Base URL of the inndx API. Defaults to https://api.inndx.io. |
.with_signer(signer) | Wallet used to sign payments. Required for paid calls and sessions. |
.with_rpc_url(url) | RPC endpoint for the Tempo chain. |
.with_header(key, value) | Adds a header to every outgoing request. |
BillingConfig can be constructed separately for more control:
| Method | Description |
|---|---|
.with_max_deposit(amount) | Default escrow cap per session in token base units. |
.with_escrow_contract(addr) | Escrow contract override. |
.with_accept_payment_origins(origins) | Origins allowed to receive payments. |
use inndx_sdk::{Config, BillingConfig, ClientConfig, InndxClient, PrivateKeySigner};
let client = InndxClient::new(Config {
client: ClientConfig::new(),
billing: BillingConfig::new()
.with_signer(signer)
.with_max_deposit(10_000_000_000_000_000_000),
})?;Per-call extras
Every resource method returns a Call builder. Before awaiting it, chain any of these to apply them to that one call only:
| Method | Effect |
|---|---|
.header(name, value) | Adds a header to the outgoing request. |
.timeout(duration) | Sets a per-call timeout. |
.with(|req| ...) | Escape hatch for the underlying reqwest_middleware::RequestBuilder. |
use std::time::Duration;
let markdown = client
.scrape()
.scrape_url_markdown(SessionOptions::default())?
.call("https://example.com")
.header("X-Scrape-Proxy", "isp")
.timeout(Duration::from_secs(10))
.await?;Scrape
client.scrape() returns a ScrapeClient. All Scrape API endpoints are session-based.
scrape_url_markdown
Fetches a URL and returns the page as a markdown string.
use inndx_sdk::SessionOptions;
let session = client
.scrape()
.scrape_url_markdown(SessionOptions::default())?;
let markdown: String = session.call("https://example.com", None).await?;
session.close().await?;Pass ScrapeUrlMarkdownParams to control proxy, timeout, or locale:
use inndx_sdk::types::scrape::{ScrapeProxy, ScrapeUrlMarkdownParams};
let markdown: String = session.call(
"https://example.com",
Some(&ScrapeUrlMarkdownParams {
proxy: Some(ScrapeProxy::Isp),
timeout_seconds: Some(30),
locale: Some("en-US".to_string()),
}),
).await?;scrape_url
Full control over output formats and options. Returns a ScrapeResponse with a url field and a results vec.
use inndx_sdk::types::scrape::{ScrapeRequest, ScrapeFormat, ScrapeProxy, ScrapeResponseResult};
let session = client
.scrape()
.scrape_url(SessionOptions::default())?;
let response = session.call(&ScrapeRequest {
url: "https://example.com".to_string(),
formats: Some(vec![ScrapeFormat::default_markdown(), ScrapeFormat::html()]),
proxy: Some(ScrapeProxy::Isp),
timeout_seconds: Some(30),
locale: Some("en-US".to_string()),
}).await?;
for result in response.results {
match result {
ScrapeResponseResult::Markdown { content } => println!("{content}"),
ScrapeResponseResult::Html { content } => println!("{content}"),
_ => {}
}
}
session.close().await?;ScrapeFormat constructors:
| Constructor | Description |
|---|---|
ScrapeFormat::default_markdown() | Page as markdown. |
ScrapeFormat::markdown(skip_tags) | Markdown with specified HTML tags removed. |
ScrapeFormat::html() | Raw HTML. |
Sessions
Opening and closing
let session = client
.scrape()
.scrape_url_markdown(SessionOptions::default())?;
let page1 = session.call("https://example.com").await?;
let page2 = session.call("https://example.com/about").await?;
println!("spent: {}", session.cumulative());
let receipt = session.close().await?;Scoped sessions
Use scope to guarantee the session closes even if your code returns an error:
let result = client
.scrape()
.scrape_url_markdown(SessionOptions::default())?
.scope(|session| async move {
session.call("https://example.com").await
})
.await?;Session options
| Field | Description |
|---|---|
max_deposit | Escrow cap for this session in token base units. |
escrow_contract | Escrow contract override for this session. |
Session members
| Member | Description |
|---|---|
.call(...) | Makes a request through the session. |
.close() | Settles the channel on-chain. Returns Ok(Some(receipt)) or Ok(None) if nothing was opened. |
.scope(f) | Runs f(session) then settles regardless of outcome. |
.cumulative() | Amount spent so far across all calls. |
.channel_id() | The on-chain channel id, or None if not yet opened. |
.opened() | Whether the channel has been opened on-chain. |
.escrow_contract() | The escrow contract this session targets. |
Reclaiming a stranded channel
If a process exits before close is called, the escrowed deposit stays on-chain. Persist the channel id as soon as the channel opens:
let page = session.call("https://example.com").await?;
if let Some(channel_id) = session.channel_id() {
persist_to_disk(channel_id);
}Forced close is a two-step sequence with a roughly 15-minute grace period between steps:
use inndx_sdk::{B256, ReclaimOptions};
let channel_id: B256 = "0x...".parse()?;
let reclaim = client.reclaim_session(channel_id, ReclaimOptions::default())?;
// Step 1: start the forced-close timer on-chain.
reclaim.request_close().await?;
// Step 2: after the grace period, withdraw.
let state = reclaim.get_state().await?;
if state.ready {
reclaim.withdraw().await?;
}The two steps do not need to run in the same process. Both request_close and withdraw are idempotent.
ReclaimOptions fields:
| Field | Description |
|---|---|
chain_id | Chain to target. Defaults to Tempo mainnet. |
escrow_contract | Escrow contract override. |
get_state() returns a ReclaimChannelState:
| Field | Type | Description |
|---|---|---|
finalized | bool | Whether the channel has been withdrawn on-chain. |
close_requested | bool | Whether request_close has been called. |
ready | bool | Whether withdraw is expected to succeed right now. |
ready_at | Option<u64> | Unix timestamp when withdraw becomes callable. |
deposit | u128 | Total deposited amount. |
settled | u128 | Amount already settled to the payee. |
refundable | u128 | Amount returned to your wallet once finalized. |
Error handling
All methods return Result<T, inndx_sdk::errors::Error>.
use inndx_sdk::errors::{Error, ErrorBody};
match session.call("https://example.com").await {
Ok(markdown) => println!("{markdown}"),
Err(Error::Api { status, body: ErrorBody::Validation { fields, .. } }) => {
for field in fields {
eprintln!("{}: {:?}", field.field, field.errors);
}
}
Err(Error::Api { status, body: ErrorBody::Generic { message, .. } }) => {
eprintln!("API error {status}: {message}");
}
Err(Error::ChannelNotReady { ready_at, .. }) => {
eprintln!("grace period not elapsed; withdraw after unix timestamp {ready_at}");
}
Err(e) => eprintln!("{e}"),
}Error variants:
| Variant | When |
|---|---|
Error::Api { status, body } | The server returned a non-2xx response. |
Error::Payment(MppError) | A payment or signing operation failed. |
Error::ChannelNotReady { channel_id, ready_at } | withdraw called before the grace period elapsed. |
Error::Request | A transport-level error. |
Error::Deserialize | Response body could not be deserialized. |
Error::SessionClosed | The session was closed before a response was received. |
Error::UnexpectedResponse | The server returned an unexpected response type. |