Skip to content

Pricing Utilities - LP Token & Swap Volume Calculations

Overview

These utilities handle the complex task of pricing LP tokens and calculating USD values for swaps. They integrate real-time blockchain data with CoinGecko price feeds to provide accurate valuations.


Core Imports & Price Caching

import Big from 'big.js';
import { PoolConfig, PoolState } from '../model';
import { DataHandlerContext, BlockData } from '@subsquid/evm-processor';
import { Store } from '@subsquid/typeorm-store';
import { updatePoolStateFromOnChain } from './pool';
import { fetchHistoricalUsd, pricePosition } from '@absinthe/common';
 
/** in-memory, process-wide price cache (key = "<id>-<hourBucket>") */
const hourlyPriceCache = new Map<string, number>();
Key Dependencies:
  • Big.js: High-precision decimal arithmetic for financial calculations
  • Price Cache: In-memory cache to avoid redundant CoinGecko API calls
  • Pool Utilities: Integration with on-chain state updates

๐Ÿ’ฐ Price Caching System

getHourlyPrice()

export async function getHourlyPrice(
  coingeckoId: string,
  timestampMs: number,
  coingeckoApiKey: string,
  isMocked: boolean = false,
): Promise<number> {
  if (isMocked) return 0;
  if (!coingeckoId) throw new Error('coingeckoId required');
 
  // Round timestamp to start of day for cache efficiency
  const dayBucket = new Date(timestampMs).setHours(0, 0, 0, 0);
  const k = `${coingeckoId}-${dayBucket}`;
 
  // Return cached price if available
  if (hourlyPriceCache.has(k)) return hourlyPriceCache.get(k)!;
 
  // Fetch from CoinGecko and cache result
  const price = await fetchHistoricalUsd(coingeckoId, timestampMs, coingeckoApiKey);
  hourlyPriceCache.set(k, price);
  return price;
}
Caching Strategy:
  • Day-level bucketing: Groups all requests within same day
  • Memory efficiency: Reduces API calls by ~24x (hours per day)
  • Process-wide cache: Shared across all pools and events
  • Cache key format: "ethereum-1640995200000" (token-daystart)
Example Usage:
// Multiple calls in same day return cached result
const ethPrice1 = await getHourlyPrice('ethereum', 1640995200000, apiKey); // API call
const ethPrice2 = await getHourlyPrice('ethereum', 1640998800000, apiKey); // Cache hit

๐Ÿ“Š Swap Volume Pricing

computePricedSwapVolume()

export async function computePricedSwapVolume(
  tokenAmount: bigint,
  coingeckoId: string,
  decimals: number,
  atMs: number,
  coingeckoApiKey: string,
  isMocked: boolean = false,
): Promise<number> {
  if (isMocked) return 0;
 
  // Get cached token price
  const price = await getHourlyPrice(coingeckoId, atMs, coingeckoApiKey);
 
  // Convert raw amount to USD value
  return pricePosition(price, tokenAmount, decimals);
}
Purpose:
  • Swap valuation: Calculate USD value of tokens traded in swaps
  • Decimal handling: Converts raw blockchain amounts to human-readable values
  • Historical pricing: Uses price at time of transaction, not current price
Example:
// Swap: 1.5 WETH at $3000/ETH on Jan 1, 2022
const usdValue = await computePricedSwapVolume(
  1500000000000000000n, // 1.5 ETH in wei
  'ethereum', // CoinGecko ID
  18, // ETH decimals
  1640995200000, // Jan 1, 2022 timestamp
  apiKey,
);
// Returns: 4500 (1.5 * $3000)

๐ŸŠ LP Token Price Calculation

computeLpTokenPrice()

The most complex function - calculates the USD value of a single LP token based on underlying reserves.

export async function computeLpTokenPrice(
  ctx: DataHandlerContext<Store>,
  block: BlockData,
  poolConfig: PoolConfig,
  poolState: PoolState,
  coingeckoApiKey: string,
  timestampMs?: number,
): Promise<{
  price: number; // USD value of 1 LP token
  token0Price: number; // USD price of token0
  token1Price: number; // USD price of token1
  token0Value: number; // Total USD value of token0 reserves
  token1Value: number; // Total USD value of token1 reserves
  totalPoolValue: number; // Total USD value of all liquidity
  totalSupplyBig: Big.Big; // Total LP tokens (decimal adjusted)
  reserve0: bigint; // Raw token0 reserves
  reserve1: bigint; // Raw token1 reserves
}> {
  // Validation: Ensure all required config exists
  if (!poolConfig) throw new Error('No poolConfig provided');
  if (!poolState) throw new Error('No poolState provided');
  if (!poolConfig.token0) throw new Error(`poolConfig.token0 missing in ${poolConfig.id}`);
  if (!poolConfig.token1) throw new Error(`poolConfig.token1 missing in ${poolConfig.id}`);
  if (!poolConfig.lpToken) throw new Error(`poolConfig.lpToken missing in ${poolConfig.id}`);
  if (!poolConfig.token0.coingeckoId || !poolConfig.token1.coingeckoId) {
    throw new Error('No coingecko id found for token0 or token1');
  }
 
  // Step 1: Update pool state with fresh blockchain data
  poolState = await updatePoolStateFromOnChain(ctx, block, poolConfig.lpToken.address, poolConfig, poolState);
 
  // Step 2: Handle edge case - zero total supply
  if (poolState.totalSupply === 0n) {
    console.warn(`Pool ${poolConfig.id} has zero total supply, returning price 0`);
    return {
      price: 0,
      token0Price: 0,
      token1Price: 0,
      token0Value: 0,
      token1Value: 0,
      totalPoolValue: 0,
      totalSupplyBig: new Big(0),
      reserve0: BigInt(0),
      reserve1: BigInt(0),
    };
  }
 
  // Step 3: Fetch token prices in parallel for efficiency
  const timestamp = timestampMs ?? Number(poolState.lastTsMs);
  const [token0Price, token1Price] = await Promise.all([
    getHourlyPrice(poolConfig.token0.coingeckoId, timestamp, coingeckoApiKey),
    getHourlyPrice(poolConfig.token1.coingeckoId, timestamp, coingeckoApiKey),
  ]);
 
  // Step 4: Handle edge case - zero token prices
  if (token0Price === 0 || token1Price === 0) {
    console.warn(`Token prices are 0 for pool ${poolConfig.id}, returning price 0`);
    return {
      price: 0,
      token0Price: 0,
      token1Price: 0,
      token0Value: 0,
      token1Value: 0,
      totalPoolValue: 0,
      totalSupplyBig: new Big(0),
      reserve0: BigInt(0),
      reserve1: BigInt(0),
    };
  }
 
  // Step 5: Calculate USD value of each token's reserves
  const token0Value = pricePosition(token0Price, poolState.reserve0, poolConfig.token0.decimals);
  const token1Value = pricePosition(token1Price, poolState.reserve1, poolConfig.token1.decimals);
  const totalPoolValue = token0Value + token1Value;
 
  // Step 6: Convert total supply to decimal-adjusted value
  const totalSupplyBig = new Big(poolState.totalSupply.toString()).div(new Big(10).pow(poolConfig.lpToken.decimals));
 
  // Step 7: Additional safety check after decimal conversion
  if (totalSupplyBig.eq(0)) {
    console.warn(`Pool ${poolConfig.id} has zero supply after conversion, returning price 0`);
    return {
      price: 0,
      token0Price: 0,
      token1Price: 0,
      token0Value: 0,
      token1Value: 0,
      totalPoolValue: 0,
      totalSupplyBig: new Big(0),
      reserve0: BigInt(0),
      reserve1: BigInt(0),
    };
  }
 
  // Step 8: Calculate final LP token price
  const price = new Big(totalPoolValue).div(totalSupplyBig).toNumber();
 
  return {
    price,
    token0Price,
    token1Price,
    token0Value,
    token1Value,
    totalPoolValue,
    totalSupplyBig,
    reserve0: poolState.reserve0,
    reserve1: poolState.reserve1,
  };
}

Example Calculation

// USDC/WETH Pool Example:
// Reserve0: 1,000,000 USDC (6 decimals)
// Reserve1: 500 WETH (18 decimals)
// USDC Price: $1.00
// WETH Price: $2,000
// Total LP Supply: 22,360 tokens (18 decimals)
 
const token0Value = 1000000 * 1.00 = $1,000,000    // USDC value
const token1Value = 500 * 2000 = $1,000,000        // WETH value
const totalPoolValue = $2,000,000                  // Total liquidity
 
const lpTokenPrice = $2,000,000 รท 22,360 = $89.44  // Per LP token

Error Handling & Edge Cases

  • Zero supply pools: Returns price 0 to avoid division by zero
  • Missing CoinGecko IDs: Throws error early to fail fast
  • Zero token prices: Returns price 0 (could be new/unlisted tokens)
  • State synchronization: Always fetches fresh reserves before calculation

Performance Optimizations

  • Parallel price fetching: Uses Promise.all() for token0 and token1 prices
  • Price caching: Leverages getHourlyPrice() caching system
  • Fresh state: Updates pool state only when needed for calculations
  • Big.js precision: Avoids floating-point errors in financial calculations