Skip to content

Type Definitions - Protocol State Structure

Overview

These type definitions provide the structure for managing state across all components of the Uniswap V2 processor, extending the base protocol state with specific pool data.


Core Type Definition

import { PoolConfig } from '../model';
import { ActiveBalance, ProtocolState } from '@absinthe/common';
import { PoolProcessState } from '../model';
import { PoolState } from '../model';
 
interface ProtocolStateUniv2 extends ProtocolState {
  config: PoolConfig;
  state: PoolState;
  processState: PoolProcessState;
  activeBalances: Map<string, ActiveBalance>;
}
 
export { ProtocolStateUniv2 };

State Structure Breakdown

Base ProtocolState (from @absinthe/common)

The base interface provides common fields for all protocol adapters:

interface ProtocolState {
  balanceWindows: TimeWeightedBalanceWindow[]; // Queued balance events
  transactions: TransactionEvent[]; // Queued transaction events
}

Extended ProtocolStateUniv2

Adds Uniswap V2-specific state management:

interface ProtocolStateUniv2 extends ProtocolState {
  // Database entities for this pool
  config: PoolConfig; // Token metadata and configuration
  state: PoolState; // Current reserves and blockchain state
  processState: PoolProcessState; // Processing timestamps and state
 
  // In-memory processing data
  activeBalances: Map<string, ActiveBalance>; // Current user LP balances
 
  // Inherited from ProtocolState:
  // balanceWindows: TimeWeightedBalanceWindow[]  // Events to send to API
  // transactions: TransactionEvent[]             // Swap events to send to API
}

State Component Details

config: PoolConfig

Purpose: Stores pool and token metadata discovered from blockchain

PoolConfig {
  id: string;                    // e.g., "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc-config"
  lpToken: Token;               // LP token metadata (address, decimals)
  token0: Token;                // First underlying token (address, decimals, coingeckoId)
  token1: Token;                // Second underlying token (address, decimals, coingeckoId)
}

Lifecycle:

  • Initialized once when pool first encountered
  • Loaded from database on processor restart
  • Immutable after creation

state: PoolState

Purpose: Current blockchain state of the pool

PoolState {
  id: string;                   // e.g., "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc-state"
  pool: PoolConfig;            // Reference to pool config
  reserve0: bigint;            // Current token0 reserves
  reserve1: bigint;            // Current token1 reserves
  totalSupply: bigint;         // Current LP token supply
  lastBlock: number;           // Last processed block
  lastTsMs: bigint;           // Last processed timestamp
  updatedAt: Date;            // Database update timestamp
}

Lifecycle:

  • Updated every time computeLpTokenPrice() is called
  • Synchronized with blockchain via updatePoolStateFromOnChain()
  • Persisted to database after each batch

processState: PoolProcessState

Purpose: Tracks timing for periodic operations

PoolProcessState {
  id: string;                          // e.g., "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc-process-state"
  pool: PoolConfig;                   // Reference to pool config
  lastInterpolatedTs?: bigint;        // Last time balance flush was performed
}

Usage:

  • Controls when to create EXHAUSTED balance windows
  • Ensures periodic balance snapshots happen on time boundaries
  • Prevents duplicate time windows across restarts

activeBalances: Map<string, ActiveBalance>

Purpose: In-memory tracking of current user LP balances

Map<userAddress, ActiveBalance> where:
 
ActiveBalance {
  balance: bigint;              // Current LP token balance (raw amount)
  updatedBlockTs: number;       // Timestamp when balance last changed
  updatedBlockHeight: number;   // Block height when balance last changed
}

Example:

activeBalances = new Map([
  [
    '0x742d35Cc1F54834567...',
    {
      balance: 1500000000000000000n, // 1.5 LP tokens (18 decimals)
      updatedBlockTs: 1640995200, // Jan 1, 2022 12:00:00
      updatedBlockHeight: 13916166, // Block when balance changed
    },
  ],
  [
    '0x8ba1f109551bD432...',
    {
      balance: 50000000000000000000n, // 50 LP tokens
      updatedBlockTs: 1640998800, // Jan 1, 2022 13:00:00
      updatedBlockHeight: 13916200, // Different block
    },
  ],
]);

Lifecycle:

  • Updated on every Transfer event (mint/burn/transfer)
  • Used to calculate time-weighted balances
  • Persisted as JSON in database during finalization
  • Loaded from database on processor restart

State Usage Patterns

During Initialization

const protocolState: ProtocolStateUniv2 = {
  config: poolConfig || new PoolConfig({}),
  state: poolState || new PoolState({}),
  processState: poolProcessState || new PoolProcessState({}),
  activeBalances: activeBalances || new Map<string, ActiveBalance>(),
  balanceWindows: [], // Empty array for new events
  transactions: [], // Empty array for new events
};

During Event Processing

// Swap event: Add to transactions array
protocolState.transactions.push(swapTransaction);
 
// Transfer event: Update activeBalances + add to balanceWindows
const newWindows = processValueChange({...});
protocolState.balanceWindows.push(...newWindows);

During Periodic Flush

// Check timing using processState
const needsFlush = currentTime > protocolState.processState.lastInterpolatedTs + windowDuration;
 
if (needsFlush) {
  // Create EXHAUSTED windows for all active users
  for (const [userAddress, balance] of protocolState.activeBalances) {
    protocolState.balanceWindows.push({
      userAddress,
      trigger: TimeWindowTrigger.EXHAUSTED,
      // ... other fields
    });
  }
}

During Finalization

// Transform accumulated events for API
const balances = toTimeWeightedBalance(protocolState.balanceWindows, ...);
const transactions = toTransaction(protocolState.transactions, ...);
 
// Send to API
await apiClient.send(balances);
await apiClient.send(transactions);
 
// Persist state to database
await ctx.store.upsert(protocolState.config);
await ctx.store.upsert(protocolState.state);
await ctx.store.upsert(protocolState.processState);

Type Safety Benefits

  1. Compile-time validation: TypeScript ensures all required fields are present
  2. IDE support: Auto-completion and refactoring across the codebase
  3. Clear interfaces: Documents expected structure for each state component
  4. Extension pattern: Easy to add new fields while maintaining compatibility

This type structure provides a clean, organized way to manage the complex state required for accurate time-weighted balance tracking and event processing in Uniswap V2 pools.