Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e1d62a6
update active addr fetch logic in overview api route
ashucoder9 Nov 13, 2025
2d67f00
minor
ashucoder9 Nov 13, 2025
7cde3e1
ship changes on the overview page, chart remains
ashucoder9 Nov 13, 2025
59d3318
update route for correct activeaddress metric fetching logic (imp)
ashucoder9 Nov 13, 2025
65158e4
chain metrics page for updated api routes
ashucoder9 Nov 13, 2025
57daf3f
limit brush slider to 100bars on daily selection
ashucoder9 Nov 13, 2025
9240a23
better
ashucoder9 Nov 13, 2025
90950d5
nits
ashucoder9 Nov 13, 2025
44cca4c
update categories for l1s
ashucoder9 Nov 13, 2025
4c96d04
minor
ashucoder9 Nov 13, 2025
ff255c6
Merge branch 'master' into stats-overview-update
ashucoder9 Nov 13, 2025
bf02bd0
Merge branch 'master' into stats-overview-update
ashucoder9 Nov 17, 2025
61fb9c3
Merge branch 'master' into stats-overview-update
ashucoder9 Nov 21, 2025
4bc149e
fix formatting issues during sorting
ashucoder9 Nov 21, 2025
865589c
fix overview cards data calculation logic
ashucoder9 Nov 21, 2025
0a608b3
fix app failure when sorting by tps/category
ashucoder9 Nov 21, 2025
be6f946
the great purge of inactive L1s
ashucoder9 Nov 21, 2025
b72e4a3
update validator globe
ashucoder9 Nov 21, 2025
45341aa
Merge branch 'master' into stats-overview-update
ashucoder9 Nov 22, 2025
a6faad9
Merge branch 'master' into stats-overview-update
owenwahlgren Nov 22, 2025
eeb8a53
Merge branch 'master' into stats-overview-update
ashucoder9 Nov 24, 2025
166fcd9
fix build errors
ashucoder9 Nov 24, 2025
2644964
nit
ashucoder9 Nov 24, 2025
e386666
fix alignment of header on chain stats
ashucoder9 Nov 24, 2025
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
544 changes: 363 additions & 181 deletions app/(home)/stats/overview/page.tsx

Large diffs are not rendered by default.

81 changes: 74 additions & 7 deletions app/api/chain-stats/[chainId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import { TimeSeriesDataPoint, TimeSeriesMetric, ICMDataPoint, ICMMetric, STATS_C
getTimestampsFromTimeRange, createTimeSeriesMetric, createICMMetric } from "@/types/stats";

interface ChainMetrics {
activeAddresses: TimeSeriesMetric;
activeAddresses: {
daily: TimeSeriesMetric;
weekly: TimeSeriesMetric;
monthly: TimeSeriesMetric;
};
activeSenders: TimeSeriesMetric;
cumulativeAddresses: TimeSeriesMetric;
cumulativeDeployers: TimeSeriesMetric;
Expand Down Expand Up @@ -96,7 +100,62 @@ async function getTimeSeriesData(
}
}

async function getICMData(chainId: string, timeRange: string, startTimestamp?: number, endTimestamp?: number): Promise<ICMDataPoint[]> {
// Separate active addresses fetching with proper time intervals (optimize other metrics as needed)
async function getActiveAddressesData(chainId: string, timeRange: string, interval: 'day' | 'week' | 'month', pageSize: number = 365, fetchAllPages: boolean = false): Promise<TimeSeriesDataPoint[]> {
try {
const { startTimestamp, endTimestamp } = getTimestampsFromTimeRange(timeRange);
let allResults: any[] = [];

const avalanche = new Avalanche({
network: "mainnet"
});

const rlToken = process.env.METRICS_BYPASS_TOKEN || '';
const params: any = {
chainId: chainId,
metric: 'activeAddresses',
startTimestamp,
endTimestamp,
timeInterval: interval,
pageSize,
};

if (rlToken) { params.rltoken = rlToken; }

const result = await avalanche.metrics.chains.getMetrics(params);

for await (const page of result) {
if (!page?.result?.results || !Array.isArray(page.result.results)) {
console.warn(`Invalid page structure for activeAddresses (${interval}) on chain ${chainId}:`, page);
continue;
}

allResults = allResults.concat(page.result.results);

if (!fetchAllPages) {
break;
}
}

return allResults
.sort((a: any, b: any) => b.timestamp - a.timestamp)
.map((result: any) => ({
timestamp: result.timestamp,
value: result.value || 0,
date: new Date(result.timestamp * 1000).toISOString().split('T')[0]
}));
} catch (error) {
console.warn(`Failed to fetch activeAddresses data for chain ${chainId} with interval ${interval}:`, error);
return [];
}
}

async function getICMData(
chainId: string,
timeRange: string,
startTimestamp?: number,
endTimestamp?: number
): Promise<ICMDataPoint[]> {
try {
let days: number;

Expand Down Expand Up @@ -215,7 +274,7 @@ export async function GET(
// Only refetch ICM data if timeRange changed (not for timestamp-based queries)
if (startTimestamp === undefined && endTimestamp === undefined && cached.icmTimeRange !== timeRange) {
try {
const newICMData = await getICMData(chainId, timeRange);
const newICMData = await getICMData(chainId, timeRange, startTimestamp, endTimestamp);
cached.data.icmMessages = createICMMetric(newICMData);
cached.icmTimeRange = timeRange;
cachedData.set(cacheKey, cached);
Expand Down Expand Up @@ -243,7 +302,9 @@ export async function GET(
const { pageSize, fetchAllPages } = config;

const [
activeAddressesData,
dailyActiveAddressesData,
weeklyActiveAddressesData,
monthlyActiveAddressesData,
activeSendersData,
cumulativeAddressesData,
cumulativeDeployersData,
Expand All @@ -262,7 +323,9 @@ export async function GET(
feesPaidData,
icmData,
] = await Promise.all([
getTimeSeriesData('activeAddresses', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
getActiveAddressesData(chainId, timeRange, 'day', pageSize, fetchAllPages),
getActiveAddressesData(chainId, timeRange, 'week', pageSize, fetchAllPages),
getActiveAddressesData(chainId, timeRange, 'month', pageSize, fetchAllPages),
getTimeSeriesData('activeSenders', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
getTimeSeriesData('cumulativeAddresses', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
getTimeSeriesData('cumulativeDeployers', chainId, timeRange, startTimestamp, endTimestamp, pageSize, fetchAllPages),
Expand All @@ -283,7 +346,11 @@ export async function GET(
]);

const metrics: ChainMetrics = {
activeAddresses: createTimeSeriesMetric(activeAddressesData),
activeAddresses: {
daily: createTimeSeriesMetric(dailyActiveAddressesData),
weekly: createTimeSeriesMetric(weeklyActiveAddressesData),
monthly: createTimeSeriesMetric(monthlyActiveAddressesData),
},
activeSenders: createTimeSeriesMetric(activeSendersData),
cumulativeAddresses: createTimeSeriesMetric(cumulativeAddressesData),
cumulativeDeployers: createTimeSeriesMetric(cumulativeDeployersData),
Expand Down Expand Up @@ -340,7 +407,7 @@ export async function GET(
if (cached) {
if (cached.icmTimeRange !== fallbackTimeRange) {
try {
const newICMData = await getICMData(chainId, fallbackTimeRange);
const newICMData = await getICMData(chainId, fallbackTimeRange, undefined, undefined);
cached.data.icmMessages = createICMMetric(newICMData);
cached.icmTimeRange = fallbackTimeRange;
cachedData.set(fallbackCacheKey, cached);
Expand Down
138 changes: 96 additions & 42 deletions app/api/overview-stats/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ interface ChainOverviewMetrics {
chainName: string;
chainLogoURI: string;
txCount: TimeSeriesMetric;
activeAddresses: TimeSeriesMetric;
activeAddresses: {
daily: TimeSeriesMetric;
weekly: TimeSeriesMetric;
monthly: TimeSeriesMetric;
};
icmMessages: ICMMetric;
validatorCount: number | string;
}
Expand All @@ -22,7 +26,11 @@ interface OverviewMetrics {
chains: ChainOverviewMetrics[];
aggregated: {
totalTxCount: TimeSeriesMetric;
totalActiveAddresses: TimeSeriesMetric;
totalActiveAddresses: {
daily: TimeSeriesMetric;
weekly: TimeSeriesMetric;
monthly: TimeSeriesMetric;
};
totalICMMessages: ICMMetric;
totalValidators: number;
activeChains: number;
Expand Down Expand Up @@ -89,10 +97,7 @@ async function getTimeSeriesData(
}

// separate active addresses fetching with proper time intervals
async function getActiveAddressesData(chainId: string, timeRange: string): Promise<TimeSeriesDataPoint[]> {
const intervalMapping = STATS_CONFIG.ACTIVE_ADDRESSES_INTERVALS[timeRange as keyof typeof STATS_CONFIG.ACTIVE_ADDRESSES_INTERVALS];
if (!intervalMapping) { return [] }

async function getActiveAddressesData(chainId: string, timeRange: string, interval: 'day' | 'week' | 'month'): Promise<TimeSeriesDataPoint[]> {
try {
const { startTimestamp, endTimestamp } = getTimestampsFromTimeRange(timeRange);
let allResults: any[] = [];
Expand All @@ -102,7 +107,7 @@ async function getActiveAddressesData(chainId: string, timeRange: string): Promi
metric: 'activeAddresses',
startTimestamp,
endTimestamp,
timeInterval: intervalMapping,
timeInterval: interval,
pageSize: 1,
};

Expand All @@ -122,7 +127,7 @@ async function getActiveAddressesData(chainId: string, timeRange: string): Promi
date: new Date(result.timestamp * 1000).toISOString().split('T')[0]
}));
} catch (error) {
console.warn(`Failed to fetch active addresses data for chain ${chainId}:`, error);
console.warn(`Failed to fetch active addresses data for chain ${chainId} with interval ${interval}:`, error);
return [];
}
}
Expand Down Expand Up @@ -194,9 +199,11 @@ async function fetchChainMetrics(chain: any, timeRange: string): Promise<ChainOv
const config = STATS_CONFIG.TIME_RANGES[timeRange as keyof typeof STATS_CONFIG.TIME_RANGES] || STATS_CONFIG.TIME_RANGES['30d'];
const { pageSize, fetchAllPages } = config;

const [txCountData, activeAddressesData, icmData, validatorCount] = await Promise.all([
const [txCountData, dailyAddresses, weeklyAddresses, monthlyAddresses, icmData, validatorCount] = await Promise.all([
getTimeSeriesData('txCount', chain.chainId, timeRange, pageSize, fetchAllPages),
getActiveAddressesData(chain.chainId, timeRange),
getActiveAddressesData(chain.chainId, timeRange, 'day'),
getActiveAddressesData(chain.chainId, timeRange, 'week'),
getActiveAddressesData(chain.chainId, timeRange, 'month'),
getICMData(chain.chainId, timeRange),
getValidatorCount(chain.subnetId),
]);
Expand All @@ -206,7 +213,11 @@ async function fetchChainMetrics(chain: any, timeRange: string): Promise<ChainOv
chainName: chain.chainName,
chainLogoURI: chain.logoUri,
txCount: createTimeSeriesMetricWithPeriodSum(txCountData), // Period sum for overview
activeAddresses: createTimeSeriesMetric(activeAddressesData),
activeAddresses: {
daily: createTimeSeriesMetric(dailyAddresses),
weekly: createTimeSeriesMetric(weeklyAddresses),
monthly: createTimeSeriesMetric(monthlyAddresses),
},
icmMessages: createICMMetricWithPeriodSum(icmData), // Period sum
validatorCount,
};
Expand Down Expand Up @@ -268,88 +279,127 @@ export async function GET(request: Request) {
}

const aggregatedTxData: TimeSeriesDataPoint[] = [];
const aggregatedAddressData: TimeSeriesDataPoint[] = [];
const aggregatedDailyAddressData: TimeSeriesDataPoint[] = [];
const aggregatedWeeklyAddressData: TimeSeriesDataPoint[] = [];
const aggregatedMonthlyAddressData: TimeSeriesDataPoint[] = [];
const aggregatedICMData: ICMDataPoint[] = [];

let totalValidators = 0;
let activeChains = 0;
let totalTxCountAllTime = 0;
let totalActiveAddressesAllTime = 0;
let totalDailyActiveAddressesAllTime = 0;
let totalWeeklyActiveAddressesAllTime = 0;
let totalMonthlyActiveAddressesAllTime = 0;
let totalICMMessagesAllTime = 0;

const dateMap = new Map<string, { tx: number; addresses: number; icm: number }>();
const dateMaps = {
tx: new Map<string, number>(),
daily: new Map<string, number>(),
weekly: new Map<string, number>(),
monthly: new Map<string, number>(),
icm: new Map<string, number>(),
};

chainMetrics.forEach(chain => {
if (typeof chain.validatorCount === 'number') {
totalValidators += chain.validatorCount;
}

const hasTx = chain.txCount.data.length > 0 && typeof chain.txCount.current_value === 'number' && chain.txCount.current_value > 0;
const hasAddresses = chain.activeAddresses.data.length > 0 && typeof chain.activeAddresses.current_value === 'number' && chain.activeAddresses.current_value > 0;
const hasAddresses = chain.activeAddresses.daily.data.length > 0 && typeof chain.activeAddresses.daily.current_value === 'number' && chain.activeAddresses.daily.current_value > 0;

if (hasTx || hasAddresses) { activeChains++; }

chain.txCount.data.forEach(point => {
const date = point.date;
const value = typeof point.value === 'number' ? point.value : 0;
const current = dateMap.get(date) || { tx: 0, addresses: 0, icm: 0 };
current.tx += value;
dateMap.set(date, current);
const current = dateMaps.tx.get(date) || 0;
dateMaps.tx.set(date, current + value);
totalTxCountAllTime += value;
});

chain.activeAddresses.data.forEach(point => {
chain.activeAddresses.daily.data.forEach(point => {
const date = point.date;
const value = typeof point.value === 'number' ? point.value : 0;
const current = dateMap.get(date) || { tx: 0, addresses: 0, icm: 0 };
current.addresses += value;
dateMap.set(date, current);
totalActiveAddressesAllTime += value;
const current = dateMaps.daily.get(date) || 0;
dateMaps.daily.set(date, current + value);
totalDailyActiveAddressesAllTime += value;
});

chain.activeAddresses.weekly.data.forEach(point => {
const date = point.date;
const value = typeof point.value === 'number' ? point.value : 0;
const current = dateMaps.weekly.get(date) || 0;
dateMaps.weekly.set(date, current + value);
totalWeeklyActiveAddressesAllTime += value;
});

chain.activeAddresses.monthly.data.forEach(point => {
const date = point.date;
const value = typeof point.value === 'number' ? point.value : 0;
const current = dateMaps.monthly.get(date) || 0;
dateMaps.monthly.set(date, current + value);
totalMonthlyActiveAddressesAllTime += value;
});

chain.icmMessages.data.forEach(point => {
const date = point.date;
const current = dateMap.get(date) || { tx: 0, addresses: 0, icm: 0 };
current.icm += point.messageCount;
dateMap.set(date, current);
const current = dateMaps.icm.get(date) || 0;
dateMaps.icm.set(date, current + point.messageCount);
totalICMMessagesAllTime += point.messageCount;
});
});

Array.from(dateMap.entries()).forEach(([date, values]) => {
Array.from(dateMaps.tx.entries()).forEach(([date, value]) => {
const timestamp = Math.floor(new Date(date).getTime() / 1000);
aggregatedTxData.push({
timestamp,
value: values.tx,
date
});
aggregatedTxData.push({ timestamp, value, date });
});

aggregatedAddressData.push({
timestamp,
value: values.addresses,
date
});
Array.from(dateMaps.daily.entries()).forEach(([date, value]) => {
const timestamp = Math.floor(new Date(date).getTime() / 1000);
aggregatedDailyAddressData.push({ timestamp, value, date });
});

Array.from(dateMaps.weekly.entries()).forEach(([date, value]) => {
const timestamp = Math.floor(new Date(date).getTime() / 1000);
aggregatedWeeklyAddressData.push({ timestamp, value, date });
});

Array.from(dateMaps.monthly.entries()).forEach(([date, value]) => {
const timestamp = Math.floor(new Date(date).getTime() / 1000);
aggregatedMonthlyAddressData.push({ timestamp, value, date });
});

Array.from(dateMaps.icm.entries()).forEach(([date, messageCount]) => {
const timestamp = Math.floor(new Date(date).getTime() / 1000);
aggregatedICMData.push({
timestamp,
date,
messageCount: values.icm,
messageCount,
incomingCount: 0,
outgoingCount: 0
});
});

// Sort by timestamp descending
aggregatedTxData.sort((a, b) => b.timestamp - a.timestamp);
aggregatedAddressData.sort((a, b) => b.timestamp - a.timestamp);
aggregatedDailyAddressData.sort((a, b) => b.timestamp - a.timestamp);
aggregatedWeeklyAddressData.sort((a, b) => b.timestamp - a.timestamp);
aggregatedMonthlyAddressData.sort((a, b) => b.timestamp - a.timestamp);
aggregatedICMData.sort((a, b) => b.timestamp - a.timestamp);

// Create aggregated metrics using period sum methods
const totalTxMetric = createTimeSeriesMetricWithPeriodSum(aggregatedTxData);
totalTxMetric.current_value = totalTxCountAllTime;

const totalAddressMetric = createTimeSeriesMetricWithPeriodSum(aggregatedAddressData);
totalAddressMetric.current_value = totalActiveAddressesAllTime;
const totalDailyAddressMetric = createTimeSeriesMetric(aggregatedDailyAddressData);
totalDailyAddressMetric.current_value = totalDailyActiveAddressesAllTime;

const totalWeeklyAddressMetric = createTimeSeriesMetric(aggregatedWeeklyAddressData);
totalWeeklyAddressMetric.current_value = totalWeeklyActiveAddressesAllTime;

const totalMonthlyAddressMetric = createTimeSeriesMetric(aggregatedMonthlyAddressData);
totalMonthlyAddressMetric.current_value = totalMonthlyActiveAddressesAllTime;

const totalICMMetric = createICMMetricWithPeriodSum(aggregatedICMData);
totalICMMetric.current_value = totalICMMessagesAllTime;
Expand All @@ -358,7 +408,11 @@ export async function GET(request: Request) {
chains: chainMetrics,
aggregated: {
totalTxCount: totalTxMetric,
totalActiveAddresses: totalAddressMetric,
totalActiveAddresses: {
daily: totalDailyAddressMetric,
weekly: totalWeeklyAddressMetric,
monthly: totalMonthlyAddressMetric,
},
totalICMMessages: totalICMMetric,
totalValidators,
activeChains
Expand Down
Loading