Skip to content

Population Strategies

Population strategies define how an entity’s state is updated when new data arrives from the blockchain. Choosing the right strategy is critical for ensuring your projected state accurately reflects the underlying on-chain activity.

When a Hyperstack handler processes a transaction or account update, it maps data from the source (e.g., an Anchor instruction or a Protobuf event) into your entity’s fields. A Population Strategy determines how that incoming value interacts with the existing value in that field.

For example, should a “Total Volume” field be overwritten by the latest trade amount, or should the latest amount be added to the current total? Strategies answer this question.

Use this decision tree to identify the correct strategy for your field.

graph TD
A[Is this field set once and never changes?] -->|YES| B(SetOnce)
A -->|NO| C[Does it need to track the latest value?]
C -->|YES| D(LastWrite)
C -->|NO| E[Is it a numeric aggregation?]
E -->|YES| F(Max / Min / Sum / Count)
E -->|NO| G[Is it a collection of events?]
G -->|YES| H(Append)
G -->|NO| I(UniqueCount)
StrategyBehaviorBest For
LastWriteOverwrites with newest data (Default)Balances, Status, Current Prices
SetOnceOnly sets if field is emptyIDs, Creation Timestamps, Owners
SumAdds incoming value to currentVolume, Total Rewards, TVL
CountIncrements by 1Trade Count, User Count, Event Count
AppendAdds to an array/listTrade History, Activity Logs
Max / MinTracks the peak/troughHigh/Low Prices, Peak Liquidity
UniqueCountCounts distinct occurrencesActive Users, Unique Voters
MergeMerges keys in an objectConfiguration, Metadata

The most common strategy. Whenever a new value arrives, it completely replaces the previous one.

  • Use Case: Tracking the current state of an account.
  • Example:
    #[map(from = "TokenAccount", field = "amount", strategy = LastWrite)]
    pub balance: u64,

The field is populated only once. Subsequent updates for the same entity will ignore this mapping if the field already has a value.

  • Use Case: Immutable properties or “First Seen” metadata.
  • Example:
    #[map(from = "Market", field = "initializer", strategy = SetOnce)]
    pub creator: Pubkey,

Numeric values are added to the existing value. This is the foundation of tracking volume and throughput.

  • Use Case: Financial metrics and cumulative totals.
  • Example:
    #[aggregate(from = "Swap", field = "amount_in", strategy = Sum)]
    pub total_volume: u64,

Ignores the incoming value and simply increments the field by 1 for every match.

  • Use Case: Tracking throughput or frequency.
  • Example:
    #[aggregate(from = "Trade", strategy = Count)]
    pub trade_count: u64,

Adds the incoming value to a list. Use this to maintain a linear history of events within an entity.

  • Use Case: Event logs, audit trails.
  • Example:
    #[event(from = "Liquidate", strategy = Append)]
    pub liquidation_history: Vec<LiquidationEvent>,

Keeps the highest or lowest value seen across all updates.

  • Use Case: 24h Highs/Lows, price discovery.
  • Example:
    #[aggregate(from = "OracleUpdate", field = "price", strategy = Max)]
    pub all_time_high: u64,

Maintains a set of unique values internally but projects only the count of that set.

  • Use Case: Tracking “Active Users” or unique participants.
  • Example:
    #[aggregate(from = "Vote", field = "voter", strategy = UniqueCount)]
    pub total_unique_voters: u32,

Used for object-like fields where you want to update specific keys without blowing away the entire object.

  • Use Case: Dynamic configuration maps.
  • Example:
    #[map(from = "Config", strategy = Merge)]
    pub metadata: Map<String, String>,

FieldStrategyWhy
current_priceLastWriteYou only care about the most recent oracle update.
day_highMaxTracks the peak price seen in the stream.
daily_volumeSumEvery swap adds to the total.
FieldStrategyWhy
total_votesSumSum of vote weights.
voter_countUniqueCountCount unique public keys that voted.
first_vote_atSetOnceRecord when the first vote was cast.
  1. Summing non-numeric types: Sum only works on integers and floats. Applying it to a Pubkey or String will result in a compilation error.
  2. Using LastWrite for Volume: If you use LastWrite for a volume field, it will only show the amount of the most recent trade, not the total.
  3. Appending unnecessarily: Append grows the size of your entity state. Avoid appending thousands of items to a single entity if you only need the latest state; use a separate stream or specific aggregations instead.
  4. Forgetting the Default: If you don’t specify a strategy, LastWrite is used. Always explicitly state the strategy for aggregations to ensure clarity.