### Environment Configuration and Commands (.env) Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Sets up custom RPC endpoints for various blockchain networks and provides commands to install dependencies, start the indexer in development mode, run tests, and build the TypeScript project. It also indicates the GraphQL API endpoint. ```bash # .env - Environment configuration # Custom RPC endpoints for each chain ENVIO_MAINNET_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY ENVIO_BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/YOUR_KEY ENVIO_ARBITRUM_RPC_URL=https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY ENVIO_OPTIMISM_RPC_URL=https://opt-mainnet.g.alchemy.com/v2/YOUR_KEY # Start indexer locally pnpm install pnpm envio dev # Access GraphQL API at http://localhost:8080 # View Hasura console for data exploration # Run tests pnpm test # Build TypeScript pnpm build ``` -------------------------------- ### Run Envio Indexer Source: https://github.com/enviodev/uniswap-v4-indexer/blob/main/README.md Starts the Uniswap v4 indexer and all required services using Docker. This command initializes and runs the indexer, making data accessible via GraphQL. ```bash pnpm envio dev ``` -------------------------------- ### Install Dependencies with pnpm Source: https://github.com/enviodev/uniswap-v4-indexer/blob/main/README.md Installs project dependencies using the pnpm package manager. Ensure you have Node.js (v18+) and pnpm (v8+) installed before running. ```bash pnpm i ``` -------------------------------- ### Configure Mainnet RPC Endpoint Source: https://github.com/enviodev/uniswap-v4-indexer/blob/main/README.md Sets the RPC endpoint for the mainnet chain. This is an example of how to customize RPC endpoints for different chains using environment variables prefixed with ENVIO_. ```bash ENVIO_MAINNET_RPC_URL=https://your-mainnet-node ``` -------------------------------- ### Configure Arbitrum RPC Endpoint Source: https://github.com/enviodev/uniswap-v4-indexer/blob/main/README.md Sets the RPC endpoint for the Arbitrum chain. This is an example of how to customize RPC endpoints for different chains using environment variables prefixed with ENVIO_. ```bash ENVIO_ARBITRUM_RPC_URL=https://your-arbitrum-node ``` -------------------------------- ### GraphQL Queries for Uniswap V4 Data Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Provides example GraphQL queries to access indexed Uniswap V4 data, including pools, swaps, liquidity changes, tokens, and hook statistics. These queries can be used with a Hasura-hosted GraphQL API and support filtering, sorting, and pagination. ```graphql # Query pools with highest TVL query TopPools { Pool( order_by: { totalValueLockedUSD: desc } limit: 10 where: { chainId: { _eq: "1" } } ) { id name token0 token1 totalValueLockedUSD volumeUSD feesUSD txCount hooks } } # Query recent swaps for a specific pool query PoolSwaps($poolId: String!) { Swap( where: { pool: { _eq: $poolId } } order_by: { timestamp: desc } limit: 100 ) { id timestamp transaction sender origin amount0 amount1 amountUSD sqrtPriceX96 tick token0 { symbol name } token1 { symbol name } } } # Query hook statistics query HookStats { HookStats( order_by: { totalVolumeUSD: desc } where: { numberOfPools: { _gt: "0" } } ) { id chainId numberOfPools numberOfSwaps totalValueLockedUSD totalVolumeUSD totalFeesUSD firstPoolCreatedAt } } # Query token details with pools query TokenData($tokenId: String!) { Token(where: { id: { _eq: $tokenId } }) { id symbol name decimals volumeUSD totalValueLockedUSD txCount poolCount derivedETH } } # Query liquidity modifications for a pool query LiquidityChanges($poolId: String!) { ModifyLiquidity( where: { pool_id: { _eq: $poolId } } order_by: { timestamp: desc } limit: 50 ) { id timestamp sender origin amount amount0 amount1 amountUSD tickLower tickUpper } } # Query tick data for a pool query PoolTicks($poolId: String!) { Tick( where: { pool_id: { _eq: $poolId } } order_by: { tickIdx: asc } ) { id tickIdx liquidityGross liquidityNet price0 price1 } } ``` -------------------------------- ### Indexer Configuration (config.yaml) Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Defines the indexer's name, operational modes, and contract event handlers for Uniswap v4 on different blockchain networks. It specifies which events to track, which transaction fields to select, and the addresses and starting blocks for each network. ```yaml name: uniswap-v4-indexer unordered_multichain_mode: true rollback_on_reorg: true preload_handlers: true contracts: - name: PoolManager handler: src/EventHandlers.ts events: - event: Initialize(bytes32 indexed id, address indexed currency0, address indexed currency1, uint24 fee, int24 tickSpacing, address hooks, uint160 sqrtPriceX96, int24 tick) - event: Swap(bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee) field_selection: transaction_fields: - "hash" - "from" - event: ModifyLiquidity(bytes32 indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt) field_selection: transaction_fields: - "hash" - "from" networks: - id: 1 # Ethereum mainnet start_block: 23597758 contracts: - name: PoolManager address: - "0x000000000004444c5dc75cB358380D2e3dE08A90" - id: 8453 # Base start_block: 36959646 contracts: - name: PoolManager address: - "0x498581ff718922c3f8e6a244956af099b2652b2b" ``` -------------------------------- ### Get Tracked Amount in USD Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Calculates the total USD value of a pair of token amounts based on a whitelist. If both tokens are whitelisted, it sums their USD values. If only one token is whitelisted, it doubles its USD value to represent the pair's tracked amount. If neither token is whitelisted, it returns zero, indicating untracked assets. This function is used to determine the TVL for pairs where at least one asset is considered significant. ```typescript // Get tracked amount based on whitelist export async function getTrackedAmountUSD( context: handlerContext, tokenAmount0: BigDecimal, token0: Token, tokenAmount1: BigDecimal, token1: Token, chainId: string, whitelistTokens: string[] ): Promise { const bundle = await context.Bundle.get(chainId); if (!bundle) return ZERO_BD; const price0USD = token0.derivedETH.times(bundle.ethPriceUSD); const price1USD = token1.derivedETH.times(bundle.ethPriceUSD); const token0Address = token0.id.split("_")[1]; const token1Address = token1.id.split("_")[1]; // Both whitelisted: sum of both if (whitelistTokens.includes(token0Address) && whitelistTokens.includes(token1Address)) { return tokenAmount0.times(price0USD).plus(tokenAmount1.times(price1USD)); } // One whitelisted: double its value if (whitelistTokens.includes(token0Address)) { return tokenAmount0.times(price0USD).times(new BigDecimal("2")); } if (whitelistTokens.includes(token1Address)) { return tokenAmount1.times(price1USD).times(new BigDecimal("2")); } // Neither whitelisted: untracked return ZERO_BD; } ``` -------------------------------- ### Configuration and Deployment Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Details on configuring the indexer for multiple chains and deployment options. ```APIDOC ## Configuration and Deployment ### Description Configure the indexer for multiple chains using YAML configuration and environment variables. Deploy locally with Docker or connect to custom RPC endpoints for each supported network. ### Configuration - **Chain Configuration**: Use YAML files to define settings for each supported blockchain network. - **Environment Variables**: Utilize environment variables for sensitive information and runtime configurations. ### Deployment - **Local Deployment**: Deploy the indexer locally using Docker. - **Custom RPC Endpoints**: Connect to custom RPC endpoints for specific networks. ``` -------------------------------- ### Chain Configuration System for Uniswap V4 Indexer (TypeScript) Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Defines chain configurations for multi-chain support in a Uniswap V4 indexer. It includes enums for ChainIds, an interface for ChainConfig, and a constant object CHAIN_CONFIGS holding specific settings for networks like Ethereum Mainnet and Base. Helper functions to retrieve and validate chain configurations and check token whitelists are also provided. Requires the 'generated' package for BigDecimal. ```typescript import { BigDecimal } from "generated"; export enum ChainId { MAINNET = 1, ARBITRUM_ONE = 42161, OPTIMISM = 10, BASE = 8453, MATIC = 137, BSC = 56, AVALANCHE = 43114, BLAST = 81457, ZORA = 7777777, WORLD = 480, UNICHAIN = 130, SONEIUM = 1868, } export interface ChainConfig { poolManagerAddress: string; stablecoinWrappedNativePoolId: string; stablecoinIsToken0: boolean; wrappedNativeAddress: string; minimumNativeLocked: BigDecimal; stablecoinAddresses: string[]; whitelistTokens: string[]; tokenOverrides: StaticTokenDefinition[]; poolsToSkip: string[]; nativeTokenDetails: NativeTokenDetails; } // Example: Ethereum mainnet configuration export const CHAIN_CONFIGS: { [chainId: number]: ChainConfig } = { [ChainId.MAINNET]: { poolManagerAddress: "0x000000000004444c5dc75cb358380d2e3de08a90", stablecoinWrappedNativePoolId: "0x4f88f7c99022eace4740c6898f59ce6a2e798a1e64ce54589720b7153eb224a7", stablecoinIsToken0: true, wrappedNativeAddress: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH minimumNativeLocked: new BigDecimal("1"), stablecoinAddresses: [ "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT ], whitelistTokens: [ "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // WETH "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC "0x0000000000000000000000000000000000000000", // Native ETH ], poolsToSkip: [ "0xdfd5c2e5eca762ad4839fe581de3afa0788a220220cf8741c845e2fc099a5996", ], nativeTokenDetails: { symbol: "ETH", name: "Ethereum", decimals: BigInt(18), }, }, [ChainId.BASE]: { poolManagerAddress: "0x498581ff718922c3f8e6a244956af099b2652b2b", wrappedNativeAddress: "0x4200000000000000000000000000000000000006", // WETH minimumNativeLocked: new BigDecimal("1"), whitelistTokens: [ "0x4200000000000000000000000000000000000006", // WETH "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", // USDC ], nativeTokenDetails: { symbol: "ETH", name: "Ethereum", decimals: BigInt(18), }, // ... other config }, }; // Helper to get config export function getChainConfig(chainId: number): ChainConfig { const config = CHAIN_CONFIGS[chainId]; if (!config) { throw new Error(`Unsupported chain ID: ${chainId}`); } return config; } // Check if token is whitelisted export function isTokenWhitelisted(chainId: number, tokenAddress: string): boolean { const config = getChainConfig(chainId); return config.whitelistTokens.includes(tokenAddress.toLowerCase()); } ``` -------------------------------- ### Liquidity Math Utilities Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Provides functions to calculate token amounts based on liquidity deltas, tick ranges, and current pool state using Uniswap v3/v4 concentrated liquidity math. ```APIDOC ## Liquidity Math Utilities ### Description Calculates token amounts from liquidity deltas based on tick ranges and current pool state. Implements Uniswap v3/v4 concentrated liquidity math using sqrt price ratios to determine how much of each token is added or removed during liquidity modifications. ### Functions #### `getAmount0` Calculates the amount of token0 for a liquidity change. **Parameters** - `tickLower` (bigint) - The lower tick of the liquidity range. - `tickUpper` (bigint) - The upper tick of the liquidity range. - `currTick` (bigint) - The current tick of the pool. - `amount` (bigint) - The delta of liquidity being added or removed. - `currSqrtPriceX96` (bigint) - The current square root price of the pool in Q96 format. **Returns** - `bigint` - The calculated amount of token0. #### `getAmount1` Calculates the amount of token1 for a liquidity change. **Parameters** - `tickLower` (bigint) - The lower tick of the liquidity range. - `tickUpper` (bigint) - The upper tick of the liquidity range. - `currTick` (bigint) - The current tick of the pool. - `amount` (bigint) - The delta of liquidity being added or removed. - `currSqrtPriceX96` (bigint) - The current square root price of the pool in Q96 format. **Returns** - `bigint` - The calculated amount of token1. ``` -------------------------------- ### Initialize Pool Event Handler for Uniswap v4 Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt This handler processes the Initialize event from Uniswap v4 PoolManager contracts to create new pool entities. It captures event parameters, fetches token metadata, calculates initial prices using sqrtPriceX96, and sets up token relationships. Dependencies include Envio's context, generated contract types (PoolManager), and utility functions like getChainConfig, getTokenMetadata, and sqrtPriceX96ToTokenPrices. It creates or updates PoolManager and Pool entities in the database. ```typescript PoolManager.Initialize.handler(async ({ event, context }) => { const chainConfig = getChainConfig(Number(event.chainId)); // Skip blacklisted pools if (chainConfig.poolsToSkip.includes(event.params.id)) { return; } // Create or update PoolManager entity let poolManager = await context.PoolManager.get( `${event.chainId}_${event.srcAddress}` ); if (!poolManager) { poolManager = { id: `${event.chainId}_${event.srcAddress}`, chainId: BigInt(event.chainId), poolCount: 1n, txCount: 0n, totalVolumeUSD: new BigDecimal(0), totalFeesUSD: new BigDecimal(0), totalValueLockedUSD: new BigDecimal(0), // ... other fields }; } // Fetch token metadata and create Token entities const token0Id = `${event.chainId}_${event.params.currency0.toLowerCase()}`; const metadata = await context.effect(getTokenMetadata, { address: event.params.currency0, chainId: event.chainId, }); // Calculate initial prices from sqrtPriceX96 const prices = sqrtPriceX96ToTokenPrices( event.params.sqrtPriceX96, token0, // Assuming token0 is fetched or available token1, // Assuming token1 is fetched or available chainConfig.nativeTokenDetails ); // Create Pool entity const pool = { id: `${event.chainId}_${event.params.id}`, chainId: BigInt(event.chainId), name: `${token0.symbol} / ${token1.symbol} - ${feeBps}%`, token0: token0Id, token1: token1Id, feeTier: BigInt(event.params.fee), sqrtPrice: event.params.sqrtPriceX96, token0Price: prices[0], token1Price: prices[1], hooks: event.params.hooks, // ... other fields }; context.Pool.set(pool); context.PoolManager.set(poolManager); }); ``` -------------------------------- ### GraphQL API Queries Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Access indexed Uniswap v4 data through the GraphQL API. Query pools, swaps, liquidity changes, tokens, and hook statistics with flexible filtering, sorting, and pagination. ```APIDOC ## GraphQL API ### Description Access indexed Uniswap v4 data through GraphQL API hosted by Hasura. Query pools, swaps, liquidity changes, tokens, and hook statistics with flexible filtering, sorting, and pagination options. ### Queries #### `TopPools` Query pools with the highest Total Value Locked (TVL). **Parameters** - `order_by` (Object) - Sorting criteria for pools. - `totalValueLockedUSD` (Enum: `asc`, `desc`) - Sort by TVL in USD. - `limit` (Int) - Maximum number of pools to return. - `where` (Object) - Filtering criteria for pools. - `chainId` (`_eq`: String) - Filter by chain ID. **Returns** - `Pool` (Array of Objects) - List of pools with their details. - `id` (String) - `name` (String) - `token0` (Object) - `token1` (Object) - `totalValueLockedUSD` (Float) - `volumeUSD` (Float) - `feesUSD` (Float) - `txCount` (Int) - `hooks` (Object) #### `PoolSwaps` Query recent swaps for a specific pool. **Parameters** - `where` (Object) - Filtering criteria for swaps. - `pool` (`_eq`: String) - The ID of the pool to query. - `order_by` (Object) - Sorting criteria for swaps. - `timestamp` (Enum: `asc`, `desc`) - Sort by timestamp. - `limit` (Int) - Maximum number of swaps to return. **Returns** - `Swap` (Array of Objects) - List of swaps for the specified pool. - `id` (String) - `timestamp` (Int) - `transaction` (String) - `sender` (String) - `origin` (String) - `amount0` (String) - `amount1` (String) - `amountUSD` (Float) - `sqrtPriceX96` (String) - `tick` (Int) - `token0` (Object) - `symbol` (String) - `name` (String) - `token1` (Object) - `symbol` (String) - `name` (String) #### `HookStats` Query statistics for hooks. **Parameters** - `order_by` (Object) - Sorting criteria for hook statistics. - `totalVolumeUSD` (Enum: `asc`, `desc`) - Sort by total volume in USD. - `where` (Object) - Filtering criteria for hook statistics. - `numberOfPools` (`_gt`: String) - Filter hooks with more than a specified number of pools. **Returns** - `HookStats` (Array of Objects) - List of hook statistics. - `id` (String) - `chainId` (String) - `numberOfPools` (Int) - `numberOfSwaps` (Int) - `totalValueLockedUSD` (Float) - `totalVolumeUSD` (Float) - `totalFeesUSD` (Float) - `firstPoolCreatedAt` (Int) #### `TokenData` Query details for a specific token. **Parameters** - `where` (Object) - Filtering criteria for tokens. - `id` (`_eq`: String) - The ID of the token to query. **Returns** - `Token` (Array of Objects) - Details of the specified token. - `id` (String) - `symbol` (String) - `name` (String) - `decimals` (Int) - `volumeUSD` (Float) - `totalValueLockedUSD` (Float) - `txCount` (Int) - `poolCount` (Int) - `derivedETH` (Float) #### `LiquidityChanges` Query liquidity modifications for a specific pool. **Parameters** - `where` (Object) - Filtering criteria for liquidity modifications. - `pool_id` (`_eq`: String) - The ID of the pool to query. - `order_by` (Object) - Sorting criteria for liquidity modifications. - `timestamp` (Enum: `asc`, `desc`) - Sort by timestamp. - `limit` (Int) - Maximum number of liquidity modifications to return. **Returns** - `ModifyLiquidity` (Array of Objects) - List of liquidity modifications for the specified pool. - `id` (String) - `timestamp` (Int) - `sender` (String) - `origin` (String) - `amount` (String) - `amount0` (String) - `amount1` (String) - `amountUSD` (Float) - `tickLower` (Int) - `tickUpper` (Int) #### `PoolTicks` Query tick data for a specific pool. **Parameters** - `where` (Object) - Filtering criteria for ticks. - `pool_id` (`_eq`: String) - The ID of the pool to query. - `order_by` (Object) - Sorting criteria for ticks. - `tickIdx` (Enum: `asc`, `desc`) - Sort by tick index. **Returns** - `Tick` (Array of Objects) - List of ticks for the specified pool. - `id` (String) - `tickIdx` (Int) - `liquidityGross` (String) - `liquidityNet` (String) - `price0` (String) - `price1` (String) ``` -------------------------------- ### Calculate Token Amounts from Liquidity Deltas (TypeScript) Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Calculates the amounts of token0 and token1 added or removed based on liquidity deltas, tick ranges, and the current pool state. It uses Uniswap v3/v4 concentrated liquidity math and requires SqrtPriceMath and TickMath helper modules. The functions take tick boundaries, current tick, liquidity amount, and current square root price as input, returning the corresponding token amounts. ```typescript // Liquidity amount calculations for concentrated liquidity import { SqrtPriceMath } from "./sqrtPriceMath"; import { TickMath } from "./tickMath"; // Calculate amount of token0 for a liquidity change export function getAmount0( tickLower: bigint, tickUpper: bigint, currTick: bigint, amount: bigint, currSqrtPriceX96: bigint ): bigint { const sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower); const sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper); let amount0 = 0n; const roundUp = amount > 0n; if (currTick < tickLower) { // Position entirely below current tick amount0 = SqrtPriceMath.getAmount0Delta( sqrtRatioAX96, sqrtRatioBX96, amount, roundUp ); } else if (currTick < tickUpper) { // Position partially in range amount0 = SqrtPriceMath.getAmount0Delta( currSqrtPriceX96, sqrtRatioBX96, amount, roundUp ); } // If currTick >= tickUpper, amount0 = 0 return amount0; } // Calculate amount of token1 for a liquidity change export function getAmount1( tickLower: bigint, tickUpper: bigint, currTick: bigint, amount: bigint, currSqrtPriceX96: bigint ): bigint { const sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower); const sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper); let amount1 = 0n; const roundUp = amount > 0n; if (currTick < tickLower) { // Position entirely below current tick amount1 = 0n; } else if (currTick < tickUpper) { // Position partially in range amount1 = SqrtPriceMath.getAmount1Delta( sqrtRatioAX96, currSqrtPriceX96, amount, roundUp ); } else { // Position entirely above current tick amount1 = SqrtPriceMath.getAmount1Delta( sqrtRatioAX96, sqrtRatioBX96, amount, roundUp ); } return amount1; } // Usage example in ModifyLiquidity handler: const currTick = pool.tick ?? 0n; const currSqrtPriceX96 = pool.sqrtPrice ?? 0n; const amount0Raw = getAmount0( event.params.tickLower, event.params.tickUpper, currTick, event.params.liquidityDelta, currSqrtPriceX96 ); const amount1Raw = getAmount1( event.params.tickLower, event.params.tickUpper, currTick, event.params.liquidityDelta, currSqrtPriceX96 ); // Convert to decimal amounts const amount0 = convertTokenToDecimal(amount0Raw, token0.decimals); const amount1 = convertTokenToDecimal(amount1Raw, token1.decimals); ``` -------------------------------- ### Convert sqrtPriceX96 to Token Prices Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Converts the sqrtPriceX96 format used in Uniswap v4 to human-readable token prices. It takes the square root price, token details for both tokens in the pair, and native token information as input. It returns an array containing the price of token0 in terms of token1 and vice versa. This function handles the complexities of token decimal places and large number calculations. ```typescript import { BigDecimal } from "generated"; // Pricing utilities for token valuation // Convert sqrtPriceX96 to token prices const Q192 = BigInt(2) ** BigInt(192); export function sqrtPriceX96ToTokenPrices( sqrtPriceX96: bigint, token0: Token, token1: Token, nativeTokenDetails: NativeTokenDetails ): BigDecimal[] { const token0Decimals = token0.id == ADDRESS_ZERO ? nativeTokenDetails.decimals : token0.decimals; const token1Decimals = token1.id == ADDRESS_ZERO ? nativeTokenDetails.decimals : token1.decimals; const num = new BigDecimal((sqrtPriceX96 * sqrtPriceX96).toString()); const denom = new BigDecimal(Q192.toString()); const price1 = num .div(denom) .times(exponentToBigDecimal(token0Decimals)) .div(exponentToBigDecimal(token1Decimals)); const price0 = safeDiv(new BigDecimal("1"), price1); return [price0, price1]; } ``` -------------------------------- ### Handle Uniswap V4 Swaps in TypeScript Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt This handler processes token swaps within Uniswap V4 pools. It calculates volume, fees, and updates the total value locked (TVL) for each pool. It also derives token prices in USD and tracks statistics for custom hooks. This code relies on generated types from 'generated' and utility functions like 'getChainConfig', 'convertTokenToDecimal', and 'getTrackedAmountUSD'. ```typescript import { PoolManager, Swap } from "generated"; PoolManager.Swap.handler(async ({ event, context }) => { let [poolManager, pool, bundle] = await Promise.all([ context.PoolManager.get(`${event.chainId}_${event.srcAddress}`), context.Pool.get(`${event.chainId}_${event.params.id}`), context.Bundle.get(event.chainId.toString()), ]); if (!pool || !poolManager) return; const [token0, token1] = await Promise.all([ context.Token.get(pool.token0), context.Token.get(pool.token1), ]); const chainConfig = getChainConfig(event.chainId); // V4 inverts amount signs: negative = sent to pool const amount0 = convertTokenToDecimal( event.params.amount0, token0.decimals ).times(new BigDecimal("-1")); const amount1 = convertTokenToDecimal( event.params.amount1, token1.decimals ).times(new BigDecimal("-1")); // Calculate absolute amounts for volume const amount0Abs = amount0.lt(new BigDecimal("0")) ? amount0.times(new BigDecimal("-1")) : amount0; const amount1Abs = amount1.lt(new BigDecimal("0")) ? amount1.times(new BigDecimal("-1")) : amount1; // Get tracked USD amount based on whitelist const trackedAmountUSD = await getTrackedAmountUSD( context, amount0Abs, token0, amount1Abs, token1, event.chainId.toString(), chainConfig.whitelistTokens ); const amountTotalUSDTracked = trackedAmountUSD.div(new BigDecimal("2")); // Calculate fees const feesUSD = amountTotalUSDTracked .times(pool.feeTier.toString()) .div(new BigDecimal("1000000")); // Update pool metrics pool = { ...pool, txCount: pool.txCount + 1n, sqrtPrice: event.params.sqrtPriceX96, tick: event.params.tick, volumeUSD: pool.volumeUSD.plus(amountTotalUSDTracked), feesUSD: pool.feesUSD.plus(feesUSD), totalValueLockedToken0: pool.totalValueLockedToken0.plus(amount0), totalValueLockedToken1: pool.totalValueLockedToken1.plus(amount1), }; // Create Swap entity let swapEntity: Swap = { id: `${event.chainId}_${event.block.number}_${event.logIndex}`, chainId: BigInt(event.chainId), transaction: event.transaction.hash, timestamp: BigInt(event.block.timestamp), pool: `${event.chainId}_${event.params.id}`, token0_id: token0.id, token1_id: token1.id, sender: event.params.sender, origin: event.transaction.from || "NONE", amount0: amount0, amount1: amount1, amountUSD: amountTotalUSDTracked, sqrtPriceX96: event.params.sqrtPriceX96, tick: event.params.tick, logIndex: BigInt(event.logIndex), }; context.Swap.set(swapEntity); context.Pool.set(pool); context.PoolManager.set(poolManager); }); ``` -------------------------------- ### Find Derived ETH Price for Token Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt Finds the derived ETH price for a given token by searching through liquidity pools. It prioritizes direct conversions to WETH or ETH, then checks stablecoin prices, and finally iterates through whitelisted pools to find the pair with the largest liquidity depth. It returns the price of the token in ETH, considering a minimum locked native amount for relevance. This function is crucial for valuing tokens not directly paired with ETH. ```typescript // Find derived ETH price for any token export async function findNativePerToken( context: handlerContext, token: Token, wrappedNativeAddress: string, stablecoinAddresses: string[], minimumNativeLocked: BigDecimal ): Promise { const tokenAddress = token.id.split("_")[1]; const chainId = token.id.split("_")[0]; // Native token = 1 ETH if (tokenAddress == wrappedNativeAddress || tokenAddress == ADDRESS_ZERO) { return ONE_BD; } const bundle = await context.Bundle.get(chainId); if (!bundle) return ZERO_BD; // Stablecoins: 1 USD / ETH price if (stablecoinAddresses.includes(tokenAddress)) { return safeDiv(ONE_BD, bundle.ethPriceUSD); } // Search whitelist pools for best price let largestLiquidityETH = ZERO_BD; let priceSoFar = ZERO_BD; for (let i = 0; i < token.whitelistPools.length; ++i) { const pool = await context.Pool.get(token.whitelistPools[i]); if (pool && pool.liquidity > ZERO_BI) { const poolToken0 = pool.token0.split("_")[1]; if (poolToken0 == tokenAddress) { const token1 = await context.Token.get(pool.token1); if (token1) { const ethLocked = pool.totalValueLockedToken1.times(token1.derivedETH); if (ethLocked.gt(largestLiquidityETH) && ethLocked.gt(minimumNativeLocked)) { largestLiquidityETH = ethLocked; priceSoFar = pool.token1Price.times(token1.derivedETH); } } } } } return priceSoFar; } ``` -------------------------------- ### Handle Uniswap v4 ModifyLiquidity Events in TypeScript Source: https://context7.com/enviodev/uniswap-v4-indexer/llms.txt This handler processes the ModifyLiquidity event from Uniswap v4 pools. It updates tick entities, calculates token amounts and USD value based on liquidity deltas, and persists changes to pool and tick data. Dependencies include PoolManager, getChainConfig, createInitialTick, getAmount0, getAmount1, and convertTokenToDecimal. ```typescript import { PoolManager } from "generated"; PoolManager.ModifyLiquidity.handler(async ({ event, context }) => { const chainConfig = getChainConfig(Number(event.chainId)); if (chainConfig.poolsToSkip.includes(event.params.id)) { return; } const poolId = `${event.chainId}_${event.params.id}`; // Update tick entities const lowerTickId = poolId + "#" + BigInt(event.params.tickLower).toString(); const upperTickId = poolId + "#" + BigInt(event.params.tickUpper).toString(); let [lowerTick, upperTick] = await Promise.all([ context.Tick.get(lowerTickId), context.Tick.get(upperTickId), ]); // Create ticks if they don't exist if (!lowerTick) { lowerTick = createInitialTick( lowerTickId, Number(event.params.tickLower), poolId, BigInt(event.chainId), BigInt(event.block.timestamp), BigInt(event.block.number) ); } // Update tick liquidity const amount = event.params.liquidityDelta; lowerTick = { ...lowerTick, liquidityGross: lowerTick.liquidityGross + amount, liquidityNet: lowerTick.liquidityNet + amount, }; upperTick = { ...upperTick, liquidityGross: upperTick.liquidityGross + amount, liquidityNet: upperTick.liquidityNet - amount, }; let pool = await context.Pool.get(poolId); if (!pool) return; const [token0, token1, bundle] = await Promise.all([ context.Token.get(pool.token0), context.Token.get(pool.token1), context.Bundle.get(event.chainId.toString()), ]); const currTick = pool.tick ?? 0n; const currSqrtPriceX96 = pool.sqrtPrice ?? 0n; // Calculate token amounts from liquidity change const amount0Raw = getAmount0( event.params.tickLower, event.params.tickUpper, currTick, event.params.liquidityDelta, currSqrtPriceX96 ); const amount1Raw = getAmount1( event.params.tickLower, event.params.tickUpper, currTick, event.params.liquidityDelta, currSqrtPriceX96 ); const amount0 = convertTokenToDecimal(amount0Raw, token0.decimals); const amount1 = convertTokenToDecimal(amount1Raw, token1.decimals); // Calculate USD value const amountUSD = amount0 .times(token0.derivedETH) .plus(amount1.times(token1.derivedETH)) .times(bundle.ethPriceUSD); // Update pool TVL pool = { ...pool, totalValueLockedToken0: pool.totalValueLockedToken0.plus(amount0), totalValueLockedToken1: pool.totalValueLockedToken1.plus(amount1), }; // Only update liquidity if position is in range if ( event.params.tickLower <= (pool.tick ?? 0n) && event.params.tickUpper > (pool.tick ?? 0n) ) { pool = { ...pool, liquidity: pool.liquidity + event.params.liquidityDelta, }; } // Create ModifyLiquidity entity const modifyLiquidity = { id: `${event.chainId}_${event.transaction.hash}_${event.logIndex}`, chainId: BigInt(event.chainId), transaction: event.transaction.hash, timestamp: BigInt(event.block.timestamp), pool_id: pool.id, token0_id: token0.id, token1_id: token1.id, sender: event.params.sender, origin: event.transaction.from || "NONE", amount: event.params.liquidityDelta, amount0: amount0, amount1: amount1, amountUSD: amountUSD, tickLower: BigInt(event.params.tickLower), tickUpper: BigInt(event.params.tickUpper), logIndex: BigInt(event.logIndex), }; context.ModifyLiquidity.set(modifyLiquidity); context.Pool.set(pool); context.Tick.set(lowerTick); context.Tick.set(upperTick); }); ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.