diff --git a/src/app/(app)/leaderboard/LeaderboardPageClient.tsx b/src/app/(app)/leaderboard/LeaderboardPageClient.tsx
index b328bc4f..cd015002 100644
--- a/src/app/(app)/leaderboard/LeaderboardPageClient.tsx
+++ b/src/app/(app)/leaderboard/LeaderboardPageClient.tsx
@@ -19,7 +19,7 @@ import { LeaderboardTable } from "@/components/dashboard/LeaderboardTable"
interface LeaderboardPageClientProps {
preloadedActiveTraders: number | null
preloadedSwapVolumeEth: number | null
- preloadedEthPrice: number | null
+ preloadedSwapVolumeUsd: number | null
preloadedLeaderboard: Array<{
rank: number
wallet: string
@@ -34,7 +34,7 @@ interface LeaderboardPageClientProps {
export function LeaderboardPageClient({
preloadedActiveTraders,
preloadedSwapVolumeEth,
- preloadedEthPrice,
+ preloadedSwapVolumeUsd,
preloadedLeaderboard,
}: LeaderboardPageClientProps) {
const [isMounted, setIsMounted] = useState(false)
@@ -47,19 +47,17 @@ export function LeaderboardPageClient({
// Prepare initial data for React Query hydration
const initialLeaderboardData =
preloadedLeaderboard.length > 0
- ? {
- success: true,
- leaderboard: preloadedLeaderboard,
- ethPrice: preloadedEthPrice,
- }
+ ? { success: true, leaderboard: preloadedLeaderboard }
: undefined
const initialStatsData =
- preloadedActiveTraders !== null || preloadedSwapVolumeEth !== null || preloadedEthPrice !== null
+ preloadedActiveTraders !== null ||
+ preloadedSwapVolumeEth !== null ||
+ preloadedSwapVolumeUsd !== null
? {
activeTraders: preloadedActiveTraders,
swapVolumeEth: preloadedSwapVolumeEth,
- ethPrice: preloadedEthPrice,
+ swapVolumeUsd: preloadedSwapVolumeUsd,
}
: undefined
diff --git a/src/app/(app)/leaderboard/page.tsx b/src/app/(app)/leaderboard/page.tsx
index 80e3899b..2f1771ab 100644
--- a/src/app/(app)/leaderboard/page.tsx
+++ b/src/app/(app)/leaderboard/page.tsx
@@ -2,7 +2,6 @@ import { Suspense } from "react"
import {
getActiveTraders,
getCumulativeSwapVolume,
- getEthPrice,
getLeaderboardTop15,
} from "@/lib/analytics-server"
import { LeaderboardPageClient } from "./LeaderboardPageClient"
@@ -33,25 +32,16 @@ async function StatsLoader({
}>
}) {
// Start all stats fetches in parallel - they'll complete independently
- // Don't use Promise.all - let them resolve individually for better streaming
const activeTradersPromise = getActiveTraders()
- const swapVolumeEthPromise = getCumulativeSwapVolume()
- const ethPricePromise = getEthPrice()
+ const swapVolumePromise = getCumulativeSwapVolume()
- // Wait for all stats - but they're already loading in parallel
- // In a true streaming setup, we'd render each as it completes
- // For now, we wait for all but they load in parallel
- const [activeTraders, swapVolumeEth, ethPrice] = await Promise.all([
- activeTradersPromise,
- swapVolumeEthPromise,
- ethPricePromise,
- ])
+ const [activeTraders, swapVolume] = await Promise.all([activeTradersPromise, swapVolumePromise])
return (
)
@@ -88,7 +78,7 @@ export default function LeaderboardPage() {
}
diff --git a/src/app/api/analytics/leaderboard/route.ts b/src/app/api/analytics/leaderboard/route.ts
index 863c9ede..fc50fd92 100644
--- a/src/app/api/analytics/leaderboard/route.ts
+++ b/src/app/api/analytics/leaderboard/route.ts
@@ -1,5 +1,4 @@
import { NextRequest, NextResponse } from "next/server"
-import { getEthPrice } from "@/lib/analytics-server"
import {
getLeaderboard,
getUserLeaderboardData,
@@ -56,14 +55,10 @@ export async function GET(request: NextRequest) {
// Get main leaderboard (top 15)
const leaderboardRows = await getLeaderboard(15)
- // Get ETH price for USD conversion
- const ethPrice = await getEthPrice()
-
- // Transform leaderboard rows using shared transformation utility
- // useTotalVolume=true means we use total_swap_vol_eth instead of swap_vol_eth_24h
+ // Transform leaderboard rows (USD from DB columns)
+ // useTotalVolume=true means we use total_swap_vol_usd
let leaderboard = transformLeaderboardRows(
leaderboardRows,
- ethPrice,
currentUserAddress,
true // Use total volume
)
@@ -90,8 +85,7 @@ export async function GET(request: NextRequest) {
} else {
// Fetch user's data separately to add them to the leaderboard
try {
- // Fetch user data, rank, and next rank volume in parallel for speed
- const [userData, actualRank, nextRankVolEth] = await Promise.all([
+ const [userData, actualRank, nextRankThreshold] = await Promise.all([
getUserLeaderboardData(currentUserAddress),
getUserRank(currentUserAddress),
getNextRankThreshold(currentUserAddress),
@@ -99,31 +93,25 @@ export async function GET(request: NextRequest) {
if (actualRank !== null && userData && userData[0] > 0) {
const userTotalSwapVolEth = Number(userData[0]) || 0
- const userSwapCount = Number(userData[1]) || 0
- const userChange24hPct = Number(userData[3]) || 0
-
- const userTotalSwapVolUsd =
- ethPrice !== null ? userTotalSwapVolEth * ethPrice : userTotalSwapVolEth
+ const userTotalSwapVolUsd = Number(userData[1]) || 0
+ const userSwapCount = Number(userData[2]) || 0
+ const userChange24hPct = Number(userData[5]) || 0
userPosition = actualRank
userVolume = userTotalSwapVolUsd
userChange24h = userChange24hPct
- // Find the next rank user's volume (for all positions > 1)
if (userPosition > 1) {
if (userPosition <= 15) {
- // User is in top 15, find next rank user from leaderboard
const nextRankUser = leaderboard.find((entry) => entry.rank === userPosition! - 1)
if (nextRankUser) {
nextRankVolume = nextRankUser.swapVolume24h
}
- } else if (nextRankVolEth !== null) {
- // User is outside top 15, use the next rank threshold from query
- nextRankVolume = ethPrice !== null ? nextRankVolEth * ethPrice : nextRankVolEth
+ } else if (nextRankThreshold.usd !== null) {
+ nextRankVolume = nextRankThreshold.usd
}
}
- // Add current user to leaderboard if not in top 15
if (userPosition > 15 && currentUserAddress) {
leaderboard.push({
rank: userPosition,
@@ -149,7 +137,6 @@ export async function GET(request: NextRequest) {
userPosition,
userVolume,
nextRankVolume,
- ethPrice,
}
// Cache the response
diff --git a/src/app/api/analytics/user/[address]/route.ts b/src/app/api/analytics/user/[address]/route.ts
index 6043caa5..33d93f49 100644
--- a/src/app/api/analytics/user/[address]/route.ts
+++ b/src/app/api/analytics/user/[address]/route.ts
@@ -1,6 +1,5 @@
import { NextRequest, NextResponse } from "next/server"
import { env } from "@/env/server"
-import { getEthPrice } from "@/lib/analytics-server"
import { getUserSwapVolume } from "@/lib/analytics/services/users.service"
import { AnalyticsClientError } from "@/lib/analytics/client"
@@ -56,12 +55,14 @@ export async function GET(
const totalTxs = fastRpcData.txn_count || 0
const swapTxs = fastRpcData.swap_count || 0
- // Get swap volume from analytics database
+ // Get swap volume from analytics database (ETH and USD)
let totalSwapVolEth = 0
+ let totalSwapVolUsd = 0
try {
- totalSwapVolEth = await getUserSwapVolume(normalizedAddress)
+ const vol = await getUserSwapVolume(normalizedAddress)
+ totalSwapVolEth = vol.eth
+ totalSwapVolUsd = vol.usd
} catch (error) {
- // Log error but don't fail the request - return partial data
if (error instanceof AnalyticsClientError) {
console.error("Analytics DB API error:", error.message)
} else {
@@ -69,13 +70,11 @@ export async function GET(
}
}
- const ethPrice = await getEthPrice()
-
return NextResponse.json({
totalTxs,
swapTxs,
totalSwapVolEth,
- ethPrice,
+ totalSwapVolUsd,
})
} catch (error) {
console.error("Error fetching user metrics:", error)
diff --git a/src/app/api/analytics/volume/swap/route.ts b/src/app/api/analytics/volume/swap/route.ts
index 7498ce30..7b567c00 100644
--- a/src/app/api/analytics/volume/swap/route.ts
+++ b/src/app/api/analytics/volume/swap/route.ts
@@ -6,13 +6,14 @@ export async function GET() {
try {
const cumulativeSwapVolume = await getSwapVolume()
- if (cumulativeSwapVolume === null) {
+ if (cumulativeSwapVolume.eth === null && cumulativeSwapVolume.usd === null) {
return NextResponse.json({ error: "No data returned from analytics API" }, { status: 500 })
}
return NextResponse.json({
success: true,
- cumulativeSwapVolEth: cumulativeSwapVolume,
+ cumulativeSwapVolEth: cumulativeSwapVolume.eth,
+ cumulativeSwapVolUsd: cumulativeSwapVolume.usd,
})
} catch (error) {
console.error("Error fetching transaction volume analytics:", error)
diff --git a/src/app/claim/page.tsx b/src/app/claim/page.tsx
index 06922cdf..d4078a4d 100644
--- a/src/app/claim/page.tsx
+++ b/src/app/claim/page.tsx
@@ -22,7 +22,8 @@ const ClaimPage = async () => {
const totalSupplyString = totalSupply !== null ? totalSupply.toString() : null
const transactionsString =
cumulativeTransactions !== null ? cumulativeTransactions.toString() : null
- const swapVolumeString = cumulativeSwapVolume !== null ? cumulativeSwapVolume.toString() : null
+ const swapVolumeUsdString =
+ cumulativeSwapVolume?.usd != null ? cumulativeSwapVolume.usd.toString() : null
const ethPriceString = ethPrice !== null ? ethPrice.toString() : null
const totalPointsString = totalPoints !== null ? totalPoints.toString() : null
@@ -30,7 +31,7 @@ const ClaimPage = async () => {
diff --git a/src/components/claim/ClaimPageClient.tsx b/src/components/claim/ClaimPageClient.tsx
index 557778a5..b7fdfd8e 100644
--- a/src/components/claim/ClaimPageClient.tsx
+++ b/src/components/claim/ClaimPageClient.tsx
@@ -5,13 +5,12 @@ import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Zap, TrendingUp, Users, Shield } from "lucide-react"
import { useRouter } from "next/navigation"
-import { DEFAULT_ETH_PRICE_USD } from "@/lib/constants"
import { formatNumber } from "@/lib/utils"
interface ClaimPageClientProps {
initialTotalSupply: string | null
initialTransactions: string | null
- initialSwapVolume: string | null
+ initialSwapVolumeUsd: string | null
initialEthPrice: string | null
initialTotalPoints: string | null
}
@@ -19,7 +18,7 @@ interface ClaimPageClientProps {
export const ClaimPageClient = ({
initialTotalSupply,
initialTransactions,
- initialSwapVolume,
+ initialSwapVolumeUsd,
initialEthPrice,
initialTotalPoints,
}: ClaimPageClientProps) => {
@@ -140,19 +139,12 @@ export const ClaimPageClient = ({
- {initialSwapVolume !== null
- ? (() => {
- const swapVolume = Number(initialSwapVolume)
- // Use ETH price if available, otherwise fallback to default price
- const price =
- initialEthPrice !== null ? Number(initialEthPrice) : DEFAULT_ETH_PRICE_USD
- const totalUsd = swapVolume * price
- return `$${totalUsd.toLocaleString(undefined, {
- maximumFractionDigits: 1,
- notation: "compact",
- compactDisplay: "short",
- })}`
- })()
+ {initialSwapVolumeUsd != null
+ ? `$${Number(initialSwapVolumeUsd).toLocaleString(undefined, {
+ maximumFractionDigits: 1,
+ notation: "compact",
+ compactDisplay: "short",
+ })}`
: "$0"}
Swap Volume
diff --git a/src/components/dashboard/LeaderboardTable.tsx b/src/components/dashboard/LeaderboardTable.tsx
index 421f7115..c343df52 100644
--- a/src/components/dashboard/LeaderboardTable.tsx
+++ b/src/components/dashboard/LeaderboardTable.tsx
@@ -31,13 +31,12 @@ interface LeaderboardData {
userVolume?: number | null
userPosition?: number | null
nextRankVolume?: number | null
- ethPrice?: number | null
}
interface LeaderboardStats {
activeTraders: number | null
swapVolumeEth: number | null
- ethPrice: number | null
+ swapVolumeUsd: number | null
}
interface LeaderboardTableProps {
@@ -62,20 +61,15 @@ export const LeaderboardTable = ({
// Get data from props (React Query managed)
const activeTraders = statsData?.activeTraders ?? null
const swapVolumeEth = statsData?.swapVolumeEth ?? null
- const ethPrice = statsData?.ethPrice ?? leaderboardData?.ethPrice ?? null
+ const swapVolumeUsd = statsData?.swapVolumeUsd ?? null
const lbData = leaderboardData?.leaderboard || []
const userVol = leaderboardData?.userVolume ?? null
const userPos = leaderboardData?.userPosition ?? null
const nextRankVol = leaderboardData?.nextRankVolume ?? null
- // Only userSwapTxs needs separate state since it's not in leaderboardData
const [userSwapTxs, setUserSwapTxs] = useState
(null)
- // Computed Values
- const totalVol = useMemo(
- () => (swapVolumeEth && ethPrice ? swapVolumeEth * ethPrice : null),
- [swapVolumeEth, ethPrice]
- )
+ const totalVol = useMemo(() => swapVolumeUsd ?? null, [swapVolumeUsd])
// Apply testing multiplier to user volume
const adjustedUserVol = useMemo(
@@ -117,7 +111,7 @@ export const LeaderboardTable = ({
swapCount: userSwapTxs !== null ? userSwapTxs : undefined,
change24h: 0,
isCurrentUser: true,
- ethValue: ethPrice && adjustedUserVol ? adjustedUserVol / ethPrice : undefined,
+ ethValue: undefined,
})
}
@@ -165,9 +159,7 @@ export const LeaderboardTable = ({
swapCount: userSwapTxs !== null ? userSwapTxs : undefined,
change24h: 0,
isCurrentUser: true,
- ethValue:
- fromLb?.ethValue ??
- (ethPrice && adjustedUserVol ? adjustedUserVol / ethPrice : undefined),
+ ethValue: fromLb?.ethValue,
},
]
}
diff --git a/src/components/dashboard/UserMetricsSection.tsx b/src/components/dashboard/UserMetricsSection.tsx
index 9bd13e51..65e4aa5c 100644
--- a/src/components/dashboard/UserMetricsSection.tsx
+++ b/src/components/dashboard/UserMetricsSection.tsx
@@ -4,7 +4,6 @@ import { useEffect, useState } from "react"
import { Card } from "@/components/ui/card"
import { TrendingUp, ArrowUpRight, Coins } from "lucide-react"
import { formatNumber } from "@/lib/utils"
-import { DEFAULT_ETH_PRICE_USD } from "@/lib/constants"
import { FEATURE_FLAGS } from "@/lib/feature-flags"
import { useUserAnalyticsData } from "@/hooks/use-dashboard-data"
@@ -14,7 +13,7 @@ interface UserMetricsSectionProps {
totalTxs: number | null
swapTxs: number | null
totalSwapVolEth: number | null
- ethPrice: number | null
+ totalSwapVolUsd: number | null
} | null
}
@@ -22,7 +21,7 @@ interface UserMetrics {
totalTxs: number
swapTxs: number
totalSwapVolEth: number
- ethPrice: number | null
+ totalSwapVolUsd: number
}
export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsSectionProps) => {
@@ -38,7 +37,7 @@ export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsS
totalTxs: initialGlobalStats.totalTxs ?? 0,
swapTxs: initialGlobalStats.swapTxs ?? 0,
totalSwapVolEth: initialGlobalStats.totalSwapVolEth ?? 0,
- ethPrice: initialGlobalStats.ethPrice ?? null,
+ totalSwapVolUsd: initialGlobalStats.totalSwapVolUsd ?? 0,
}
}
// Otherwise, use user-specific data from React Query if available
@@ -84,13 +83,11 @@ export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsS
try {
// If feature flag is enabled, fetch global stats (same endpoints as claim page)
if (FEATURE_FLAGS.show_global_stats) {
- const [transactionsResponse, swapVolumeResponse, swapCountResponse, ethPriceResponse] =
- await Promise.all([
- fetch("/api/analytics/transactions"),
- fetch("/api/analytics/volume/swap"),
- fetch("/api/analytics/swap-count"),
- fetch("/api/analytics/eth-price"),
- ])
+ const [transactionsResponse, swapVolumeResponse, swapCountResponse] = await Promise.all([
+ fetch("/api/analytics/transactions"),
+ fetch("/api/analytics/volume/swap"),
+ fetch("/api/analytics/swap-count"),
+ ])
if (!transactionsResponse.ok || !swapVolumeResponse.ok) {
throw new Error("Failed to fetch global metrics")
@@ -99,16 +96,12 @@ export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsS
const transactionsData = await transactionsResponse.json()
const swapVolumeData = await swapVolumeResponse.json()
const swapCountData = swapCountResponse.ok ? await swapCountResponse.json() : null
- const ethPriceData = ethPriceResponse.ok ? await ethPriceResponse.json() : null
setMetrics({
totalTxs: transactionsData.cumulativeSuccessfulTxs || 0,
swapTxs: swapCountData?.swapTxCount || 0,
totalSwapVolEth: swapVolumeData.cumulativeSwapVolEth || 0,
- ethPrice:
- ethPriceData?.ethPrice !== null && ethPriceData?.ethPrice !== undefined
- ? Number(ethPriceData.ethPrice)
- : null,
+ totalSwapVolUsd: swapVolumeData.cumulativeSwapVolUsd ?? 0,
})
} else {
// User-specific data is handled by React Query hook
@@ -130,10 +123,7 @@ export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsS
totalTxs: data.totalTxs || 0,
swapTxs: data.swapTxs || 0,
totalSwapVolEth: data.totalSwapVolEth || 0,
- ethPrice:
- data.ethPrice !== null && data.ethPrice !== undefined
- ? Number(data.ethPrice)
- : null,
+ totalSwapVolUsd: data.totalSwapVolUsd ?? 0,
})
}
}
@@ -151,7 +141,7 @@ export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsS
// Show placeholder data when not logged in (only for user-specific stats)
const showPlaceholder = !FEATURE_FLAGS.show_global_stats && !address
const displayMetrics = showPlaceholder
- ? { totalTxs: 0, swapTxs: 0, totalSwapVolEth: 0, ethPrice: null }
+ ? { totalTxs: 0, swapTxs: 0, totalSwapVolEth: 0, totalSwapVolUsd: 0 }
: metrics
const isGlobalStats = FEATURE_FLAGS.show_global_stats
@@ -219,19 +209,12 @@ export const UserMetricsSection = ({ address, initialGlobalStats }: UserMetricsS
- {(() => {
- const swapVolume = displayMetrics.totalSwapVolEth
- const price =
- displayMetrics.ethPrice !== null
- ? displayMetrics.ethPrice
- : DEFAULT_ETH_PRICE_USD
- const totalUsd = swapVolume * price
- return `$${totalUsd.toLocaleString(undefined, {
- maximumFractionDigits: 1,
- notation: "compact",
- compactDisplay: "short",
- })}`
- })()}
+ $
+ {(displayMetrics.totalSwapVolUsd ?? 0).toLocaleString(undefined, {
+ maximumFractionDigits: 1,
+ notation: "compact",
+ compactDisplay: "short",
+ })}
diff --git a/src/hooks/use-dashboard-data.ts b/src/hooks/use-dashboard-data.ts
index 358c3504..dbb2c1bf 100644
--- a/src/hooks/use-dashboard-data.ts
+++ b/src/hooks/use-dashboard-data.ts
@@ -21,7 +21,7 @@ interface UserAnalyticsData {
totalTxs: number
swapTxs: number
totalSwapVolEth: number
- ethPrice: number | null
+ totalSwapVolUsd: number
}
/**
@@ -51,7 +51,7 @@ async function fetchUserAnalytics(address: string): Promise {
totalTxs: data.totalTxs || 0,
swapTxs: data.swapTxs || 0,
totalSwapVolEth: data.totalSwapVolEth || 0,
- ethPrice: data.ethPrice !== null && data.ethPrice !== undefined ? Number(data.ethPrice) : null,
+ totalSwapVolUsd: data.totalSwapVolUsd || 0,
}
}
diff --git a/src/hooks/use-leaderboard-data.ts b/src/hooks/use-leaderboard-data.ts
index 1fea41fb..5b154d81 100644
--- a/src/hooks/use-leaderboard-data.ts
+++ b/src/hooks/use-leaderboard-data.ts
@@ -19,13 +19,12 @@ export interface LeaderboardData {
userPosition?: number | null
userVolume?: number | null
nextRankVolume?: number | null
- ethPrice?: number | null
}
interface LeaderboardStats {
activeTraders: number | null
swapVolumeEth: number | null
- ethPrice: number | null
+ swapVolumeUsd: number | null
}
/**
@@ -47,20 +46,18 @@ async function fetchLeaderboard(currentUserAddress?: string | null): Promise {
- const [activeTradersRes, swapVolumeRes, ethPriceRes] = await Promise.all([
+ const [activeTradersRes, swapVolumeRes] = await Promise.all([
fetch("/api/analytics/active-traders"),
fetch("/api/analytics/volume/swap"),
- fetch("/api/analytics/eth-price"),
])
const activeTradersData = activeTradersRes.ok ? await activeTradersRes.json() : null
const swapVolumeData = swapVolumeRes.ok ? await swapVolumeRes.json() : null
- const ethPriceData = ethPriceRes.ok ? await ethPriceRes.json() : null
return {
activeTraders: activeTradersData?.activeTraders ?? null,
swapVolumeEth: swapVolumeData?.cumulativeSwapVolEth ?? null,
- ethPrice: ethPriceData?.ethPrice ?? null,
+ swapVolumeUsd: swapVolumeData?.cumulativeSwapVolUsd ?? null,
}
}
diff --git a/src/lib/analytics-server.ts b/src/lib/analytics-server.ts
index 12a15c04..681de586 100644
--- a/src/lib/analytics-server.ts
+++ b/src/lib/analytics-server.ts
@@ -34,61 +34,29 @@ export async function getCumulativeTransactions(): Promise {
}
/**
- * Server-side function to fetch current ETH price from Alchemy
+ * Server-side function to fetch current ETH price from Alchemy (used by claim page and eth-price API)
*/
export async function getEthPrice(): Promise {
try {
const apiKey = env.ALCHEMY_API_KEY
-
if (!apiKey) {
console.error("ALCHEMY_API_KEY not configured")
return null
}
-
const response = await fetch(
`https://api.g.alchemy.com/prices/v1/${apiKey}/tokens/by-symbol?symbols=ETH`,
- {
- method: "GET",
- headers: {
- accept: "application/json",
- },
- cache: "no-store",
- }
+ { method: "GET", headers: { accept: "application/json" }, cache: "no-store" }
)
-
if (!response.ok) {
- const errorText = await response.text()
- console.error("Failed to fetch ETH price:", response.status, errorText)
+ console.error("Failed to fetch ETH price:", response.status, await response.text())
return null
}
-
const data = await response.json()
-
- // Alchemy returns data in format:
- // {
- // "data": [
- // {
- // "symbol": "ETH",
- // "prices": [
- // {
- // "currency": "usd",
- // "value": "2933.1463611734",
- // "lastUpdatedAt": "2025-12-29T16:38:25Z"
- // }
- // ]
- // }
- // ]
- // }
- if (data.data && Array.isArray(data.data) && data.data.length > 0) {
+ if (data.data?.length > 0) {
const ethData = data.data.find((item: any) => item.symbol === "ETH")
- if (ethData && ethData.prices && Array.isArray(ethData.prices) && ethData.prices.length > 0) {
- const usdPrice = ethData.prices.find((price: any) => price.currency === "usd")
- if (usdPrice && usdPrice.value) {
- return Number(usdPrice.value)
- }
- }
+ const usdPrice = ethData?.prices?.find((p: any) => p.currency === "usd")
+ if (usdPrice?.value) return Number(usdPrice.value)
}
-
return null
} catch (error) {
console.error("Error fetching ETH price:", error)
@@ -100,9 +68,11 @@ export async function getEthPrice(): Promise {
* Server-side function to fetch cumulative swap volume from analytics API
* Calls the internal API route which handles the external API call
*/
-export async function getCumulativeSwapVolume(): Promise {
+export async function getCumulativeSwapVolume(): Promise<{
+ eth: number | null
+ usd: number | null
+}> {
try {
- // Call the internal API route
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
const response = await fetch(`${baseUrl}/api/analytics/volume/swap`, {
cache: "no-store",
@@ -110,19 +80,18 @@ export async function getCumulativeSwapVolume(): Promise {
if (!response.ok) {
console.error("Failed to fetch swap volume:", response.statusText)
- return null
+ return { eth: null, usd: null }
}
const data = await response.json()
+ if (!data.success) return { eth: null, usd: null }
- if (data.success && data.cumulativeSwapVolEth !== null) {
- return Number(data.cumulativeSwapVolEth)
- }
-
- return null
+ const eth = data.cumulativeSwapVolEth != null ? Number(data.cumulativeSwapVolEth) : null
+ const usd = data.cumulativeSwapVolUsd != null ? Number(data.cumulativeSwapVolUsd) : null
+ return { eth, usd }
} catch (error) {
console.error("Error fetching cumulative swap volume:", error)
- return null
+ return { eth: null, usd: null }
}
}
@@ -212,7 +181,7 @@ export async function getActiveTraders(): Promise {
/**
* Server-side function to fetch leaderboard data (top 15) without user-specific data
- * This can be used for SSR to show the leaderboard immediately
+ * This can be used for SSR to show the leaderboard immediately (USD from DB)
*/
export async function getLeaderboardTop15(): Promise<{
leaderboard: Array<{
@@ -224,28 +193,13 @@ export async function getLeaderboardTop15(): Promise<{
isCurrentUser: boolean
ethValue: number
}>
- ethPrice: number | null
} | null> {
try {
- // Get ETH price for USD conversion
- const ethPrice = await getEthPrice()
-
- // Use the SQL flow via leaderboard service
const leaderboardRows = await getLeaderboard(15)
+ // useTotalVolume=false: use swap_vol_usd_24h for SSR
+ const leaderboard = transformLeaderboardRows(leaderboardRows, null, false)
- // Transform to expected format with USD conversion using shared utility
- // useTotalVolume=false means we use swap_vol_eth_24h (24h volume) for SSR
- const leaderboard = transformLeaderboardRows(
- leaderboardRows,
- ethPrice,
- null, // No current user for SSR
- false // Use 24h volume for SSR
- )
-
- return {
- leaderboard,
- ethPrice,
- }
+ return { leaderboard }
} catch (error) {
console.error("Error fetching leaderboard top 15:", error)
return null
diff --git a/src/lib/analytics/queries.ts b/src/lib/analytics/queries.ts
index 542c5c91..537d9ef6 100644
--- a/src/lib/analytics/queries.ts
+++ b/src/lib/analytics/queries.ts
@@ -61,7 +61,8 @@ ORDER BY day_utc DESC
export const GET_SWAP_COUNT = `
SELECT
COUNT(*) AS swap_tx_count,
- SUM(COALESCE(p.swap_vol_eth, 0)) AS total_swap_vol_eth
+ SUM(COALESCE(p.swap_vol_eth, 0)) AS total_swap_vol_eth,
+ SUM(COALESCE(p.swap_vol_usd, 0)) AS total_swap_vol_usd
FROM mevcommit_57173.processed_l1_txns_v2 p
WHERE p.is_swap = TRUE
AND EXISTS (
@@ -76,7 +77,9 @@ WITH daily AS (
SELECT
date_trunc('day', l1_timestamp) AS day,
SUM(COALESCE(total_vol_eth, 0)) AS daily_total_tx_vol_eth,
- SUM(COALESCE(swap_vol_eth, 0)) AS daily_total_swap_vol_eth
+ SUM(COALESCE(swap_vol_eth, 0)) AS daily_total_swap_vol_eth,
+ SUM(COALESCE(total_vol_usd, 0)) AS daily_total_tx_vol_usd,
+ SUM(COALESCE(swap_vol_usd, 0)) AS daily_total_swap_vol_usd
FROM mevcommit_57173.processed_l1_txns_v2
GROUP BY 1
),
@@ -90,13 +93,23 @@ cumulative AS (
SUM(daily_total_swap_vol_eth) OVER (
ORDER BY day ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
- ) AS cumulative_total_swap_vol_eth
+ ) AS cumulative_total_swap_vol_eth,
+ SUM(daily_total_tx_vol_usd) OVER (
+ ORDER BY day ASC
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cumulative_total_tx_vol_usd,
+ SUM(daily_total_swap_vol_usd) OVER (
+ ORDER BY day ASC
+ ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
+ ) AS cumulative_total_swap_vol_usd
FROM daily
)
SELECT
day,
cumulative_total_tx_vol_eth,
- cumulative_total_swap_vol_eth
+ cumulative_total_swap_vol_eth,
+ cumulative_total_tx_vol_usd,
+ cumulative_total_swap_vol_usd
FROM cumulative
WHERE CAST(day AS DATE) >= DATE '2025-11-20'
ORDER BY day DESC
@@ -108,6 +121,7 @@ WITH all_time AS (
SELECT
lower(from_address) AS wallet,
SUM(COALESCE(swap_vol_eth, 0)) AS total_swap_vol_eth,
+ SUM(COALESCE(swap_vol_usd, 0)) AS total_swap_vol_usd,
COUNT(*) AS swap_count
FROM mevcommit_57173.processed_l1_txns_v2
WHERE is_swap = TRUE
@@ -116,7 +130,8 @@ WITH all_time AS (
current_24h AS (
SELECT
lower(from_address) AS wallet,
- SUM(COALESCE(swap_vol_eth, 0)) AS swap_vol_eth_24h
+ SUM(COALESCE(swap_vol_eth, 0)) AS swap_vol_eth_24h,
+ SUM(COALESCE(swap_vol_usd, 0)) AS swap_vol_usd_24h
FROM mevcommit_57173.processed_l1_txns_v2
WHERE is_swap = TRUE
AND l1_timestamp >= date_trunc('day', CURRENT_TIMESTAMP) - INTERVAL '1' DAY
@@ -135,8 +150,10 @@ previous_24h AS (
SELECT
a.wallet,
COALESCE(a.total_swap_vol_eth, 0) AS total_swap_vol_eth,
+ COALESCE(a.total_swap_vol_usd, 0) AS total_swap_vol_usd,
COALESCE(a.swap_count, 0) AS swap_count,
COALESCE(c.swap_vol_eth_24h, 0) AS swap_vol_eth_24h,
+ COALESCE(c.swap_vol_usd_24h, 0) AS swap_vol_usd_24h,
CASE
WHEN COALESCE(p.swap_vol_eth_prev_24h, 0) > 0
THEN ((COALESCE(c.swap_vol_eth_24h, 0) - COALESCE(p.swap_vol_eth_prev_24h, 0)) / p.swap_vol_eth_prev_24h * 100)
@@ -156,6 +173,7 @@ export const LEADERBOARD_USER_DATA = `
WITH all_time_user AS (
SELECT
SUM(COALESCE(swap_vol_eth, 0)) AS total_swap_vol_eth,
+ SUM(COALESCE(swap_vol_usd, 0)) AS total_swap_vol_usd,
COUNT(*) AS swap_count
FROM mevcommit_57173.processed_l1_txns_v2
WHERE is_swap = TRUE
@@ -163,7 +181,8 @@ WITH all_time_user AS (
),
current_24h_user AS (
SELECT
- SUM(COALESCE(swap_vol_eth, 0)) AS swap_vol_eth_24h
+ SUM(COALESCE(swap_vol_eth, 0)) AS swap_vol_eth_24h,
+ SUM(COALESCE(swap_vol_usd, 0)) AS swap_vol_usd_24h
FROM mevcommit_57173.processed_l1_txns_v2
WHERE is_swap = TRUE
AND lower(from_address) = lower(:addr)
@@ -180,8 +199,10 @@ previous_24h_user AS (
)
SELECT
COALESCE(a.total_swap_vol_eth, 0) AS total_swap_vol_eth,
+ COALESCE(a.total_swap_vol_usd, 0) AS total_swap_vol_usd,
COALESCE(a.swap_count, 0) AS swap_count,
COALESCE(c.swap_vol_eth_24h, 0) AS swap_vol_eth_24h,
+ COALESCE(c.swap_vol_usd_24h, 0) AS swap_vol_usd_24h,
CASE
WHEN COALESCE(p.swap_vol_eth_prev_24h, 0) > 0
THEN ((COALESCE(c.swap_vol_eth_24h, 0) - COALESCE(p.swap_vol_eth_prev_24h, 0)) / p.swap_vol_eth_prev_24h * 100)
@@ -214,11 +235,13 @@ FROM (
export const LEADERBOARD_NEXT_RANK_THRESHOLD = `
SELECT
- MIN(total_swap_vol_eth) AS total_swap_vol_eth
+ MIN(total_swap_vol_eth) AS total_swap_vol_eth,
+ MIN(total_swap_vol_usd) AS total_swap_vol_usd
FROM (
SELECT
lower(from_address) AS wallet,
- SUM(COALESCE(swap_vol_eth, 0)) AS total_swap_vol_eth
+ SUM(COALESCE(swap_vol_eth, 0)) AS total_swap_vol_eth,
+ SUM(COALESCE(swap_vol_usd, 0)) AS total_swap_vol_usd
FROM mevcommit_57173.processed_l1_txns_v2
WHERE is_swap = TRUE
GROUP BY lower(from_address)
@@ -234,7 +257,8 @@ FROM (
// Users domain
export const GET_USER_SWAP_VOLUME = `
SELECT
- SUM(COALESCE(p.swap_vol_eth, 0)) AS total_swap_vol_eth
+ SUM(COALESCE(p.swap_vol_eth, 0)) AS total_swap_vol_eth,
+ SUM(COALESCE(p.swap_vol_usd, 0)) AS total_swap_vol_usd
FROM mevcommit_57173.processed_l1_txns_v2 p
WHERE lower(p.from_address) = lower(:addr)
AND p.is_swap = TRUE
diff --git a/src/lib/analytics/services/leaderboard-transform.ts b/src/lib/analytics/services/leaderboard-transform.ts
index 803abf4c..c581d42a 100644
--- a/src/lib/analytics/services/leaderboard-transform.ts
+++ b/src/lib/analytics/services/leaderboard-transform.ts
@@ -28,15 +28,13 @@ export interface TransformedLeaderboardEntry {
}
/**
- * Transform raw leaderboard rows into formatted entries
+ * Transform raw leaderboard rows into formatted entries (USD from DB columns)
* @param rows Raw leaderboard rows from database
- * @param ethPrice ETH price for USD conversion
* @param currentUserAddress Optional current user address for isCurrentUser flag
- * @param useTotalVolume If true, uses total_swap_vol_eth; if false, uses swap_vol_eth_24h
+ * @param useTotalVolume If true, uses total_swap_vol_usd; if false, uses swap_vol_usd_24h
*/
export function transformLeaderboardRows(
rows: LeaderboardRow[],
- ethPrice: number | null,
currentUserAddress?: string | null,
useTotalVolume: boolean = true
): TransformedLeaderboardEntry[] {
@@ -46,15 +44,14 @@ export function transformLeaderboardRows(
const wallet = row[0]
const walletLower = wallet?.toLowerCase() || wallet
const totalSwapVolEth = Number(row[1]) || 0
- const swapCount = Number(row[2]) || 0
- const swapVolEth24h = Number(row[3]) || 0
- const change24hPct = Number(row[4]) || 0
+ const totalSwapVolUsd = Number(row[2]) || 0
+ const swapCount = Number(row[3]) || 0
+ const swapVolEth24h = Number(row[4]) || 0
+ const swapVolUsd24h = Number(row[5]) || 0
+ const change24hPct = Number(row[6]) || 0
- // Use total volume or 24h volume based on parameter
const volumeEth = useTotalVolume ? totalSwapVolEth : swapVolEth24h
-
- // Convert volume to USD
- const volumeUsd = ethPrice !== null ? volumeEth * ethPrice : volumeEth
+ const volumeUsd = useTotalVolume ? totalSwapVolUsd : swapVolUsd24h
return {
rank: index + 1,
diff --git a/src/lib/analytics/services/leaderboard.service.ts b/src/lib/analytics/services/leaderboard.service.ts
index 22c68183..56fc83d3 100644
--- a/src/lib/analytics/services/leaderboard.service.ts
+++ b/src/lib/analytics/services/leaderboard.service.ts
@@ -9,8 +9,10 @@ import type { QueryOptions } from "../client"
export type LeaderboardRow = [
wallet: string,
total_swap_vol_eth: number,
+ total_swap_vol_usd: number,
swap_count: number,
swap_vol_eth_24h: number,
+ swap_vol_usd_24h: number,
change_24h_pct: number,
]
@@ -19,8 +21,10 @@ export type LeaderboardRow = [
*/
export type UserLeaderboardDataRow = [
total_swap_vol_eth: number,
+ total_swap_vol_usd: number,
swap_count: number,
swap_vol_eth_24h: number,
+ swap_vol_usd_24h: number,
change_24h_pct: number,
]
@@ -30,9 +34,12 @@ export type UserLeaderboardDataRow = [
export type UserRankResult = [user_rank: number]
/**
- * Next rank threshold result
+ * Next rank threshold result (eth and usd)
*/
-export type NextRankThresholdResult = [total_swap_vol_eth: number | null]
+export type NextRankThresholdResult = [
+ total_swap_vol_eth: number | null,
+ total_swap_vol_usd: number | null,
+]
const client = getAnalyticsClient()
@@ -99,22 +106,26 @@ export async function getUserRank(address: string, options?: QueryOptions): Prom
}
/**
- * Get the volume threshold needed to reach the next rank
+ * Get the volume threshold needed to reach the next rank (ETH and USD)
* Returns the total swap volume of the user just above the current user
- * Returns null if user is already #1 or has no swap volume
+ * Returns { eth: null, usd: null } if user is already #1 or has no swap volume
*/
export async function getNextRankThreshold(
address: string,
options?: QueryOptions
-): Promise {
+): Promise<{ eth: number | null; usd: number | null }> {
const addr = sanitizeAddress(address)
const row = await client.executeOne("leaderboard/next-rank-threshold", { addr }, options)
- if (!row || row[0] === null) {
- return null
+ if (!row) {
+ return { eth: null, usd: null }
}
- const threshold = Number(row[0])
- return Number.isFinite(threshold) ? threshold : null
+ const eth = row[0] !== null && row[0] !== undefined ? Number(row[0]) : null
+ const usd = row[1] !== null && row[1] !== undefined ? Number(row[1]) : null
+ return {
+ eth: eth !== null && Number.isFinite(eth) ? eth : null,
+ usd: usd !== null && Number.isFinite(usd) ? usd : null,
+ }
}
diff --git a/src/lib/analytics/services/transactions.service.ts b/src/lib/analytics/services/transactions.service.ts
index 1cb3f7de..515fba78 100644
--- a/src/lib/analytics/services/transactions.service.ts
+++ b/src/lib/analytics/services/transactions.service.ts
@@ -19,7 +19,11 @@ export type TransactionsAnalyticsRow = [
/**
* Swap count result row
*/
-export type SwapCountRow = [swap_tx_count: number, total_swap_vol_eth: number]
+export type SwapCountRow = [
+ swap_tx_count: number,
+ total_swap_vol_eth: number,
+ total_swap_vol_usd: number,
+]
/**
* Swap volume result row
@@ -28,6 +32,8 @@ export type SwapVolumeRow = [
day: string,
cumulative_total_tx_vol_eth: number,
cumulative_total_swap_vol_eth: number,
+ cumulative_total_tx_vol_usd: number,
+ cumulative_total_swap_vol_usd: number,
]
const client = getAnalyticsClient()
@@ -116,23 +122,24 @@ export async function getSwapCount(options?: QueryOptions): Promise {
+export async function getSwapVolume(
+ options?: QueryOptions
+): Promise<{ eth: number | null; usd: number | null }> {
const rows = await client.execute("transactions/get-swap-volume", undefined, options)
if (rows.length === 0) {
- return null
+ return { eth: null, usd: null }
}
- // Get the first row (most recent day) - the query already orders by day DESC
- // Format: [day, cumulative_total_tx_vol_eth, cumulative_total_swap_vol_eth]
const latestRow = rows[0] as SwapVolumeRow
+ const eth = latestRow[2] !== null && latestRow[2] !== undefined ? Number(latestRow[2]) : null
+ const usd = latestRow[4] !== null && latestRow[4] !== undefined ? Number(latestRow[4]) : null
- // Extract cumulative_total_swap_vol_eth from index 2 (swap volume)
- const cumulativeSwapVolume =
- latestRow[2] !== null && latestRow[2] !== undefined ? Number(latestRow[2]) : null
-
- return cumulativeSwapVolume !== null && !isNaN(cumulativeSwapVolume) ? cumulativeSwapVolume : null
+ return {
+ eth: eth !== null && !isNaN(eth) ? eth : null,
+ usd: usd !== null && !isNaN(usd) ? usd : null,
+ }
}
diff --git a/src/lib/analytics/services/users.service.ts b/src/lib/analytics/services/users.service.ts
index 2176bdd6..fe6b91c6 100644
--- a/src/lib/analytics/services/users.service.ts
+++ b/src/lib/analytics/services/users.service.ts
@@ -6,7 +6,10 @@ import type { QueryOptions } from "../client"
/**
* User swap volume result row
*/
-export type UserSwapVolumeRow = [total_swap_vol_eth: number | null]
+export type UserSwapVolumeRow = [
+ total_swap_vol_eth: number | null,
+ total_swap_vol_usd: number | null,
+]
const client = getAnalyticsClient()
@@ -23,19 +26,32 @@ function sanitizeAddress(address: string): string {
}
/**
- * Get total swap volume for a specific user address
+ * Get total swap volume for a specific user address (ETH and USD from DB)
* @param address Ethereum address (0x-prefixed hex string)
- * @returns Total swap volume in ETH, or 0 if user has no swaps
*/
-export async function getUserSwapVolume(address: string, options?: QueryOptions): Promise {
+export async function getUserSwapVolume(
+ address: string,
+ options?: QueryOptions
+): Promise<{ eth: number; usd: number }> {
const addr = sanitizeAddress(address)
const row = await client.executeOne("users/get-user-swap-volume", { addr }, options)
- if (!row || row[0] === null || row[0] === undefined) {
- return 0
+ if (!row) {
+ return { eth: 0, usd: 0 }
}
- const volume = Number(row[0])
- return Number.isFinite(volume) ? volume : 0
+ const eth =
+ row[0] !== null && row[0] !== undefined
+ ? Number.isFinite(Number(row[0]))
+ ? Number(row[0])
+ : 0
+ : 0
+ const usd =
+ row[1] !== null && row[1] !== undefined
+ ? Number.isFinite(Number(row[1]))
+ ? Number(row[1])
+ : 0
+ : 0
+ return { eth, usd }
}