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.
Resolvers enrich your entities with data that doesn’t live on-chain. When you define a resolver on an entity field, Hyperstack automatically fetches the external data server-side and delivers it to your clients as part of the entity — no extra API calls needed.
The built-in TokenMetadata resolver enriches your entity with SPL token metadata (name, symbol, decimals, logo) for any mint address. Hyperstack resolves this automatically server-side when your entity includes a field typed as TokenMetadata:
use hyperstack::resolvers::TokenMetadata;
#[entity(name = "OreRound")]
pub struct OreRound {
pub id: RoundId,
pub state: RoundState,
#[resolve(address = "oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp")]
pub ore_metadata: Option<TokenMetadata>,
}
When a new OreRound entity is created, Hyperstack sees the TokenMetadata field, resolves the metadata server-side, and delivers it as part of the entity. By the time data reaches your TypeScript client, the field is already filled in:
for await (const round of hs.views.OreRound.latest.use()) {
console.log(round.ore_metadata?.name); // "Ore"
console.log(round.ore_metadata?.symbol); // "ORE"
console.log(round.ore_metadata?.decimals); // 11
console.log(round.ore_metadata?.logo_uri); // "https://..."
}
| Field | Type | Description | |
|---|
mint | string | The mint address (always present) | |
name | `string | null` | Token name from on-chain metadata |
symbol | `string | null` | Token ticker symbol |
decimals | `number | null` | Number of decimal places |
logo_uri | `string | null` | URL to the token’s logo image |
Generated TypeScript
The CLI generates both a TypeScript interface and a Zod schema for TokenMetadata in your stack SDK:
// Auto-generated in your stack SDK
export interface TokenMetadata {
mint: string;
name?: string | null;
symbol?: string | null;
decimals?: number | null;
logo_uri?: string | null;
}
export const TokenMetadataSchema = z.object({
mint: z.string(),
name: z.string().nullable().optional(),
symbol: z.string().nullable().optional(),
decimals: z.number().nullable().optional(),
logo_uri: z.string().nullable().optional(),
});
Resolvers provide data that you can reference in other field mappings. The TokenMetadata resolver’s decimals field is commonly used to convert raw token amounts to human-readable UI amounts:
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite,
transform = ui_amount(ore_metadata.decimals))]
pub motherlode: Option<f64>,
This automatically converts the raw motherlode value (e.g., 150000000000) to a UI amount (e.g., 1.5) using the token’s decimal places.
Real example from ORE stack:
#[derive(Debug, Clone, Serialize, Deserialize, Stream)]
pub struct RoundState {
#[resolve(address = "oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp")]
pub ore_metadata: Option<TokenMetadata>,
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite,
transform = ui_amount(ore_metadata.decimals))]
pub motherlode: Option<f64>,
#[map(ore_sdk::accounts::Round::total_deployed, strategy = LastWrite,
transform = ui_amount(9))] // SOL always has 9 decimals
pub total_deployed: Option<f64>,
}
Option B: Use computed fields
Store the raw value and derive the UI amount in a #[computed] field:
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite)]
pub motherlode_raw: u64,
#[computed(state.motherlode_raw.map(|v| v as f64 / 10f64.powi(ore_metadata.decimals.unwrap_or(0) as i32)))]
pub motherlode_ui: Option<f64>,
Resolver Computed Methods
Resolvers also provide computed methods — functions that derive new values from the resolved data. These are evaluated server-side and delivered to your client as regular entity fields.
The TokenMetadata resolver provides two computed methods:
| Method | Description | Example |
|---|
ui_amount | Converts raw token amount to human-readable UI amount | 1_000_000_000 with 9 decimals → 1.0 |
raw_amount | Converts human-readable UI amount to raw token amount | 1.0 with 9 decimals → 1_000_000_000 |
Example using computed method in transform:
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite,
transform = ui_amount(ore_metadata.decimals))]
pub motherlode: Option<f64>,
The ui_amount transform uses the resolver’s decimals field to perform the conversion.
Real Example: Converting Raw Amounts
From the ORE stack, converting multiple fields using resolver decimals:
#[derive(Debug, Clone, Serialize, Deserialize, Stream)]
pub struct TreasuryState {
#[map(ore_sdk::accounts::Treasury::motherlode, strategy = LastWrite,
transform = ui_amount(11))] // ORE has 11 decimals
pub motherlode: Option<f64>,
#[map(ore_sdk::accounts::Treasury::total_refined, strategy = LastWrite,
transform = ui_amount(11))]
pub total_refined: Option<f64>,
#[map(ore_sdk::accounts::Treasury::total_staked, strategy = LastWrite,
transform = ui_amount(11))]
pub total_staked: Option<f64>,
}
Using Resolver Fields in Computed Fields
You can reference resolver fields in #[computed] expressions:
#[derive(Debug, Clone, Serialize, Deserialize, Stream)]
pub struct RoundState {
#[resolve(address = "oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp")]
pub ore_metadata: Option<TokenMetadata>,
#[map(ore_sdk::accounts::Round::deployed, strategy = LastWrite)]
pub deployed_per_square: Option<Vec<u64>>,
// Convert each square's raw amount to UI amount
#[computed(state.deployed_per_square.map(|squares| {
squares.iter().map(|&raw| {
raw as f64 / 10f64.powi(ore_metadata.decimals.unwrap_or(0) as i32)
}).collect::<Vec<f64>>()
}))]
pub deployed_per_square_ui: Option<Vec<f64>>,
}
How It Works
- You define a
TokenMetadata field on your entity in Rust
- Hyperstack resolves the metadata server-side when the entity is first created
- Computed fields referencing the resolver are evaluated server-side on every update
- Your client receives the fully enriched entity — metadata and computed values included
The resolution happens transparently. Your TypeScript and React code simply reads the fields like any other entity data.
:::note[Caching]
Resolver data is cached server-side. Token metadata is fetched once per mint and reused across all entities that reference it.
:::
Real Example: Complete Entity with Resolver
From the ORE stack, showing resolver usage across multiple fields:
#[entity(name = "OreRound")]
#[view(name = "latest", sort_by = "id.round_id", order = "desc")]
pub struct OreRound {
pub id: RoundId,
pub state: RoundState,
pub results: RoundResults,
#[resolve(address = "oreoU2P8bN6jkk3jbaiVxYnG1dCXcYxwhwyK9jSybcp")]
pub ore_metadata: Option<TokenMetadata>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Stream)]
pub struct RoundState {
// Use resolver decimals in transform
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite,
transform = ui_amount(ore_metadata.decimals))]
pub motherlode: Option<f64>,
// Use fixed decimals for SOL
#[map(ore_sdk::accounts::Round::total_deployed, strategy = LastWrite,
transform = ui_amount(9))]
pub total_deployed: Option<f64>,
// Store raw values
#[map(ore_sdk::accounts::Round::deployed, strategy = LastWrite)]
pub deployed_per_square: Option<Vec<u64>>,
// Convert to UI amounts in computed field
#[computed(state.deployed_per_square.map(|x| x.ui_amount(9)))]
pub deployed_per_square_ui: Option<Vec<f64>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Stream)]
pub struct RoundResults {
#[map(ore_sdk::accounts::Round::top_miner_reward, strategy = LastWrite,
transform = ui_amount(ore_metadata.decimals))]
pub top_miner_reward: Option<f64>,
}
In this example:
ore_metadata is resolved once per round
motherlode and top_miner_reward use the resolver’s decimals
total_deployed and deployed_per_square_ui use fixed decimals (SOL = 9)
- All conversions happen server-side
Next Steps