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.

Field mappings define how on-chain data flows into entity fields. This page covers all mapping types, population strategies, and transformations available in Hyperstack.

Overview

Mappings connect on-chain events to entity fields using attributes:
#[entity(name = "Token")]
pub struct Token {
    // Map from instruction argument
    #[map(CreateIx::mint)]
    pub mint: String,
    
    // Map from account field
    #[map(BondingCurve::virtual_sol_reserves)]
    pub sol_reserves: u64,
    
    // Aggregate from multiple events
    #[aggregate(from = BuyIx, field = amount, strategy = Sum)]
    pub total_volume: u64,
    
    // Capture event history
    #[event(from = TradeIx, strategy = Append)]
    pub trades: Vec<TradeEvent>,
}

Mapping Types

#[map] - Direct Field Mapping

Maps a field from an account or instruction directly to an entity field.

Syntax

#[map(Source::field, ...options)]
pub field_name: Type,

From Account Field

#[map(BondingCurve::virtual_sol_reserves)]
pub sol_reserves: u64,
Extracts virtual_sol_reserves from BondingCurve account updates.

From Instruction Argument

#[map(CreateIx::mint)]
pub mint: String,
Extracts mint argument from CreateIx instruction.

From Instruction Account

#[map(CreateIx::accounts::mint)]
pub mint: String,
Extracts mint from the accounts of CreateIx instruction.

Options

OptionDescriptionExample
primary_keyMark as entity primary key#[map(CreateIx::mint, primary_key)]
lookup_indexCreate reverse lookup#[map(Curve::mint, lookup_index)]
strategyPopulation strategy#[map(..., strategy = LastWrite)]
renameRename field in output#[map(..., rename = "customName")]
transformApply transformation#[map(..., transform = Base58Encode)]
conditionConditional update#[map(..., condition = "status == 1")]
whenUpdate only after instruction#[map(..., when = CompleteIx)]
emitInclude in mutations#[map(..., emit = false)]

#[from_instruction] - Instruction Source

Alias for #[map] when mapping from instructions (legacy syntax).
#[from_instruction(CreateIx::mint, primary_key)]
pub mint: String,
Equivalent to:
#[map(CreateIx::mint, primary_key)]
pub mint: String,

#[aggregate] - Computed Aggregations

Aggregate values from multiple events over time.

Syntax

#[aggregate(from = Source, field = source_field, strategy = Strategy)]
pub field_name: Type,

Sum Strategy

#[aggregate(from = BuyIx, field = amount, strategy = Sum)]
pub total_volume: u64,
Sums amount from all BuyIx events.

Count Strategy

#[aggregate(from = BuyIx, strategy = Count)]
pub trade_count: u64,
Counts number of BuyIx events.

UniqueCount Strategy

#[aggregate(from = TradeIx, field = user, strategy = UniqueCount)]
pub unique_traders: u64,
Counts unique values of user across all TradeIx events.

Min/Max Strategy

#[aggregate(from = TradeIx, field = price, strategy = Max)]
pub highest_price: u64,

#[aggregate(from = TradeIx, field = price, strategy = Min)]
pub lowest_price: u64,

Multiple Sources

// Sum volume from both buys and sells
#[aggregate(from = BuyIx, field = sol_amount, strategy = Sum)]
#[aggregate(from = SellIx, field = sol_amount, strategy = Sum)]
pub total_volume: u64,

#[event] - Capture Events

Capture entire instruction payloads as event history.

Syntax

#[event(from = Source, strategy = Append, ...options)]
pub events: Vec<EventType>,

Basic Event Capture

#[event(from = TradeIx, strategy = Append)]
pub trades: Vec<TradeEvent>,
Generated Event Type:
interface TradeEvent {
  timestamp: number;
  slot: number;
  signature: string;
  data: TradeIx;  // Full instruction data
}

With Field Selection

#[event(from = TradeIx, strategy = Append, capture = [user, amount, timestamp])]
pub trades: Vec<TradeEvent>,
Captures only selected fields from the instruction.

With Transformations

#[event(from = TradeIx, strategy = Append, transforms = { user: Base58Encode })]
pub trades: Vec<TradeEvent>,
Applies transformations to specific fields.

#[capture] - Account Snapshots

Capture full account state at a point in time.
#[capture(from = BondingCurve, strategy = LastWrite)]
pub curve_snapshot: Option<BondingCurveSnapshot>,
Generated Snapshot Type:
interface BondingCurveSnapshot {
  timestamp: number;
  slot: number;
  signature: string;
  account_address: string;
  data: BondingCurve;  // Full account data
}

#[computed] - Derived Fields

Compute values from other entity fields.

Syntax

#[computed(expression)]
pub field_name: Type,

Arithmetic

#[computed(sol_reserves / token_reserves)]
pub price: Option<f64>,

Field References

#[computed(trading.total_volume / trading.trade_count)]
pub avg_trade_size: Option<f64>,

Type Casts

#[computed((sol_reserves as f64) / (token_reserves as f64))]
pub price_f64: Option<f64>,

Unwrap with Default

#[computed(optional_field.unwrap_or(0))]
pub safe_value: u64,

Method Calls

#[computed(trades.len())]
pub trade_count_computed: usize,

Context Access

#[computed(__slot)]  // Current slot
pub last_update_slot: Option<u64>,

#[computed(__timestamp)]  // Current timestamp
pub last_update_time: i64,

#[resolve] - External Data Enrichment

Enrich entities with data from external sources (token metadata, URLs, etc.).

Token Resolver

#[resolve(Token, strategy = SetOnce, extracts = [
    (target = "metadata.name", source = "name"),
    (target = "metadata.symbol", source = "symbol"),
    (target = "metadata.decimals", source = "decimals"),
])]
pub mint: String,
Fetches token metadata from the mint address and extracts fields.

URL Resolver

#[resolve(Url(url_path = "info.uri"), strategy = SetOnce, extracts = [
    (target = "metadata.image", source = "image"),
    (target = "metadata.description", source = "description"),
])]
pub uri: String,
Fetches JSON from a URL and extracts fields.

Population Strategies

Strategies control how field values are updated when new events arrive.

SetOnce

Set the field once, never overwrite.
#[map(CreateIx::mint, strategy = SetOnce)]
pub mint: String,
Behavior:
  • First event sets the value
  • Subsequent events ignored
  • Use for immutable data (timestamps, creators, IDs)
Note: SetOnce is the default for primary_key fields.

LastWrite

Always use the latest value.
#[map(BondingCurve::virtual_sol_reserves, strategy = LastWrite)]
pub sol_reserves: u64,
Behavior:
  • Every event overwrites the current value
  • Use for current state (balances, status, timestamps)
Note: LastWrite is the default strategy for #[map] fields.

Append

Collect values into an array.
#[event(from = TradeIx, strategy = Append)]
pub trades: Vec<TradeEvent>,
Behavior:
  • Each event appends to the array
  • Array grows indefinitely (subject to max_array_length)
  • Mutations include only new items (delta transmission)
  • Use for event history
Capacity: Default max 100 items per array (configurable via StateTableConfig).

Sum

Running total of numeric values.
#[aggregate(from = BuyIx, field = amount, strategy = Sum)]
pub total_volume: u64,
Behavior:
  • Each event adds to the current total
  • total_volume += amount
  • Use for cumulative metrics (volume, fees, rewards)

Count

Count number of events.
#[aggregate(from = BuyIx, strategy = Count)]
pub trade_count: u64,
Behavior:
  • Each event increments by 1
  • trade_count += 1
  • Use for event counters

Min / Max

Track minimum or maximum value.
#[aggregate(from = TradeIx, field = price, strategy = Max)]
pub highest_price: u64,

#[aggregate(from = TradeIx, field = price, strategy = Min)]
pub lowest_price: u64,
Behavior:
  • Max: Update if new value is greater
  • Min: Update if new value is lesser
  • Use for extremes (high/low prices, peak volume)

UniqueCount

Count unique values.
#[aggregate(from = TradeIx, field = user, strategy = UniqueCount)]
pub unique_traders: u64,
Behavior:
  • Maintains an internal HashSet of values
  • Field stores the count of unique values
  • Use for unique participants, distinct items

Merge

Merge objects (experimental).
#[map(Account::config, strategy = Merge)]
pub config: ConfigObject,
Behavior:
  • Deep merge of object fields
  • New fields added, existing fields overwritten

Transformations

Transformations convert field values during extraction.

Base58Encode

Encode bytes as base58 string.
#[map(Account::mint, transform = Base58Encode)]
pub mint: String,  // Converts [u8; 32] → String

Base58Decode

Decode base58 string to bytes.
#[map(Instruction::mint_str, transform = Base58Decode)]
pub mint_bytes: [u8; 32],  // Converts String → [u8; 32]

HexEncode / HexDecode

Encode/decode hexadecimal strings.
#[map(Account::data, transform = HexEncode)]
pub data_hex: String,

#[map(Instruction::hex_data, transform = HexDecode)]
pub data_bytes: Vec<u8>,

ToString

Convert value to string.
#[map(Account::amount, transform = ToString)]
pub amount_str: String,  // "1000000"

ToNumber

Parse string to number.
#[map(Instruction::amount_str, transform = ToNumber)]
pub amount: u64,

Advanced Mapping Features

Conditional Updates

Update field only when a condition is true.
#[map(TradeIx::amount, condition = "trade_type == 0")]  // Only buys
pub buy_amount: u64,

#[map(TradeIx::amount, condition = "trade_type == 1")]  // Only sells
pub sell_amount: u64,
Supported Operators:
  • ==, != - Equality
  • >, >=, <, <= - Comparison
  • &&, || - Logical AND/OR

When Clause

Update field only after a specific instruction has been seen.
#[map(UpdateIx::score, when = CompleteIx)]
pub final_score: u64,
Behavior:
  • Field updates are deferred until CompleteIx arrives
  • Deferred operations applied when CompleteIx is processed
  • Use for multi-step workflows

Stop Clause

Stop updating field once a specific instruction is seen.
#[map(UpdateIx::score, stop = FinalizeIx)]
pub running_score: u64,
Behavior:
  • Field updates normally until FinalizeIx arrives
  • After FinalizeIx, updates ignored
  • Use for finalized/frozen values

Join On

Map fields from a different entity (cross-entity reference).
#[map(OtherEntity::field, join_on = "foreign_key")]
pub referenced_value: u64,

Lookup By

Resolve primary key via a specific lookup index.
#[map(TradeIx::bonding_curve, lookup_by = bonding_curve)]
pub curve_address: String,

Temporal Field

Create a time-based lookup index.
#[map(UpdateRoundIx::round_id, lookup_index, temporal_field = "timestamp")]
pub round_id: u64,
Events are associated with the round_id active at their timestamp.

Emit Control

Control whether a field is included in mutations.
#[map(Account::internal_state, emit = false)]
pub internal: u64,  // Not sent to clients
Useful for:
  • Internal bookkeeping
  • Computed field inputs
  • Reducing bandwidth

Mapping Patterns

Multi-Source Aggregation

Aggregate from multiple event types:
#[aggregate(from = BuyIx, field = sol_amount, strategy = Sum)]
#[aggregate(from = SellIx, field = sol_amount, strategy = Sum)]
pub total_volume: u64,

Conditional Aggregation

Aggregate with conditions:
// Count only large trades
#[aggregate(from = TradeIx, strategy = Count, condition = "amount > 1000000")]
pub large_trade_count: u64,

// Sum only buys
#[aggregate(from = TradeIx, field = amount, strategy = Sum, condition = "is_buy == true")]
pub buy_volume: u64,

Nested Field Access

Access nested fields from source:
#[map(Account::config.max_supply)]
pub max_supply: u64,

#[map(Instruction::params.fee_basis_points)]
pub fee_bps: u16,

Default Values

Provide fallback for missing fields:
#[map(Account::optional_value, default = 0)]
pub value: u64,

Examples

Token with Comprehensive Mappings

#[entity(name = "Token")]
#[derive(Stream)]
pub struct Token {
    // Identity
    #[map(CreateIx::mint, primary_key)]
    pub mint: String,
    
    #[map(BondingCurve::mint, lookup_index)]
    pub mint_lookup: String,
    
    // Metadata (enriched)
    #[resolve(Token, strategy = SetOnce, extracts = [
        (target = "metadata.name", source = "name"),
        (target = "metadata.symbol", source = "symbol"),
    ])]
    pub name: Option<String>,
    pub symbol: Option<String>,
    
    // Current state
    #[map(BondingCurve::virtual_sol_reserves, strategy = LastWrite)]
    pub sol_reserves: u64,
    
    #[map(BondingCurve::virtual_token_reserves, strategy = LastWrite)]
    pub token_reserves: u64,
    
    // Computed
    #[computed(sol_reserves / token_reserves)]
    pub price: Option<f64>,
    
    // Aggregations
    #[aggregate(from = BuyIx, field = sol_amount, strategy = Sum)]
    #[aggregate(from = SellIx, field = sol_amount, strategy = Sum)]
    pub total_volume: u64,
    
    #[aggregate(from = BuyIx, strategy = Count)]
    #[aggregate(from = SellIx, strategy = Count)]
    pub trade_count: u64,
    
    #[aggregate(from = BuyIx, field = user, strategy = UniqueCount)]
    #[aggregate(from = SellIx, field = user, strategy = UniqueCount)]
    pub unique_traders: u64,
    
    // History
    #[event(from = BuyIx, strategy = Append)]
    #[event(from = SellIx, strategy = Append)]
    pub trades: Vec<TradeEvent>,
    
    // Snapshots
    #[capture(from = BondingCurve, strategy = LastWrite)]
    pub latest_curve: Option<BondingCurveSnapshot>,
}

User Profile with Conditions

#[entity(name = "UserProfile")]
#[derive(Stream)]
pub struct UserProfile {
    #[map(TradeIx::user, primary_key)]
    pub user: String,
    
    // Separate buy/sell volume
    #[aggregate(from = TradeIx, field = amount, strategy = Sum, condition = "is_buy == true")]
    pub buy_volume: u64,
    
    #[aggregate(from = TradeIx, field = amount, strategy = Sum, condition = "is_buy == false")]
    pub sell_volume: u64,
    
    // Large trade count
    #[aggregate(from = TradeIx, strategy = Count, condition = "amount > 1000000")]
    pub whale_trades: u64,
    
    // Last trade timestamp
    #[map(TradeIx::timestamp, strategy = LastWrite)]
    pub last_trade_at: i64,
}

Next Steps

Streams

Learn about stream definitions

Entities

Understand entity structure

Computed Fields

Derive values from fields

Resolvers

Enrich with external data

Build docs developers (and LLMs) love