Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hypertekorg/hyperstack/llms.txt

Use this file to discover all available pages before exploring further.

The Rust SDK provides a high-performance client for connecting to Hyperstack servers. Built on Tokio and tokio-tungstenite, it offers async streaming with zero-cost abstractions and compile-time type safety.

Installation

Add to your Cargo.toml:
[dependencies]
hyperstack-sdk = "0.5"

TLS Options

By default, the SDK uses rustls for TLS. You can switch to native TLS:
[dependencies]
hyperstack-sdk = { version = "0.5", default-features = false, features = ["native-tls"] }

Requirements

  • Rust 1.70+
  • Tokio runtime
  • A generated SDK crate from the Hyperstack CLI

Quick Start

Basic Connection

use hyperstack_sdk::prelude::*;
use my_stack::{PumpfunToken, PumpfunTokenEntity};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let hs = HyperStack::connect("wss://mainnet.hyperstack.xyz").await?;
    
    // List all entities
    let tokens = hs.list::<PumpfunTokenEntity>().await;
    println!("Found {} tokens", tokens.len());
    
    Ok(())
}

Stream Updates

Streams are lazy - calling watch() returns immediately without subscribing. The subscription happens automatically on first poll:
use hyperstack_sdk::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let hs = HyperStack::connect("wss://mainnet.hyperstack.xyz").await?;
    
    let mut stream = hs.watch::<PumpfunTokenEntity>();
    while let Some(update) = stream.next().await {
        match update {
            Update::Upsert { key, data } => println!("Updated {}", key),
            Update::Patch { key, data } => println!("Patched {}", key),
            Update::Delete { key } => println!("Deleted {}", key),
        }
    }
    
    Ok(())
}
The prelude module re-exports all commonly needed types including StreamExt, so you don’t need separate imports from futures_util.

API Reference

HyperStack Client

Simple Connection

let hs = HyperStack::connect("wss://example.com").await?;

Advanced Configuration

use std::time::Duration;

let hs = HyperStack::builder()
    .url("wss://example.com")
    .auto_reconnect(true)
    .max_reconnect_attempts(10)
    .ping_interval(Duration::from_secs(30))
    .initial_data_timeout(Duration::from_secs(5))
    .connect()
    .await?;

Core Methods

get
async fn get<E: Entity>(key: &str) -> Option<E::Data>
Get a single entity by key.
let token = hs.get::<PumpfunTokenEntity>("token-123").await;
list
async fn list<E: Entity>() -> Vec<E::Data>
Get all entities of type E.
let tokens = hs.list::<PumpfunTokenEntity>().await;
println!("Found {} tokens", tokens.len());
watch
fn watch<E: Entity>() -> EntityStream<E::Data>
Stream all updates (lazy - no .await needed).
let mut stream = hs.watch::<PumpfunTokenEntity>();
while let Some(update) = stream.next().await {
    // Handle update
}
watch_key
fn watch_key<E: Entity>(key: &str) -> EntityStream<E::Data>
Stream updates for a specific key (lazy).
let mut stream = hs.watch_key::<PumpfunTokenEntity>("token-123");
while let Some(update) = stream.next().await {
    println!("Token 123 updated: {:?}", update.data());
}
watch_keys
fn watch_keys<E: Entity>(keys: &[&str]) -> EntityStream<E::Data>
Stream updates for multiple keys (lazy).
let mut stream = hs.watch_keys::<PumpfunTokenEntity>(&["token-1", "token-2"]);
watch_rich
fn watch_rich<E: Entity>() -> RichEntityStream<E::Data>
Stream with before/after values (lazy).
let mut stream = hs.watch_rich::<PumpfunTokenEntity>();
while let Some(update) = stream.next().await {
    match update {
        RichUpdate::Updated { before, after, .. } => {
            println!("Changed from {:?} to {:?}", before, after);
        }
        _ => {}
    }
}
watch_key_rich
fn watch_key_rich<E: Entity>(key: &str) -> RichEntityStream<E::Data>
Rich stream for specific key (lazy).
let mut stream = hs.watch_key_rich::<PumpfunTokenEntity>("token-123");
connection_state
async fn connection_state() -> ConnectionState
Get current connection state.
let state = hs.connection_state().await;
match state {
    ConnectionState::Connected => println!("Connected"),
    ConnectionState::Connecting => println!("Connecting..."),
    ConnectionState::Reconnecting { attempt } => println!("Reconnecting (attempt {})", attempt),
    ConnectionState::Disconnected => println!("Disconnected"),
    ConnectionState::Error => println!("Error"),
}
disconnect
async fn disconnect()
Close the connection.
hs.disconnect().await?;

Update Types

Update type

Standard updates from watch():
pub enum Update<T> {
    Upsert { key: String, data: T },  // Full entity update
    Patch { key: String, data: T },   // Partial update (merged)
    Delete { key: String },           // Entity removed
}
Helper methods:
  • key() - Get the entity key
  • data() - Get data reference (if not Delete)
  • is_delete() - Check if delete variant
  • has_data() - Check if has data (not Delete)
  • into_data() - Consume and get data
  • into_key() - Consume and get key
  • map(f) - Transform data

RichUpdate type

Rich updates from watch_rich() with before/after diffs:
pub enum RichUpdate<T> {
    Created { key: String, data: T },
    Updated { key: String, before: T, after: T, patch: Option<Value> },
    Deleted { key: String, last_known: Option<T> },
}
The Updated variant includes patch - the raw JSON of changed fields:
if update.has_patch_field("trading") {
    // The trading field was modified
}

Stream Operators

All streams support chainable operators:
use std::collections::HashSet;

let watchlist: HashSet<String> = /* tokens to watch */;

let mut price_alerts = hs
    .watch_rich::<PumpfunTokenEntity>()
    .filter(move |u| watchlist.contains(u.key()))
    .filter_map(|update| match update {
        RichUpdate::Updated { before, after, .. } => {
            let prev = before.trading.last_trade_price.flatten().unwrap_or(0.0);
            let curr = after.trading.last_trade_price.flatten().unwrap_or(0.0);
            if prev > 0.0 {
                let pct = (curr - prev) / prev * 100.0;
                if pct.abs() > 0.1 {
                    return Some((after.info.name.clone(), pct));
                }
            }
            None
        }
        _ => None,
    });

while let Some((name, pct)) = price_alerts.next().await {
    println!("[PRICE] {:?} changed by {:.2}%", name, pct);
}

Available Operators

OperatorDescription
.filter(predicate)Keep only updates matching the predicate
.filter_map(f)Filter and transform in one step
.map(f)Transform each update
All operators are chainable and return streams that support the same operators.

Views API

The Views API provides a unified interface for accessing state, list, and derived views:
use hyperstack_sdk::prelude::*;
use my_stack::{OreRound, OreRoundViews};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let hs = HyperStack::connect("wss://mainnet.hyperstack.xyz").await?;
    
    // Get a views accessor
    let views = hs.views::<OreRoundViews>();
    
    // Access derived view (e.g., "latest" round)
    let latest = views.latest().get().await;
    println!("Latest round: {:?}", latest);
    
    // Access list view
    let all_rounds = views.list().get().await;
    println!("Found {} rounds", all_rounds.len());
    
    // Access state view by key
    let specific = views.state().get("round_key").await;
    
    // Watch derived view for updates
    let mut stream = views.latest().watch();
    while let Some(update) = stream.next().await {
        println!("Latest round updated: {:?}", update);
    }
    
    Ok(())
}

View Types

| View Type | Access Pattern | Returns | |-----------|---------------|---------|| | State | views.state().get(key) | Option<T> | | List | views.list().get() | Vec<T> | | Derived Single | views.{name}().get() | Option<T> | | Derived Collection | views.{name}().get() | Vec<T> | All view types support .watch() for streaming updates.

Understanding Option<Option<T>> Fields

Generated entity types often have fields typed as Option<Option<T>>. This represents the patch semantics of Hyperstack updates: | Value | Meaning | |-------|---------|| | None | Field was not included in this update (no change) | | Some(None) | Field was explicitly set to null | | Some(Some(value)) | Field has a concrete value | This distinction matters for partial updates (patches). When the server sends a patch, only changed fields are included.

Working with nested options

// Access a nested optional field
let price = token.trading.last_trade_price.flatten().unwrap_or(0.0);

// Check if field was explicitly set (vs absent from patch)
match &token.reserves.current_price_sol {
    None => println!("Price not in this update"),
    Some(None) => println!("Price explicitly cleared"),
    Some(Some(price)) => println!("Price: {}", price),
}

// Compare values in before/after
if before.trading.last_trade_price != after.trading.last_trade_price {
    println!("Price changed!");
}

Generating a Rust SDK

Use the Hyperstack CLI to generate a typed Rust SDK from your spec:
# Generate SDK crate
hs sdk create rust settlement-game

# With custom output directory
hs sdk create rust settlement-game --output ./crates/game-sdk

# With custom crate name
hs sdk create rust settlement-game --crate-name game-sdk
This generates a crate with:
generated/settlement-game-stack/
├── Cargo.toml
└── src/
    ├── lib.rs      # Re-exports
    ├── types.rs    # Data structs
    └── entity.rs   # Entity trait implementations
Add the generated crate to your Cargo.toml:
[dependencies]
hyperstack-sdk = "0.5"
settlement-game-stack = { path = "./generated/settlement-game-stack" }

Connection Management

Auto-Reconnection

The SDK automatically reconnects on connection loss with configurable backoff:
use std::time::Duration;

let hs = HyperStack::builder()
    .url("wss://example.com")
    .auto_reconnect(true)
    .reconnect_intervals(vec![
        Duration::from_secs(1),
        Duration::from_secs(2),
        Duration::from_secs(5),
        Duration::from_secs(10),
    ])
    .max_reconnect_attempts(20)
    .connect()
    .await?;

Monitoring Connection State

let state = hs.connection_state().await;
match state {
    ConnectionState::Connected => println!("Ready"),
    ConnectionState::Connecting => println!("Establishing connection..."),
    ConnectionState::Reconnecting { attempt } => {
        println!("Reconnecting (attempt {})...", attempt)
    }
    ConnectionState::Disconnected => println!("Not connected"),
    ConnectionState::Error => println!("Connection error"),
}

Examples

Price Alert System

use hyperstack_sdk::prelude::*;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let hs = HyperStack::connect("wss://mainnet.hyperstack.xyz").await?;
    
    let mut stream = hs.watch_rich::<TokenEntity>()
        .filter_map(|update| match update {
            RichUpdate::Updated { before, after, key, .. } => {
                let prev = before.price.unwrap_or(0.0);
                let curr = after.price.unwrap_or(0.0);
                let change_pct = ((curr - prev) / prev) * 100.0;
                
                if change_pct.abs() > 5.0 {
                    Some((key, change_pct))
                } else {
                    None
                }
            }
            _ => None,
        });
    
    while let Some((token, change)) = stream.next().await {
        println!("Alert: {} changed by {:.2}%", token, change);
    }
    
    Ok(())
}

Game Leaderboard

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let hs = HyperStack::connect("wss://mainnet.hyperstack.xyz").await?;
    
    // Get initial leaderboard
    let mut players = hs.list::<PlayerEntity>().await;
    players.sort_by(|a, b| b.score.cmp(&a.score));
    
    println!("Top 10 Players:");
    for (i, player) in players.iter().take(10).enumerate() {
        println!("{}. {} - {} points", i + 1, player.name, player.score);
    }
    
    // Watch for score changes
    let mut stream = hs.watch_rich::<PlayerEntity>();
    while let Some(update) = stream.next().await {
        if let RichUpdate::Updated { before, after, .. } = update {
            let diff = after.score - before.score;
            if diff > 0 {
                println!("{} scored {} points!", after.name, diff);
            }
        }
    }
    
    Ok(())
}

Next Steps

CLI Reference

Generate Rust SDKs with the CLI

TypeScript SDK

Explore the TypeScript SDK

Stack API

Learn about Stack API concepts

GitHub

View source and examples

Build docs developers (and LLMs) love