SDKs
The TypeScript, Rust and Python SDKs for Tenzro Network, hardware device keys, and how signing and credentials work.
Tenzro publishes three first-party SDKs with the same shape: tenzro-sdk for TypeScript, tenzro-sdk for Rust and tenzro for Python. Each wraps the JSON-RPC surface of a node with typed clients per area (wallet, identity, inference, payments, settlement, storage, agents and more), and each includes a gateway that can call any method the node serves.
All SDKs are open source under Apache 2.0.
Install
What a registry serves today differs from the source, and this page documents the source (version 0.9.0 of the TypeScript and Rust SDKs, 0.2.0 of the Python SDK, in sdk/ of tenzro-network). A call added after the published release is only in the source build.
# TypeScript (Node 18+, browsers, edge runtimes): published, latest 0.7.0
npm install tenzro-sdk
# Rust: published, latest 0.7.0
cargo add tenzro-sdk
# Python (3.10+): not published to PyPI yet; build from the repository
git clone https://github.com/tenzro/tenzro-network.git
pip install ./tenzro-network/sdk/tenzro-py| Package | Where | Published | In the repository |
|---|---|---|---|
tenzro-sdk (TypeScript) | npm | 0.7.0 | 0.9.0 |
@tenzro/device-key (TPM / Secure Enclave keys for Node.js) | npm | 0.1.1 | in the repository |
@tenzro/inject (injected window.tenzro provider) | npm | not published | sdk/tenzro-inject |
tenzro-sdk (Rust) | crates.io | 0.7.0 | 0.9.0 |
tenzro (Python) | PyPI | not published | 0.2.0 (sdk/tenzro-py) |
tenzro-device-key (Python) | PyPI | not published | in the repository |
Signing needs hardware: on a server or desktop the SDKs sign with the machine's TPM 2.0 or Secure Enclave through @tenzro/device-key (TypeScript) or tenzro-device-key (Python); in a browser they use a passkey. Without one they refuse to sign. Every method the SDKs bind is documented with its params, result and errors in the generated JSON-RPC reference.
Connect
The TypeScript client takes an endpoint for JSON-RPC and an apiEndpoint for the Web API (health and status).
import { TenzroClient } from "tenzro-sdk";
const client = new TenzroClient({
endpoint: "https://rpc.tenzro.com",
apiEndpoint: "https://api.tenzro.com",
});
const height = await client.getBlockNumber();
const finalized = await client.getFinalizedBlock();
const balance = await client.getBalance("0xYourAddress"); // bigint, in weiIf you leave out apiEndpoint, the client derives it from endpoint for the public endpoints and for a local node (localhost:8545 maps to localhost:8080).
The Rust client is built from an SdkConfig:
use tenzro_sdk::{TenzroClient, config::SdkConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = SdkConfig::builder()
.endpoint("https://rpc.tenzro.com")
.timeout(30_000)
.max_retries(3)
.build()?;
let client = TenzroClient::connect(config).await?;
println!("height: {}", client.block_number().await?);
let models = client.inference().list_models().await?;
println!("{} models", models.len());
Ok(())
}TenzroClient::new("https://rpc.tenzro.com") is the one-line form with default settings.
Run inference
List models and send a chat request through the provider client:
const models = await client.inference.listModels();
const reply = await client.provider.chat("<model id>", [
{ role: "user", content: "Summarize the latest block in one sentence." },
]);use tenzro_sdk::provider::ChatMessage;
let reply = client
.provider()
.chat("<model id>", vec![ChatMessage {
role: "user".into(),
content: "Summarize the latest block in one sentence.".into(),
..Default::default()
}])
.await?;If you do not want to pick a model yourself, client.inference.routeIntent(...) resolves a use case and budget to a model, and chatByIntent(...) runs the request in one call.
The node also serves OpenAI-compatible routes (/v1/chat/completions, /v1/responses, /v1/embeddings, /v1/audio/transcriptions, /v1/audio/speech and more), so any OpenAI client library works when you point its base URL at https://rpc.tenzro.com/v1. See OpenAI-compatible APIs.
Signing and credentials
Tenzro Network is closed by default: anything that moves money or changes state needs a signature from the account that pays. The SDKs never hold your keys. You pass in a signer backed by a passkey, a TPM 2.0 or a Secure Enclave key, and the SDK builds the canonical transaction hash, asks the signer for the composite hybrid signature (classical plus ML-DSA-65) and submits the signed transaction with eth_sendRawTransaction.
// signer: a HybridSigner backed by your passkey or device key
const txHash = await client.wallet.sendSelfCustody({
signer,
to: "0xRecipient",
value: 1_000_000_000_000_000_000n, // 1 TNZO in wei
});// signer: Arc<dyn HybridSigner> backed by your device key
let tx_hash = client
.wallet()
.send_self_custody(&signer, recipient, 1_000_000_000_000_000_000u128)
.await?;In the browser, a passkey enrols with client.passkeyRpc.enroll and signs transactions from its account as a HybridSigner whose P-256 leg is its WebAuthn assertion (user verification required). See Hardware-rooted keys and Console and passkey wallet.
Other credentials are read from the environment in server runtimes, so they stay out of your code:
| Variable | Header sent | Used for |
|---|---|---|
TENZRO_BEARER_JWT and TENZRO_DPOP_PROOF | Authorization: DPoP ... and DPoP | OAuth 2.1 sessions bound to a DPoP key |
TENZRO_ADMIN_TOKEN | X-Tenzro-Admin-Token | Operator-only methods on your own node |
AuthClient (client.auth in TypeScript, client.auth() in Rust) handles onboarding for humans, delegated agents and autonomous agents and returns DPoP-bound tokens. HardwareDpopProver mints the DPoP proofs with the machine's hardware P-256 key, and jwkThumbprint computes the key's thumbprint. See RPC access.
The ML-DSA-65 leg is mandatory for account transactions. An account signs nothing until it has enrolled its ML-DSA-65 key, and a new account's first transaction is the enrollment. client.accountKeys().sendEnrolling in the TypeScript SDK, and send_enrolling in the Rust and Python SDKs, send a transaction and, if the node answers that no key is enrolled, enrol the hardware's key and send it again. The CLI equivalent is tenzro pq-hybrid enroll. See Cryptography.
Call any method
Typed clients cover the common paths. Everything else is reachable through the gateway, which reads the method list from the node itself, so a newer node simply reports more:
const dir = await client.gateway.methods({ contains: "forecast" });
for (const m of dir.methods) console.log(m.method, m.gate, m.class);
const result = await client.gateway.call("tenzro_getSupplyMetrics", {});let dir = client.gateway().methods(None, Some("forecast")).await?;
let result = client.gateway().call("tenzro_getSupplyMetrics", serde_json::json!({})).await?;The gateway runs through the same access checks as a direct JSON-RPC call. It reaches exactly what your credentials allow.
Choosing
- TypeScript for web and desktop apps, agents, serverless and edge functions.
- Rust for daemons, high-volume services and anything that embeds a node.
- The CLI for scripts and operator tasks.
For every client and method, see the SDK reference.