Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 6 additions & 97 deletions app/api/chain-stats/[chainId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { NextResponse } from 'next/server';
import { Avalanche } from "@avalanche-sdk/chainkit";
import { TimeSeriesDataPoint, TimeSeriesMetric, ICMDataPoint, ICMMetric, STATS_CONFIG, getTimestampsFromTimeRange, createTimeSeriesMetric, createICMMetric } from "@/types/stats";
import l1ChainsData from "@/constants/l1-chains.json";

// Filter to only mainnet chains for ICM aggregation
const mainnetChains = l1ChainsData.filter(c => (c as { isTestnet?: boolean }).isTestnet !== true);
import { getChainICMData } from "@/lib/icm-clickhouse";

export const dynamic = 'force-dynamic';

Expand Down Expand Up @@ -503,116 +500,28 @@ async function getICMData(
case '7d': return 7;
case '30d': return 30;
case '90d': return 90;
case 'all': return 365;
case 'all': return 730;
default: return 30;
}
};
days = getDaysFromTimeRange(timeRange);
}

// For "all" chains, aggregate data from all mainnet chains
if (chainId === "all") {
return await aggregateAllChainsICMData(days, startTimestamp, endTimestamp);
}

const response = await fetchWithTimeout(
`https://idx6.solokhin.com/api/${chainId}/metrics/dailyMessageVolume?days=${days}`,
{ headers: { 'Accept': 'application/json' } }
);

if (!response.ok) return [];
const data = await response.json();
if (!Array.isArray(data)) return [];

let filteredData = data
.sort((a: any, b: any) => b.timestamp - a.timestamp)
.map((item: any) => ({
timestamp: item.timestamp,
date: item.date,
messageCount: item.messageCount || 0,
incomingCount: item.incomingCount || 0,
outgoingCount: item.outgoingCount || 0,
}));
let result = await getChainICMData(chainId, days);

if (startTimestamp !== undefined && endTimestamp !== undefined) {
filteredData = filteredData.filter((item: ICMDataPoint) =>
result = result.filter((item) =>
item.timestamp >= startTimestamp && item.timestamp <= endTimestamp
);
}

return filteredData;
return result;
} catch (error) {
if (error instanceof Error && error.name !== 'AbortError') {
console.warn(`[getICMData] Failed for chain ${chainId}:`, error);
}
console.warn(`[getICMData] Failed for chain ${chainId}:`, error);
return [];
}
}

// Helper function to aggregate ICM data from all mainnet chains
async function aggregateAllChainsICMData(
days: number,
startTimestamp?: number,
endTimestamp?: number
): Promise<ICMDataPoint[]> {
const aggregatedByDate = new Map<string, ICMDataPoint>();

// Process chains in batches to avoid overwhelming the API
const BATCH_SIZE = 20;

for (let i = 0; i < mainnetChains.length; i += BATCH_SIZE) {
const batch = mainnetChains.slice(i, i + BATCH_SIZE);

await Promise.all(
batch.map(async (chain) => {
try {
const response = await fetchWithTimeout(
`https://idx6.solokhin.com/api/${chain.chainId}/metrics/dailyMessageVolume?days=${days}`,
{ headers: { 'Accept': 'application/json' } }
);

if (response.ok) {
const data = await response.json();
if (Array.isArray(data)) {
data.forEach((item: any) => {
const dateKey = item.date;
const existing = aggregatedByDate.get(dateKey);

if (existing) {
existing.messageCount += item.messageCount || 0;
existing.incomingCount += item.incomingCount || 0;
existing.outgoingCount += item.outgoingCount || 0;
} else {
aggregatedByDate.set(dateKey, {
timestamp: item.timestamp,
date: item.date,
messageCount: item.messageCount || 0,
incomingCount: item.incomingCount || 0,
outgoingCount: item.outgoingCount || 0,
});
}
});
}
}
} catch {
// skip chains that fail or timeout
}
})
);
}

let result = Array.from(aggregatedByDate.values())
.sort((a, b) => b.timestamp - a.timestamp);

if (startTimestamp !== undefined && endTimestamp !== undefined) {
result = result.filter((item) =>
item.timestamp >= startTimestamp && item.timestamp <= endTimestamp
);
}

return result;
}

const ALL_METRICS = [
'activeAddresses', 'activeSenders', 'cumulativeAddresses', 'cumulativeDeployers',
'txCount', 'cumulativeTxCount', 'cumulativeContracts', 'contracts', 'deployers',
Expand Down
105 changes: 2 additions & 103 deletions app/api/icm-contract-fees/route.ts
Original file line number Diff line number Diff line change
@@ -1,116 +1,15 @@
import { NextResponse } from "next/server";

interface ContractStatsResponse {
contracts: string[];
timeRange: {
from: number;
to: number;
};
transactions: {
total: number;
totalGasCost: number;
};
icmMessages: {
count: number;
totalGasCost: number;
};
interactions: {
uniqueAddresses: number;
avgDailyAddresses: number;
};
concentration: {
top5AccountsPercentage: number;
top20AccountsPercentage: number;
};
}

interface DailyFeeData {
date: string;
timestamp: number;
feesPaid: number;
txCount: number;
}

let cachedDailyData: {
data: DailyFeeData[];
totalFees: number;
lastUpdated: string;
} | null = null;

let lastCacheTime = 0;
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours - historical fee data changes slowly
import { getICMContractFeesData } from "@/lib/icm-clickhouse";

export async function GET(_request: Request) {
try {
const icmContract = "0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf";
const deploymentTimestamp = 1709586720;
const now = Math.floor(Date.now() / 1000);

if (cachedDailyData && Date.now() - lastCacheTime < CACHE_DURATION) {
return NextResponse.json(cachedDailyData);
}

const dailyData: DailyFeeData[] = [];
const oneDaySeconds = 86400;
let currentTimestamp = deploymentTimestamp;

while (currentTimestamp < now) {
const nextTimestamp = Math.min(currentTimestamp + oneDaySeconds, now);

try {
const response = await fetch(
`https://idx6.solokhin.com/api/43114/contract-stats?contracts=${icmContract}&tsFrom=${currentTimestamp}&tsTo=${nextTimestamp}`,
{
headers: {
Accept: "application/json",
},
}
);

if (response.ok) {
const data: ContractStatsResponse = await response.json();
const dailyFees = data.transactions?.totalGasCost || 0;
const dailyTxCount = data.transactions?.total || 0;
const date = new Date(currentTimestamp * 1000).toISOString().split('T')[0];

dailyData.push({
date,
timestamp: currentTimestamp,
feesPaid: dailyFees,
txCount: dailyTxCount,
});
}
} catch (error) {
console.warn(`Failed to fetch ICM data for day ${currentTimestamp}:`, error);
}

currentTimestamp = nextTimestamp;
await new Promise(resolve => setTimeout(resolve, 5));
}

const totalFees = dailyData.reduce((sum, item) => sum + item.feesPaid, 0);

const result = {
data: dailyData,
totalFees,
lastUpdated: new Date().toISOString(),
};

cachedDailyData = result;
lastCacheTime = Date.now();

const result = await getICMContractFeesData();
return NextResponse.json(result);
} catch (error) {
console.error("Error fetching ICM contract fees:", error);

if (cachedDailyData) {
return NextResponse.json(cachedDailyData);
}

return NextResponse.json(
{ error: "Failed to fetch ICM contract fees data" },
{ status: 500 }
);
}
}

Loading