diff --git a/app/(home)/stats/avax-token/layout.tsx b/app/(home)/stats/avax-token/layout.tsx new file mode 100644 index 00000000000..51176f84c76 --- /dev/null +++ b/app/(home)/stats/avax-token/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import { createMetadata } from "@/utils/metadata"; + +export const metadata: Metadata = createMetadata({ + title: "AVAX Token Stats", + description: + "Track AVAX token supply, staking, and burn metrics including total supply, circulating supply and fees burned across all chains.", + openGraph: { + url: "/stats/avax-token", + images: { + alt: "AVAX Token Stats", + url: "/api/og/stats/c-chain?title=AVAX Token Stats&description=Track AVAX token supply, staking, and burn metrics", + width: 1280, + height: 720, + }, + }, + twitter: { + images: { + alt: "AVAX Token Stats", + url: "/api/og/stats/c-chain?title=AVAX Token Stats&description=Track AVAX token supply, staking, and burn metrics", + width: 1280, + height: 720, + }, + }, +}); + +export default function AvaxTokenLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/(home)/stats/avax-token/page.tsx b/app/(home)/stats/avax-token/page.tsx new file mode 100644 index 00000000000..1dbb0953e80 --- /dev/null +++ b/app/(home)/stats/avax-token/page.tsx @@ -0,0 +1,837 @@ +"use client"; + +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Tooltip as UITooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { CircleDotDashed, CircleFadingPlus, Lock, BadgeDollarSign, RefreshCw, Flame, Award, MessageSquareIcon, Server, Unlock, HandCoins, Info, ArrowUpRight } from "lucide-react"; +import { useEffect, useState, useMemo } from "react"; +import Image from "next/image"; +import { Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, Brush, LineChart, Line } from "recharts"; +import { StatsBubbleNav } from "@/components/stats/stats-bubble.config"; +import { AvalancheLogo } from "@/components/navigation/avalanche-logo"; + +interface AvaxSupplyData { + totalSupply: string; + circulatingSupply: string; + totalPBurned: string; + totalCBurned: string; + totalXBurned: string; + totalStaked: string; + totalLocked: string; + totalRewards: string; + lastUpdated: string; + genesisUnlock: string; + l1ValidatorFees: string; + price: number; + priceChange24h: number; +} + +interface FeeDataPoint { + date: string; + timestamp: number; + value: number; +} + +interface CChainFeesResponse { + feesPaid: { + data: Array<{ date: string; timestamp: number; value: string | number }>; + }; +} + +interface ICMFeesResponse { + data: Array<{ + date: string; + timestamp: number; + feesPaid: number; + txCount: number; + }>; + totalFees: number; + lastUpdated: string; +} + +type Period = "D" | "W" | "M"; + +export default function AvaxTokenPage() { + const [data, setData] = useState(null); + const [cChainFees, setCChainFees] = useState([]); + const [icmFees, setICMFees] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [period, setPeriod] = useState("D"); + const [brushIndexes, setBrushIndexes] = useState<{ + startIndex: number; + endIndex: number; + } | null>(null); + + const fetchData = async () => { + try { + setLoading(true); + setError(null); + + const [supplyRes, cChainRes, icmRes] = await Promise.all([ + fetch("/api/avax-supply"), + fetch("/api/chain-stats/43114?timeRange=all"), + fetch("/api/icm-contract-fees?timeRange=all"), + ]); + + if (!supplyRes.ok || !cChainRes.ok) { + throw new Error("Failed to fetch required data"); + } + + const supplyData = await supplyRes.json(); + const cChainData: CChainFeesResponse = await cChainRes.json(); + + setData(supplyData); + + const cChainFeesData: FeeDataPoint[] = cChainData.feesPaid.data + .map((item) => ({ + date: item.date, + timestamp: item.timestamp, + value: typeof item.value === "string" ? parseFloat(item.value) : item.value, + })) + .reverse(); + + setCChainFees(cChainFeesData); + + if (icmRes.ok) { + const icmData: ICMFeesResponse = await icmRes.json(); + if (icmData.data && Array.isArray(icmData.data)) { + const icmFeesData: FeeDataPoint[] = icmData.data + .map((item) => ({ + date: item.date, + timestamp: item.timestamp, + value: item.feesPaid, + })) + .reverse(); + setICMFees(icmFeesData); + } + } + } catch (err) { + setError(err instanceof Error ? err.message : "An error occurred"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, []); + + const formatNumber = (value: string | number): string => { + const num = typeof value === "string" ? parseFloat(value) : value; + if (isNaN(num)) return "N/A"; + + if (num >= 1e9) { + return `${(num / 1e9).toFixed(2)}B`; + } else if (num >= 1e6) { + return `${(num / 1e6).toFixed(2)}M`; + } else if (num >= 1e3) { + return `${(num / 1e3).toFixed(2)}K`; + } + return num.toLocaleString(undefined, { maximumFractionDigits: 2 }); + }; + + const formatFullNumber = (value: string | number): string => { + const num = typeof value === "string" ? parseFloat(value) : value; + if (isNaN(num)) return "N/A"; + return num.toLocaleString(undefined, { maximumFractionDigits: 2 }); + }; + + const formatUSD = (avaxAmount: string | number): string => { + const amount = typeof avaxAmount === "string" ? parseFloat(avaxAmount) : avaxAmount; + const price = data?.price || 0; + if (isNaN(amount) || price === 0) return ""; + const usdValue = amount * price; + + if (usdValue >= 1e9) { + return `$${(usdValue / 1e9).toFixed(1)} Billion USD`; + } else if (usdValue >= 1e6) { + return `$${(usdValue / 1e6).toFixed(1)} Million USD`; + } else if (usdValue >= 1e3) { + return `$${(usdValue / 1e3).toFixed(1)}K USD`; + } + return `$${usdValue.toLocaleString(undefined, { + maximumFractionDigits: 2, + })} USD`; + }; + + const calculatePercentage = (part: string, total: string): string => { + const partNum = parseFloat(part); + const totalNum = parseFloat(total); + if (isNaN(partNum) || isNaN(totalNum) || totalNum === 0) return "0"; + return ((partNum / totalNum) * 100).toFixed(2); + }; + + const aggregatedFeeData = useMemo(() => { + if (cChainFees.length === 0 && icmFees.length === 0) return []; + + const allDates = new Set([...cChainFees.map((d) => d.date), ...icmFees.map((d) => d.date)]); + const cChainMap = new Map(cChainFees.map((d) => [d.date, d.value])); + const icmMap = new Map(icmFees.map((d) => [d.date, d.value])); + + let mergedData = Array.from(allDates) + .map((date) => ({ + date, + cChainFees: cChainMap.get(date) || 0, + icmFees: icmMap.get(date) || 0, + })) + .sort((a, b) => a.date.localeCompare(b.date)); + + if (period === "D") return mergedData; + + const grouped = new Map< + string, + { cChainSum: number; icmSum: number; date: string } + >(); + + mergedData.forEach((point) => { + const date = new Date(point.date); + let key: string; + + if (period === "W") { + const weekStart = new Date(date); + weekStart.setDate(date.getDate() - date.getDay()); + key = weekStart.toISOString().split("T")[0]; + } else { + key = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart( + 2, + "0" + )}`; + } + + if (!grouped.has(key)) { + grouped.set(key, { cChainSum: 0, icmSum: 0, date: key }); + } + + const group = grouped.get(key)!; + group.cChainSum += point.cChainFees; + group.icmSum += point.icmFees; + }); + + return Array.from(grouped.values()) + .map((group) => ({ + date: group.date, + cChainFees: group.cChainSum, + icmFees: group.icmSum, + })) + .sort((a, b) => a.date.localeCompare(b.date)); + }, [cChainFees, icmFees, period]); + + useEffect(() => { + if (aggregatedFeeData.length === 0) return; + + if (period === "D") { + const daysToShow = 90; + setBrushIndexes({ + startIndex: Math.max(0, aggregatedFeeData.length - daysToShow), + endIndex: aggregatedFeeData.length - 1, + }); + } else { + setBrushIndexes({ + startIndex: 0, + endIndex: aggregatedFeeData.length - 1, + }); + } + }, [period, aggregatedFeeData.length]); + + const displayData = brushIndexes ? aggregatedFeeData.slice(brushIndexes.startIndex, brushIndexes.endIndex + 1) : aggregatedFeeData; + + const formatXAxis = (value: string) => { + const date = new Date(value); + if (period === "M") { + return date.toLocaleDateString("en-US", { + month: "short", + year: "2-digit", + }); + } + return date.toLocaleDateString("en-US", { month: "short", day: "numeric" }); + }; + + const formatTooltipDate = (value: string) => { + const date = new Date(value); + + if (period === "M") { + return date.toLocaleDateString("en-US", { + month: "long", + year: "numeric", + }); + } + + if (period === "W") { + const endDate = new Date(date); + endDate.setDate(date.getDate() + 6); + + const startMonth = date.toLocaleDateString("en-US", { month: "long" }); + const endMonth = endDate.toLocaleDateString("en-US", { month: "long" }); + const startDay = date.getDate(); + const endDay = endDate.getDate(); + const year = endDate.getFullYear(); + + if (startMonth === endMonth) { + return `${startMonth} ${startDay}-${endDay}, ${year}`; + } else { + return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${year}`; + } + } + + return date.toLocaleDateString("en-US", { + day: "numeric", + month: "long", + year: "numeric", + }); + }; + + const totalICMFees = useMemo( + () => icmFees.reduce((sum, item) => sum + item.value, 0), + [icmFees] + ); + + // show the actual total supply minus the total burned + const actualTotalSupply = data ? 720000000 - (parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)) : 0; + + const metrics = data + ? [ + { + label: "AVAX Price", + value: data.price > 0 ? `$${data.price.toFixed(2)}` : "N/A", + fullValue: data.price > 0 ? `$${data.price.toFixed(4)}` : "N/A", + icon: BadgeDollarSign, + subtext:data.priceChange24h !== 0 ? `${data.priceChange24h > 0 ? "+" : ""}${data.priceChange24h.toFixed(2)}% (24h)` : "USD", + color: data.priceChange24h >= 0 ? "#10B981" : "#EF4444", + tooltip: "Current AVAX price in USD from CoinGecko", + }, + { + label: "Total Supply", + value: formatNumber(actualTotalSupply), + fullValue: formatFullNumber(actualTotalSupply), + icon: CircleDotDashed, + subtext: data.price > 0 ? formatUSD(actualTotalSupply) : "AVAX", + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#E84142", + tooltip: "Total supply minus the burned tokens from P-Chain, C-Chain, and X-Chain", + }, + { + label: "Circulating Supply", + value: formatNumber(data.circulatingSupply), + fullValue: formatFullNumber(data.circulatingSupply), + icon: CircleFadingPlus, + subtext: data.price > 0 ? formatUSD(data.circulatingSupply) : `${calculatePercentage(data.circulatingSupply, data.totalSupply)}% of total`, + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#3752AC", + tooltip: "AVAX tokens actively circulating in the market", + }, + { + label: "Genesis Unlock", + value: formatNumber(data.genesisUnlock), + fullValue: formatFullNumber(data.genesisUnlock), + icon: Unlock, + subtext: data.price > 0 ? formatUSD(data.genesisUnlock) : "AVAX", + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#E84142", + tooltip: "Amount of AVAX un during the genesis event", + }, + { + label: "Total Staked", + value: formatNumber(data.totalStaked), + fullValue: formatFullNumber(data.totalStaked), + icon: HandCoins, + subtext:data.price > 0 ? formatUSD(data.totalStaked) : `${calculatePercentage(data.totalStaked, data.circulatingSupply)}% of circulating`, + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#8B5CF6", + tooltip: "Total AVAX staked and delegated to validators on the Primary Network", + }, + { + label: "Total Locked", + value: formatNumber(data.totalLocked), + fullValue: formatFullNumber(data.totalLocked), + icon: Lock, + subtext: data.price > 0 ? formatUSD(data.totalLocked) : `${calculatePercentage(data.totalLocked, data.circulatingSupply)}% of circulating`, + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#10B981", + tooltip: "Total AVAX locked in UTXOs on P-Chain and X-Chain", + }, + { + label: "Total Rewards", + value: formatNumber(data.totalRewards), + fullValue: formatFullNumber(data.totalRewards), + icon: Award, + subtext: data.price > 0 ? formatUSD(data.totalRewards) : "AVAX", + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#F59E0B", + tooltip: "Cumulative staking rewards issued to validators and delegators", + }, + { + label: "Total Burned", + value: formatNumber(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)), + fullValue: formatFullNumber(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)), + icon: Flame, + subtext: + data.price > 0 + ? formatUSD(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)) + : `${calculatePercentage((parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)).toString(), data.totalSupply)}% of genesis supply`, + subtextTooltip: data.price > 0 ? "at current prices" : undefined, + color: "#F59E0B", + tooltip: "Total AVAX burned across P-Chain, C-Chain, and X-Chain", + }, + ] + : []; + + const chainData = data + ? [ + { + chain: "C-Chain", + burned: formatFullNumber(data.totalCBurned), + percentage: parseFloat(calculatePercentage(data.totalCBurned,(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)).toString())), + color: "bg-[#E84142]", + logo: "https://images.ctfassets.net/gcj8jwzm6086/5VHupNKwnDYJvqMENeV7iJ/3e4b8ff10b69bfa31e70080a4b142cd0/avalanche-avax-logo.svg", + }, + { + chain: "P-Chain", + burned: formatFullNumber(data.totalPBurned), + percentage: parseFloat(calculatePercentage(data.totalPBurned,(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)).toString())), + color: "bg-[#3752AC]", + logo: "https://images.ctfassets.net/gcj8jwzm6086/42aMwoCLblHOklt6Msi6tm/1e64aa637a8cead39b2db96fe3225c18/pchain-square.svg", + }, + { + chain: "X-Chain", + burned: formatFullNumber(data.totalXBurned), + percentage: parseFloat(calculatePercentage(data.totalXBurned,(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned)).toString())), + color: "bg-[#10B981]", + logo: "https://images.ctfassets.net/gcj8jwzm6086/5xiGm7IBR6G44eeVlaWrxi/1b253c4744a3ad21a278091e3119feba/xchain-square.svg", + }, + ] + : []; + + if (loading) { + return ( +
+
+
+
+
+
+
+
+
+
+
+ +
+ {[1, 2, 3, 4, 5, 6, 7, 8].map((i) => ( +
+
+
+
+
+
+
+ ))} +
+
+ +
+ ); + } + + if (error) { + return ( +
+
+ + +

{error}

+ +
+
+
+ +
+ ); + } + + return ( +
+
+
+
+
+
+ +
+
+

Avalanche (AVAX)

+ +
+
+ +
+ +
+
+
+ +
+ +
+ {metrics.map((metric) => { + const Icon = metric.icon; + return ( +
+ + +
+ +

+ {metric.label} +

+ +
+
+ +

{metric.tooltip}

+
+
+

+ {metric.value} +

+ {metric.subtextTooltip ? ( + + +

+ {metric.subtext} +

+
+ +

{metric.subtextTooltip}

+
+
+ ) : ( +

+ {metric.subtext} +

+ )} +
+ ); + })} +
+
+ +
+
+ +
+
+
+

Network Fees Paid

+

+ C-Chain and ICM contract fees over time +

+
+
+ {(["D", "W", "M"] as const).map((p) => ( + + ))} +
+
+
+ +
+ + + + + formatNumber(value)} + className="text-xs text-neutral-600 dark:text-neutral-400" + tick={{ + className: "fill-neutral-600 dark:fill-neutral-400", + }} + /> + { + if (!active || !payload?.[0]) return null; + const formattedDate = formatTooltipDate(payload[0].payload.date); + return ( +
+
+
+ {formattedDate} +
+
+
+ + C-Chain:{" "} + + + {formatNumber(payload[0].payload.cChainFees)}{" "}AVAX + +
+
+
+ + ICM:{" "} + + + {formatNumber(payload[0].payload.icmFees)}{" "}AVAX + +
+
+
+ ); + }} + /> + + + + +
+ +
+ + + { + if ( + e.startIndex !== undefined && + e.endIndex !== undefined + ) { + setBrushIndexes({ + startIndex: e.startIndex, + endIndex: e.endIndex, + }); + } + }} + travellerWidth={8} + tickFormatter={formatXAxis} + > + + + + + + +
+ + +
+ +
+ +
+

Fees Burned by Chain

+
+ +
+ {chainData.map((chain) => ( +
+
+
+
+ {`${chain.chain} +
+
+

+ {chain.chain} +

+

+ {chain.burned} AVAX +

+
+
+ + {chain.percentage.toFixed(2)}% + +
+ +
+
+
+
+ ))} + +
+
+ Total Burned + + {data && formatFullNumber(parseFloat(data.totalPBurned) + parseFloat(data.totalCBurned) + parseFloat(data.totalXBurned))}{" "}AVAX + +
+
+
+ + + + {data && ( + + +
+
+
+ +
+
+

L1 Validator Fees

+

+ All-time fees paid by L1 validators +

+
+
+
+

+ {formatNumber(data.l1ValidatorFees)} AVAX +

+ {data.price > 0 && ( +

+ {formatUSD(data.l1ValidatorFees)} +

+ )} +
+
+
+
+ )} + + + +
+
+
+ +
+
+

Total ICM Fees

+

+ All-time fees from Interchain Messages +

+
+
+
+

+ {formatNumber(totalICMFees)} AVAX +

+ {data && data.price > 0 && ( +

+ {formatUSD(totalICMFees)} +

+ )} +
+
+
+
+
+
+
+
+ + +
+ ); +} diff --git a/app/api/avax-supply/route.ts b/app/api/avax-supply/route.ts new file mode 100644 index 00000000000..f1d310cb604 --- /dev/null +++ b/app/api/avax-supply/route.ts @@ -0,0 +1,68 @@ +import { NextResponse } from "next/server"; + +interface CoinGeckoResponse { + "avalanche-2": { + usd: number; + usd_24h_change: number; + }; +} + +export async function GET() { + try { + const [supplyResponse, priceResponse] = await Promise.all([ + fetch("https://data-api.avax.network/v1/avax/supply", { + headers: { + accept: "application/json", + }, + next: { revalidate: 86400 }, // 24 hours - supply data changes slowly + }), + fetch( + "https://api.coingecko.com/api/v3/simple/price?ids=avalanche-2&vs_currencies=usd&include_24hr_change=true", + { + headers: { + Accept: "application/json", + }, + next: { revalidate: 60 }, // 1 minute - price data changes frequently + } + ), + ]); + + if (!supplyResponse.ok) { + throw new Error(`Failed to fetch AVAX supply data: ${supplyResponse.status}`); + } + + const supplyData = await supplyResponse.json(); + + let priceData = { + price: 0, + change24h: 0, + }; + + if (priceResponse.ok) { + try { + const priceJson: CoinGeckoResponse = await priceResponse.json(); + priceData = { + price: priceJson["avalanche-2"]?.usd || 0, + change24h: priceJson["avalanche-2"]?.usd_24h_change || 0, + }; + } catch (priceError) { + console.warn("Failed to parse price data:", priceError); + } + } else { + console.warn("Price API returned non-ok response"); + } + + return NextResponse.json({ + ...supplyData, + price: priceData.price, + priceChange24h: priceData.change24h, + }); + } catch (error) { + console.error("Error fetching AVAX supply:", error); + return NextResponse.json( + { error: "Failed to fetch AVAX supply data" }, + { status: 500 } + ); + } +} + diff --git a/app/api/icm-contract-fees/route.ts b/app/api/icm-contract-fees/route.ts new file mode 100644 index 00000000000..bea8d3ccbaf --- /dev/null +++ b/app/api/icm-contract-fees/route.ts @@ -0,0 +1,116 @@ +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 + +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(); + + 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 } + ); + } +} + diff --git a/components/stats/stats-bubble.config.tsx b/components/stats/stats-bubble.config.tsx index 6d5f5bcae80..1f14eca9efc 100644 --- a/components/stats/stats-bubble.config.tsx +++ b/components/stats/stats-bubble.config.tsx @@ -9,6 +9,7 @@ export const statsBubbleConfig: BubbleNavigationConfig = { { id: "c-chain", label: "C-Chain", href: "/stats/primary-network/c-chain" }, { id: "playground", label: "Playground", href: "/stats/playground" }, { id: "validators", label: "Validators", href: "/stats/validators" }, + { id: "avax-token", label: "AVAX", href: "/stats/avax-token" }, ], activeColor: "bg-blue-600", darkActiveColor: "dark:bg-blue-500", diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c7cac..c4b7818fbb2 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.