⚠️Blast API (blastapi.io) ends Oct 31. Migrate to Dwellir and skip Alchemy's expensive compute units.
Switch Today →
Skip to main content

Aggregator V2

Aggregator V2 is a specialized data structure in Aptos designed for high-performance concurrent counters that enable parallel transaction execution without conflicts. It leverages Aptos's Block-STM parallel execution engine to allow multiple transactions to increment or decrement the same counter simultaneously, dramatically improving throughput for applications with shared state.

Overview#

Traditional blockchain counters create transaction conflicts when multiple operations try to modify the same value concurrently. Aggregator V2 solves this by using commutative merge semantics, where operations can be applied in any order and combined at the end of block execution. This enables true parallelism for operations like tracking token supply, counting users, or maintaining statistics.

Technical Implementation#

Aggregator V2 uses a mathematical property called commutativity: addition and subtraction operations produce the same result regardless of execution order. The Block-STM engine tracks delta values during parallel execution and merges them atomically at commit time.

module 0x1::token_counter {
use aptos_framework::aggregator_v2::{Self, Aggregator};

struct TokenStats has key {
total_minted: Aggregator<u64>,
total_burned: Aggregator<u64>,
active_holders: Aggregator<u64>
}

public fun initialize(account: &signer) {
move_to(account, TokenStats {
total_minted: aggregator_v2::create_aggregator(0),
total_burned: aggregator_v2::create_aggregator(0),
active_holders: aggregator_v2::create_aggregator(0)
});
}

public entry fun mint_tokens(amount: u64) acquires TokenStats {
let stats = borrow_global_mut<TokenStats>(@0x1);
aggregator_v2::add(&mut stats.total_minted, amount);
}

public entry fun burn_tokens(amount: u64) acquires TokenStats {
let stats = borrow_global_mut<TokenStats>(@0x1);
aggregator_v2::add(&mut stats.total_burned, amount);
}

public entry fun on_new_holder() acquires TokenStats {
let stats = borrow_global_mut<TokenStats>(@0x1);
aggregator_v2::add(&mut stats.active_holders, 1);
}

#[view]
public fun get_total_minted(): u64 acquires TokenStats {
let stats = borrow_global<TokenStats>(@0x1);
aggregator_v2::read(&stats.total_minted)
}

#[view]
public fun circulating_supply(): u64 acquires TokenStats {
let stats = borrow_global<TokenStats>(@0x1);
aggregator_v2::read(&stats.total_minted) - aggregator_v2::read(&stats.total_burned)
}
}

API Functions#

// Create new aggregator with initial value
aggregator_v2::create_aggregator<T>(initial_value: T): Aggregator<T>

// Add value (works for any numeric type)
aggregator_v2::add<T>(aggregator: &mut Aggregator<T>, value: T)

// Subtract value
aggregator_v2::sub<T>(aggregator: &mut Aggregator<T>, value: T)

// Read current value
aggregator_v2::read<T>(aggregator: &Aggregator): T

// Try to subtract with bounds checking
aggregator_v2::try_sub<T>(aggregator: &mut Aggregator<T>, value: T): bool

Real-World Use Cases#

  1. Token Supply Tracking: Track total minted and burned tokens across thousands of concurrent mint/burn transactions without creating bottlenecks or conflicts.

  2. User Statistics: Maintain real-time counts of active users, daily transactions, or engagement metrics in high-traffic applications without serialization.

  3. DEX Volume Counters: Aggregate trading volumes, swap counts, and liquidity metrics across parallel trading operations on decentralized exchanges.

  4. NFT Collection Stats: Track total minted NFTs, active listings, and collection metrics as multiple users mint and trade simultaneously.

  5. Gaming Leaderboards: Update player scores, achievement counts, and global statistics with high concurrency during peak gaming periods.

  6. Protocol Analytics: Maintain real-time protocol metrics like total value locked, transaction counts, and fee accumulation without performance degradation.

Performance Benefits#

Without Aggregator V2, concurrent counter updates would create conflicts requiring sequential execution:

// Traditional counter - causes conflicts
struct OldCounter has key {
value: u64 // Every update conflicts with others
}

public fun increment() acquires OldCounter {
let counter = borrow_global_mut<OldCounter>(@0x1);
counter.value = counter.value + 1; // Conflict!
}

With Aggregator V2, the same operations execute in parallel:

// Parallel counter - no conflicts
struct NewCounter has key {
value: Aggregator<u64> // Parallel updates merge
}

public fun increment() acquires NewCounter {
let counter = borrow_global_mut<NewCounter>(@0x1);
aggregator_v2::add(&mut counter.value, 1); // Parallelizes!
}

Benchmark results show 10-100x throughput improvements for counter-heavy workloads.

Best Practices#

Use for Shared State: Apply Aggregator V2 to any counter or accumulator that multiple transactions will modify concurrently.

Combine with Regular Fields: Mix aggregators with normal fields in the same struct for optimal performance:

struct MixedStats has key {
total_count: Aggregator<u64>, // Parallel updates
last_updated: u64, // Sequential field
admin: address // Sequential field
}

Read Sparingly in Transactions: Reading aggregator values during transaction execution may reduce parallelism. Prefer reads in view functions.

Batch Operations: When possible, batch multiple small updates into larger operations to reduce overhead.

Consider Overflow: While aggregators support large numbers, implement checks for meaningful limits based on your application logic.

Monitor Performance: Use Aptos performance metrics to verify that aggregators are providing expected parallelism benefits.

Limitations and Considerations#

  • Aggregators only support commutative operations (addition, subtraction)
  • Cannot use aggregators for operations requiring specific ordering
  • Reading aggregator values in transaction execution may impact parallelism
  • Not suitable for operations requiring immediate consistency checks
  • Best suited for statistics and metrics rather than critical balance tracking

Migration from V1#

If you're using the older Aggregator V1 API, migrate to V2 for improved performance:

// V1 (deprecated)
use aptos_framework::aggregator;
let agg = aggregator::create(100); // With max limit

// V2 (recommended)
use aptos_framework::aggregator_v2;
let agg = aggregator_v2::create_aggregator(0); // No max limit needed