# API implementation

This script queries the Upheaval Finance DEX subgraph to fetch detailed position data for a given pool. It retrieves pool information, active positions, and position snapshots from the last 7 days, then organizes and displays the results with per-user breakdowns and summary statistics.

<https://raw.githubusercontent.com/upheaval-defi/khype/refs/heads/main/fetch-pairs-positions.js>

```
/**
 * thBILL Pool Position Snapshots Fetcher
 * =======================================
 * 
 * This script fetches and displays current position snapshots for any pool
 * from the Upheaval Finance DEX subgraph.
 * 
 * WHAT IT DOES:
 * -------------
 * 1. Fetches all current positions for the thBILL pool
 * 2. Gets position snapshots from the last 7 days
 * 3. Shows detailed position data including liquidity, deposits, withdrawals, and fees
 * 4. Groups and displays data by position and user
 * 5. Provides summary statistics
 * 
 * 
 * OUTPUT FORMAT:
 * --------------
 * The script prints organized data showing:
 * - Pool information (tokens, addresses)
 * - Current positions with their owners
 * - Position snapshots with detailed metrics
 * - Summary statistics
 * 
 * USAGE:
 * ------
 * Run directly: `node fetch-pair-positions.js`
 * 
 * @date 2025-09-11
 */

const POOL_ID = '<YOUR_POOL_ID>';
const SUBGRAPH_URL = 'https://api.upheaval.fi/subgraphs/name/upheaval/exchange-v3';

// Get timestamp for 7 days ago
function getSevenDaysAgo() {
  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
  return Math.floor(sevenDaysAgo.getTime() / 1000);
}

// GraphQL query to fetch pool information
const POOL_INFO_QUERY = `
  query GetPoolInfo($poolId: String!) {
    pool(id: $poolId) {
      id
      token0 {
        id
        name
        symbol
        decimals
      }
      token1 {
        id
        name
        symbol
        decimals
      }
      feeTier
      liquidity
      sqrtPrice
      tick
      observationIndex
      volumeUSD
      txCount
      totalValueLockedUSD
    }
  }
`;

// GraphQL query to fetch current positions for the pool
const CURRENT_POSITIONS_QUERY = `
  query GetCurrentPositions($poolId: String!) {
    positions(
      where: {
        pool: $poolId,
        liquidity_gt: "0"
      }
      orderBy: liquidity
      orderDirection: desc
      first: 1000
    ) {
      id
      owner
      liquidity
      depositedToken0
      depositedToken1
      withdrawnToken0
      withdrawnToken1
      collectedFeesToken0
      collectedFeesToken1
      tickLower {
        tickIdx
      }
      tickUpper {
        tickIdx
      }
      pool {
        id
        token0 {
          id
          name
          symbol
        }
        token1 {
          id
          name
          symbol
        }
      }
    }
  }
`;

// GraphQL query to fetch position snapshots for the pool
const POSITION_SNAPSHOTS_QUERY = `
  query GetPositionSnapshots($poolId: String!, $timestamp: BigInt!) {
    positionSnapshots(
      where: {
        pool: $poolId,
        timestamp_gte: $timestamp
      }
      orderBy: timestamp
      orderDirection: desc
      first: 1000
    ) {
      id
      owner
      pool {
        id
        token0 {
          id
          name
          symbol
        }
        token1 {
          id
          name
          symbol
        }
      }
      position {
        id
        tickLower {
          tickIdx
        }
        tickUpper {
          tickIdx
        }
      }
      blockNumber
      timestamp
      liquidity
      depositedToken0
      depositedToken1
      withdrawnToken0
      withdrawnToken1
      collectedFeesToken0
      collectedFeesToken1
      transaction {
        id
      }
    }
  }
`;

async function fetchPoolInfo() {
  try {
    console.log(`Fetching pool information for ${POOL_ID}...`);
    
    const response = await fetch(SUBGRAPH_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/graphql-response+json, application/json',
      },
      body: JSON.stringify({
        query: POOL_INFO_QUERY,
        variables: {
          poolId: POOL_ID
        }
      })
    });

    const data = await response.json();
    
    if (data.errors) {
      throw new Error('GraphQL errors: ' + JSON.stringify(data.errors));
    }

    if (!data.data.pool) {
      throw new Error(`Pool ${POOL_ID} not found`);
    }

    return data.data.pool;

  } catch (error) {
    console.error('Error fetching pool info:', error);
    throw error;
  }
}

async function fetchCurrentPositions() {
  try {
    console.log(`Fetching current positions for pool ${POOL_ID}...`);
    
    const response = await fetch(SUBGRAPH_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/graphql-response+json, application/json',
      },
      body: JSON.stringify({
        query: CURRENT_POSITIONS_QUERY,
        variables: {
          poolId: POOL_ID
        }
      })
    });

    const data = await response.json();
    
    if (data.errors) {
      throw new Error('GraphQL errors: ' + JSON.stringify(data.errors));
    }

    console.log(`Found ${data.data.positions.length} current positions with liquidity > 0`);
    return data.data.positions;

  } catch (error) {
    console.error('Error fetching current positions:', error);
    throw error;
  }
}

async function fetchPositionSnapshots() {
  try {
    const sevenDaysAgoTimestamp = getSevenDaysAgo();
    
    console.log(`Fetching position snapshots for pool ${POOL_ID}...`);
    console.log(`Time range: Last 7 days (since ${new Date(sevenDaysAgoTimestamp * 1000).toISOString()})`);
    
    const response = await fetch(SUBGRAPH_URL, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/graphql-response+json, application/json',
      },
      body: JSON.stringify({
        query: POSITION_SNAPSHOTS_QUERY,
        variables: {
          poolId: POOL_ID,
          timestamp: sevenDaysAgoTimestamp.toString()
        }
      })
    });

    const data = await response.json();
    
    if (data.errors) {
      throw new Error('GraphQL errors: ' + JSON.stringify(data.errors));
    }

    console.log(`Found ${data.data.positionSnapshots.length} position snapshots`);
    return data.data.positionSnapshots;

  } catch (error) {
    console.error('Error fetching position snapshots:', error);
    throw error;
  }
}

function formatTokenAmount(amount) {
  if (!amount || amount === '0') return '0';
  const numAmount = parseFloat(amount);
  if (numAmount === 0) return '0';
  
  if (numAmount >= 1) {
    return numAmount.toFixed(6);
  } else {
    return numAmount.toFixed(12);
  }
}

function displayPoolInfo(pool) {
  console.log('\n=== POOL INFORMATION ===');
  console.log(`Pool ID: ${pool.id}`);
  console.log(`Token0: ${pool.token0.name} (${pool.token0.symbol}) - ${pool.token0.id}`);
  console.log(`Token1: ${pool.token1.name} (${pool.token1.symbol}) - ${pool.token1.id}`);
  console.log(`Fee Tier: ${pool.feeTier}`);
  console.log(`Current Liquidity: ${pool.liquidity}`);
  console.log(`Current Tick: ${pool.tick}`);
  console.log(`Total Value Locked USD: $${parseFloat(pool.totalValueLockedUSD || 0).toFixed(2)}`);
  console.log(`Volume USD: $${parseFloat(pool.volumeUSD || 0).toFixed(2)}`);
  console.log(`Transaction Count: ${pool.txCount}`);
}

function displayCurrentPositions(positions, pool) {
  console.log('\n=== CURRENT POSITIONS (Active Liquidity) ===');
  
  if (positions.length === 0) {
    console.log('No active positions found in this pool');
    return;
  }

  // Group positions by owner
  const positionsByOwner = {};
  positions.forEach(position => {
    const owner = position.owner;
    if (!positionsByOwner[owner]) {
      positionsByOwner[owner] = [];
    }
    positionsByOwner[owner].push(position);
  });

  Object.entries(positionsByOwner).forEach(([owner, userPositions]) => {
    console.log(`\n👤 Owner: ${owner} (${userPositions.length} positions)`);
    
    userPositions.forEach((position, index) => {
      console.log(`\n  📍 Position ${index + 1}:`);
      console.log(`     Position ID: ${position.id}`);
      console.log(`     Liquidity: ${position.liquidity}`);
      console.log(`     Tick Range: ${position.tickLower.tickIdx} to ${position.tickUpper.tickIdx}`);
      console.log(`     Deposited ${pool.token0.symbol}: ${formatTokenAmount(position.depositedToken0)}`);
      console.log(`     Deposited ${pool.token1.symbol}: ${formatTokenAmount(position.depositedToken1)}`);
      console.log(`     Withdrawn ${pool.token0.symbol}: ${formatTokenAmount(position.withdrawnToken0)}`);
      console.log(`     Withdrawn ${pool.token1.symbol}: ${formatTokenAmount(position.withdrawnToken1)}`);
      console.log(`     Collected Fees ${pool.token0.symbol}: ${formatTokenAmount(position.collectedFeesToken0)}`);
      console.log(`     Collected Fees ${pool.token1.symbol}: ${formatTokenAmount(position.collectedFeesToken1)}`);
    });
  });

  // Summary statistics
  const totalLiquidity = positions.reduce((sum, p) => sum + parseFloat(p.liquidity), 0);
  console.log(`\n📊 CURRENT POSITIONS SUMMARY:`);
  console.log(`   Total Active Positions: ${positions.length}`);
  console.log(`   Unique Owners: ${Object.keys(positionsByOwner).length}`);
  console.log(`   Total Liquidity: ${totalLiquidity.toFixed(0)}`);
  console.log(`   Average Liquidity per Position: ${(totalLiquidity / positions.length).toFixed(0)}`);
}

function displayPositionSnapshots(snapshots, pool) {
  console.log('\n=== POSITION SNAPSHOTS (Last 7 Days) ===');
  
  if (snapshots.length === 0) {
    console.log('No position snapshots found in the last 7 days');
    return;
  }

  // Group snapshots by position ID
  const snapshotsByPosition = {};
  snapshots.forEach(snapshot => {
    const positionId = snapshot.position?.id;
    if (positionId) {
      if (!snapshotsByPosition[positionId]) {
        snapshotsByPosition[positionId] = [];
      }
      snapshotsByPosition[positionId].push(snapshot);
    }
  });

  // Sort positions by activity level
  const sortedPositions = Object.entries(snapshotsByPosition)
    .sort(([, a], [, b]) => b.length - a.length);

  sortedPositions.forEach(([positionId, positionSnapshots]) => {
    const firstSnapshot = positionSnapshots[0];
    const owner = firstSnapshot.owner;
    
    console.log(`\n🎯 Position ID: ${positionId}`);
    console.log(`   Owner: ${owner}`);
    console.log(`   Snapshots: ${positionSnapshots.length}`);
    
    if (firstSnapshot.position?.tickLower && firstSnapshot.position?.tickUpper) {
      console.log(`   Tick Range: ${firstSnapshot.position.tickLower.tickIdx} to ${firstSnapshot.position.tickUpper.tickIdx}`);
    }

    // Show recent snapshots (max 3)
    const recentSnapshots = positionSnapshots.slice(0, 3);
    recentSnapshots.forEach((snapshot, index) => {
      const date = new Date(parseInt(snapshot.timestamp) * 1000);
      console.log(`\n   📸 Snapshot ${index + 1} - ${date.toISOString()}`);
      console.log(`      Block: ${snapshot.blockNumber}`);
      console.log(`      Liquidity: ${snapshot.liquidity}`);
      console.log(`      Deposited ${pool.token0.symbol}: ${formatTokenAmount(snapshot.depositedToken0)}`);
      console.log(`      Deposited ${pool.token1.symbol}: ${formatTokenAmount(snapshot.depositedToken1)}`);
      console.log(`      Withdrawn ${pool.token0.symbol}: ${formatTokenAmount(snapshot.withdrawnToken0)}`);
      console.log(`      Withdrawn ${pool.token1.symbol}: ${formatTokenAmount(snapshot.withdrawnToken1)}`);
      console.log(`      Fees ${pool.token0.symbol}: ${formatTokenAmount(snapshot.collectedFeesToken0)}`);
      console.log(`      Fees ${pool.token1.symbol}: ${formatTokenAmount(snapshot.collectedFeesToken1)}`);
    });

    if (positionSnapshots.length > 3) {
      console.log(`   ... and ${positionSnapshots.length - 3} more snapshots`);
    }
  });

  // Overall summary
  const uniqueOwners = new Set(snapshots.map(s => s.owner)).size;
  console.log(`\n📊 SNAPSHOTS SUMMARY:`);
  console.log(`   Total Snapshots: ${snapshots.length}`);
  console.log(`   Unique Positions: ${sortedPositions.length}`);
  console.log(`   Unique Owners: ${uniqueOwners}`);
  console.log(`   Most Active Position: ${sortedPositions[0]?.[0]} (${sortedPositions[0]?.[1].length} snapshots)`);
}

// Main execution
async function main() {
  try {
    console.log(`\n🔍 FETCHING DATA FOR POOL: ${POOL_ID}\n`);
    
    // Fetch all data
    const [poolInfo, currentPositions, positionSnapshots] = await Promise.all([
      fetchPoolInfo(),
      fetchCurrentPositions(),
      fetchPositionSnapshots()
    ]);
    
    // Display all information
    displayPoolInfo(poolInfo);
    displayCurrentPositions(currentPositions, poolInfo);
    displayPositionSnapshots(positionSnapshots, poolInfo);
    
    console.log('\n✅ Data fetch complete!\n');
    
    return {
      poolInfo,
      currentPositions,
      positionSnapshots
    };
    
  } catch (error) {
    console.error('Failed to fetch pool position data:', error);
  }
}

// Export functions for use in other modules
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    fetchPoolInfo,
    fetchCurrentPositions,
    fetchPositionSnapshots,
    displayPoolInfo,
    displayCurrentPositions,
    displayPositionSnapshots,
    POOL_ID,
    SUBGRAPH_URL,
    main
  };
}

// Run if this file is executed directly
if (typeof require !== 'undefined' && require.main === module) {
  main();
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.upheaval.fi/api-implementation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
