Skip to content

Overview

The Hyperstack Rust DSL (Domain Specific Language) is a declarative syntax for defining streaming data pipelines. Using procedural macros, you describe what data you want from Solana, not how to fetch it.


Instead of writing complex ETL pipelines with manual account parsing and event handling, the DSL lets you define:

  • Entities — Structured data objects that project on-chain state
  • Field Mappings — How data flows from Solana accounts into your entities
  • Aggregations — Computed metrics that update automatically
  • Resolvers — External data (like token metadata) enriched automatically
  • Strategies — How incoming data merges with existing state

The macros transform your Rust code into a JSON-based stack spec (.stack.json), which Hyperstack compiles into optimized bytecode for real-time execution.


#[hyperstack(idl = "my_program.json")]
pub mod my_stream {
#[entity]
pub struct Token {
// Map account fields directly
#[map(my_program_sdk::accounts::TokenAccount::balance, strategy = LastWrite)]
pub balance: u64,
// Aggregate events into metrics
#[aggregate(from = my_program_sdk::instructions::Trade, field = amount, strategy = Sum)]
pub total_volume: u64,
// Derive computed values
#[computed(balance * price)]
pub tvl: u64,
// Enrich with off-chain token metadata (name, symbol, decimals, logo)
#[resolve(address = "So11111111111111111111111111111111111111112")]
pub token_metadata: Option<TokenMetadata>,
}
}

MacroPurpose
#[hyperstack]Entry point — defines the stream and links to data sources (IDL/Protobuf)
MacroPurpose
#[entity]Marks a struct as a data projection
#[view]Defines queryable views (list, state) for the entity
MacroPurpose
#[map]Maps fields from Solana account state
#[from_instruction]Extracts data from instruction arguments
#[aggregate]Computes running values (Sum, Count, etc.)
#[event]Captures instructions as structured events
#[snapshot]Captures complete account state
#[computed]Derives values from other entity fields
#[derive_from]Populates from instruction metadata
#[resolve]Fetches off-chain data via a named resolver

Resolvers are types that fetch external data server-side and deliver it as part of your entity. You attach them to fields using #[resolve].

ResolverPurpose
TokenMetadataFetches SPL token metadata (name, symbol, decimals, logo) for a mint address via the DAS API. Also exposes ui_amount and raw_amount helper methods for use in #[computed] transforms.

For example, the ORE stack resolves token metadata for the ORE mint at a known address:

#[resolve(address = "oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp")]
pub ore_metadata: Option<TokenMetadata>,
// The resolved decimals can then be used in transforms:
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite,
transform = ui_amount(ore_metadata.decimals))]
pub motherlode: Option<f64>,

See Resolvers for the full reference.

SyntaxPurpose
lookup_index(register_from = [...])Inline PDA resolution on #[map] fields (preferred)
#[resolve_key]Advanced: custom primary key resolution
#[register_pda]Advanced: manual PDA mapping registration

When data arrives, strategies determine how it’s merged with existing state:

StrategyBehaviorExample Use
LastWriteOverwrite with latestCurrent balances
SetOnceWrite only if emptyIDs, creation timestamps
SumAdd to existing totalVolume, TVL
CountIncrement by 1Trade count
AppendAdd to listEvent history
Max / MinKeep extreme valuePrice highs/lows

  • Macro Reference — Complete documentation of every macro and its arguments
  • Population Strategies — Deep dive into update strategies and when to use each
  • Resolvers — Enrich entities with token metadata and computed fields