Hanzo ZT

Rust SDK

Complete documentation for hanzo-zt, the Rust zero-trust networking SDK with ZAP transport integration.

Rust SDK

The Rust SDK (hanzo-zt) is the reference implementation of Hanzo ZT. It provides the full API surface including ZAP transport integration, async I/O via Tokio, and the zt:// URL scheme.

Repository: github.com/hanzoai/dev (crate at hanzo-dev/zt/)

Installation

Add to your Cargo.toml:

[dependencies]
hanzo-zt = "0.1"
tokio = { version = "1", features = ["full"] }

Features

FeatureDefaultDescription
zapYesZAP transport integration (ZtTransport, zt:// scheme)
tunnelNoTunnel integration for relay routing
# Disable ZAP, use raw connections only
hanzo-zt = { version = "0.1", default-features = false }

# Enable tunnel support
hanzo-zt = { version = "0.1", features = ["tunnel"] }

Core Types

ZtContext

The main entry point. Manages authentication, service discovery, and connections.

use hanzo_zt::{ZtContext, ConfigBuilder, HanzoJwtCredentials};

let creds = HanzoJwtCredentials::resolve()?;
let config = ConfigBuilder::new()
    .controller_url("https://zt-api.hanzo.ai")
    .credentials(creds)
    .billing(true)
    .build()?;

let ctx = ZtContext::new(config).await?;

Methods:

MethodDescription
new(config)Create a new context with the given configuration
authenticate()Authenticate with the ZT controller using configured credentials
dial(service)Dial a service by name, returns a ZtConnection
listen(service)Bind to a service, returns a ZtListener
services()List all services available to the current identity
events()Subscribe to lifecycle events (returns broadcast receiver)
session()Get the current API session info

Config and ConfigBuilder

Builder pattern for ZT configuration:

use hanzo_zt::{ConfigBuilder, HanzoJwtCredentials};
use std::time::Duration;

let config = ConfigBuilder::new()
    .controller_url("https://zt-api.hanzo.ai")
    .credentials(HanzoJwtCredentials::resolve()?)
    .billing(true)
    .commerce_url("https://api.hanzo.ai/commerce")
    .connect_timeout(Duration::from_secs(30))
    .request_timeout(Duration::from_secs(15))
    .build()?;

Config fields:

FieldTypeDefaultDescription
controller_urlStringRequiredZT controller URL
credentialsArc(dyn Credentials)RequiredAuth credentials
billing_enabledbooltrueEnable billing checks
commerce_urlStringhttps://api.hanzo.ai/commerceCommerce API URL
identity_fileOption(PathBuf)NonePath to identity file (cert-based auth)
connect_timeoutDuration30sConnection timeout
request_timeoutDuration15sController API request timeout

HanzoJwtCredentials

Authenticates using a JWT from Hanzo IAM:

use hanzo_zt::HanzoJwtCredentials;

// Resolve from environment (HANZO_API_KEY) or auth file (~/.hanzo/auth.json)
let creds = HanzoJwtCredentials::resolve()?;

// From an explicit token
let creds = HanzoJwtCredentials::from_token("eyJhbGciOiJSUzI1NiIs...");

// Display (masked)
println!("{}", creds.display());
// "Hanzo IAM ([email protected])" or "Hanzo API key (...xxxxx)"

The Credentials trait:

pub trait Credentials: Send + Sync + std::fmt::Debug {
    /// Returns the auth method identifier ("ext-jwt" or "password")
    fn auth_method(&self) -> &str;

    /// Returns the authentication payload for the controller
    fn auth_payload(&self) -> Result<serde_json::Value>;

    /// Human-readable display with masked secrets
    fn display(&self) -> String;
}

ApiKeyCredentials

Alternative authentication with a direct API token:

use hanzo_zt::ApiKeyCredentials;

let creds = ApiKeyCredentials::new("zt-controller-api-token");
assert_eq!(creds.auth_method(), "password");

ZtConnection

A bidirectional connection to a ZT service. Implements tokio::io::AsyncRead and tokio::io::AsyncWrite.

let conn = ctx.dial("my-service").await?;

// Message-based API
conn.send(b"hello").await?;
let response = conn.recv().await?;

// Stream-based API (AsyncRead/AsyncWrite)
use tokio::io::{AsyncReadExt, AsyncWriteExt};
conn.write_all(b"hello").await?;
let mut buf = vec![0u8; 1024];
let n = conn.read(&mut buf).await?;

// Metadata
println!("Service: {}", conn.service_name());
println!("Session: {}", conn.session_id());
println!("Connected: {}", conn.is_connected());

// Close
conn.close().await?;

ZtListener

Accepts incoming connections on a bound service:

let listener = ctx.listen("my-service").await?;

loop {
    let conn = listener.accept().await?;
    println!("Accepted connection on {}", listener.service_name());

    tokio::spawn(async move {
        let data = conn.recv().await.unwrap();
        conn.send(&data).await.unwrap(); // Echo
    });
}

BillingGuard

Enforces paid-only access:

use hanzo_zt::billing::{BillingGuard, UsageRecord};

let guard = BillingGuard::new("https://api.hanzo.ai/commerce", "jwt-token");

// Check balance (called automatically by ctx.dial())
guard.check_balance("my-service").await?;

// Record usage after session
guard.record_usage(&UsageRecord {
    service: "my-service".to_string(),
    session_id: "sess_abc".to_string(),
    bytes_sent: 4096,
    bytes_received: 8192,
    duration_ms: 5000,
}).await?;

ZtTransport (ZAP)

ZAP transport over the ZT fabric. Requires the zap feature (enabled by default).

use hanzo_zt::ZtTransport;

// Create from controller URL and service name
let transport = ZtTransport::new(
    "https://zt-api.hanzo.ai",
    "my-service",
).await?;

// Or from an existing context
use std::sync::Arc;
let ctx = Arc::new(ctx);
let transport = ZtTransport::from_context(ctx, "my-service").await?;

// Use with ZAP client
// The transport implements the hanzo_zap::transport::Transport trait
transport.send(b"zap-frame").await?;
let frame = transport.recv().await?;

// Check state
println!("Connected: {}", transport.is_connected());
println!("Local: {:?}", transport.local_addr());   // "zt://local/my-service"
println!("Peer: {:?}", transport.peer_addr());     // "zt://my-service"

// Close
transport.close().await?;

Error Handling

All fallible operations return hanzo_zt::Result, which is std::result::Result with ZtError.

use hanzo_zt::ZtError;

match ctx.dial("my-service").await {
    Ok(conn) => { /* use connection */ }
    Err(ZtError::AuthFailed(msg)) => {
        eprintln!("Auth failed: {msg}");
    }
    Err(ZtError::NotAuthenticated) => {
        eprintln!("Call authenticate() first");
    }
    Err(ZtError::ServiceNotFound(name)) => {
        eprintln!("Service '{name}' not found");
    }
    Err(ZtError::NoEdgeRouters(service)) => {
        eprintln!("No edge routers for '{service}'");
    }
    Err(ZtError::InsufficientBalance(msg)) => {
        eprintln!("Add credits: {msg}");
    }
    Err(ZtError::ConnectionClosed) => {
        eprintln!("Connection was closed");
    }
    Err(e) => {
        eprintln!("Other error: {e}");
    }
}

ZtError variants:

VariantDescription
AuthFailed(String)Authentication failed with reason
NotAuthenticatedNo active session (call authenticate() first)
Controller(String)Controller API returned an error
ServiceNotFound(String)Named service does not exist
NoEdgeRouters(String)No edge routers available for service
ConnectionFailed(String)Failed to establish connection
ConnectionClosedConnection was closed
InsufficientBalance(String)Billing balance is zero or negative
BillingError(String)Error contacting the Commerce API
InvalidConfig(String)Configuration validation failed
Identity(String)Identity-related error
Timeout(String)Operation timed out
Io(std::io::Error)I/O error
Http(reqwest::Error)HTTP client error
Json(serde_json::Error)JSON parse error
UrlParse(url::ParseError)URL parse error

Events

Subscribe to lifecycle events:

use hanzo_zt::ZtEvent;

let mut events = ctx.events();
tokio::spawn(async move {
    while let Ok(event) = events.recv().await {
        match event {
            ZtEvent::Authenticated { identity_id } => {
                println!("Authenticated as {identity_id}");
            }
            ZtEvent::SessionCreated { service, session_id } => {
                println!("Session {session_id} for {service}");
            }
            ZtEvent::BillingOk { service } => {
                println!("Billing OK for {service}");
            }
            _ => {}
        }
    }
});

Complete Example

End-to-end example: authenticate, list services, dial, send/receive, and record usage.

use hanzo_zt::{
    ZtContext, ConfigBuilder, HanzoJwtCredentials,
    billing::UsageRecord, ZtEvent,
};
use std::time::Instant;

#[tokio::main]
async fn main() -> hanzo_zt::Result<()> {
    // Setup
    let creds = HanzoJwtCredentials::resolve()?;
    let config = ConfigBuilder::new()
        .controller_url("https://zt-api.hanzo.ai")
        .credentials(creds)
        .billing(true)
        .build()?;

    let ctx = ZtContext::new(config).await?;

    // Subscribe to events
    let mut events = ctx.events();
    tokio::spawn(async move {
        while let Ok(ev) = events.recv().await {
            println!("[event] {:?}", ev);
        }
    });

    // Authenticate
    ctx.authenticate().await?;

    // List services
    let services = ctx.services().await?;
    for svc in &services {
        println!("Service: {} ({})", svc.name, svc.id);
    }

    // Dial
    let start = Instant::now();
    let mut conn = ctx.dial("echo-service").await?;

    // Send request
    let request = b"Hello, ZT!";
    conn.send(request).await?;

    // Receive response
    let response = conn.recv().await?;
    println!("Response: {}", String::from_utf8_lossy(&response));

    // Close and record usage
    let duration = start.elapsed();
    conn.close().await?;

    let guard = hanzo_zt::BillingGuard::new(
        "https://api.hanzo.ai/commerce",
        &std::env::var("HANZO_API_KEY").unwrap_or_default(),
    );
    guard.record_usage(&UsageRecord {
        service: "echo-service".to_string(),
        session_id: conn.session_id().to_string(),
        bytes_sent: request.len() as u64,
        bytes_received: response.len() as u64,
        duration_ms: duration.as_millis() as u64,
    }).await?;

    Ok(())
}

Testing

The crate includes 9 tests covering configuration, authentication, and connection:

cargo test -p hanzo-zt

Example test:

#[test]
fn test_config_builder_success() {
    let creds = HanzoJwtCredentials::from_token("test-token");
    let config = ConfigBuilder::new()
        .controller_url("https://zt-api.hanzo.ai")
        .credentials(creds)
        .billing(false)
        .build();
    assert!(config.is_ok());
    let config = config.expect("config should build");
    assert_eq!(config.controller_url, "https://zt-api.hanzo.ai");
    assert!(!config.billing_enabled);
}

#[test]
fn test_hanzo_jwt_from_token() {
    let creds = HanzoJwtCredentials::from_token("test-token-123");
    assert_eq!(creds.auth_method(), "ext-jwt");
    assert!(creds.display().contains("...n-123"));
}

Dependencies

CratePurpose
reqwestHTTP client for controller and billing APIs
tokioAsync runtime (sync, time, io-util, net)
serde / serde_jsonSerialization
tracingStructured logging
thiserrorError derive macro
chronoDateTime handling for session expiry
urlURL parsing
hanzo-zapZAP transport trait (optional, zap feature)

On this page