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();
}
Last updated