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.
What are Population Strategies?
Section titled “What are Population Strategies?”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.
Strategy Selection Guide
Section titled “Strategy Selection Guide”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)Quick Reference Table
Section titled “Quick Reference Table”| Strategy | Behavior | Best For |
|---|---|---|
LastWrite | Overwrites with newest data (Default) | Balances, Status, Current Prices |
SetOnce | Only sets if field is empty | IDs, Creation Timestamps, Owners |
Sum | Adds incoming value to current | Volume, Total Rewards, TVL |
Count | Increments by 1 | Trade Count, User Count, Event Count |
Append | Adds to an array/list | Trade History, Activity Logs |
Max / Min | Tracks the peak/trough | High/Low Prices, Peak Liquidity |
UniqueCount | Counts distinct occurrences | Active Users, Unique Voters |
Merge | Merges keys in an object | Configuration, Metadata |
Detailed Reference
Section titled “Detailed Reference”LastWrite (Default)
Section titled “LastWrite (Default)”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,
SetOnce
Section titled “SetOnce”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,
Append
Section titled “Append”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>,
Max / Min
Section titled “Max / Min”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,
UniqueCount
Section titled “UniqueCount”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>,
Common Patterns
Section titled “Common Patterns”Token Price tracking
Section titled “Token Price tracking”| Field | Strategy | Why |
|---|---|---|
current_price | LastWrite | You only care about the most recent oracle update. |
day_high | Max | Tracks the peak price seen in the stream. |
daily_volume | Sum | Every swap adds to the total. |
Governance tracking
Section titled “Governance tracking”| Field | Strategy | Why |
|---|---|---|
total_votes | Sum | Sum of vote weights. |
voter_count | UniqueCount | Count unique public keys that voted. |
first_vote_at | SetOnce | Record when the first vote was cast. |
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”- Summing non-numeric types:
Sumonly works on integers and floats. Applying it to aPubkeyorStringwill result in a compilation error. - Using LastWrite for Volume: If you use
LastWritefor a volume field, it will only show the amount of the most recent trade, not the total. - Appending unnecessarily:
Appendgrows 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. - Forgetting the Default: If you don’t specify a strategy,
LastWriteis used. Always explicitly state the strategy for aggregations to ensure clarity.