Skip to content

Resolvers

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:

#[hyperstack(idl = "idl/ore.json")]
pub mod ore_stream {
#[entity]
pub struct OreRound {
#[map(ore_sdk::accounts::Round::reward_mint, primary_key, strategy = SetOnce)]
pub mint: String,
// Hyperstack resolves this automatically from the mint
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://..."
}
FieldTypeDescription
mintstringThe mint address (always present)
namestring | nullToken name from on-chain metadata
symbolstring | nullToken ticker symbol
decimalsnumber | nullNumber of decimal places
logo_uristring | nullURL to the token’s logo image

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 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:

MethodDescriptionExample
ui_amountConverts raw token amount to human-readable UI amount1_000_000_000 with 9 decimals → 1.0
raw_amountConverts human-readable UI amount to raw token amount1.0 with 9 decimals → 1_000_000_000

Use these in #[computed] expressions:

#[entity]
pub struct OreRound {
pub ore_metadata: Option<TokenMetadata>,
#[map(ore_sdk::accounts::Round::motherlode, strategy = LastWrite)]
pub motherlode_raw: u64,
// Server-side: converts raw amount using the resolved decimals
#[computed(TokenMetadata::ui_amount(motherlode_raw, ore_metadata.decimals))]
pub motherlode_ui: Option<f64>,
}

On the client, motherlode_ui arrives as a ready-to-display number:

for await (const round of hs.views.OreRound.latest.use()) {
console.log(round.motherlode_ui); // 1.5 (human-readable ORE amount)
}

  1. You define a TokenMetadata field on your entity in Rust
  2. Hyperstack resolves the metadata server-side when the entity is first created
  3. Computed fields referencing the resolver are evaluated server-side on every update
  4. 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.