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
16 changes: 7 additions & 9 deletions src/app/(app)/leaderboard/LeaderboardPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -34,7 +34,7 @@ interface LeaderboardPageClientProps {
export function LeaderboardPageClient({
preloadedActiveTraders,
preloadedSwapVolumeEth,
preloadedEthPrice,
preloadedSwapVolumeUsd,
preloadedLeaderboard,
}: LeaderboardPageClientProps) {
const [isMounted, setIsMounted] = useState(false)
Expand All @@ -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

Expand Down
20 changes: 5 additions & 15 deletions src/app/(app)/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Suspense } from "react"
import {
getActiveTraders,
getCumulativeSwapVolume,
getEthPrice,
getLeaderboardTop15,
} from "@/lib/analytics-server"
import { LeaderboardPageClient } from "./LeaderboardPageClient"
Expand Down Expand Up @@ -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 (
<LeaderboardPageClient
preloadedActiveTraders={activeTraders}
preloadedSwapVolumeEth={swapVolumeEth}
preloadedEthPrice={ethPrice}
preloadedSwapVolumeEth={swapVolume?.eth ?? null}
preloadedSwapVolumeUsd={swapVolume?.usd ?? null}
preloadedLeaderboard={leaderboardData}
/>
)
Expand Down Expand Up @@ -88,7 +78,7 @@ export default function LeaderboardPage() {
<LeaderboardPageClient
preloadedActiveTraders={null}
preloadedSwapVolumeEth={null}
preloadedEthPrice={null}
preloadedSwapVolumeUsd={null}
preloadedLeaderboard={[]}
/>
}
Expand Down
29 changes: 8 additions & 21 deletions src/app/api/analytics/leaderboard/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { NextRequest, NextResponse } from "next/server"
import { getEthPrice } from "@/lib/analytics-server"
import {
getLeaderboard,
getUserLeaderboardData,
Expand Down Expand Up @@ -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
)
Expand All @@ -90,40 +85,33 @@ 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),
])

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,
Expand All @@ -149,7 +137,6 @@ export async function GET(request: NextRequest) {
userPosition,
userVolume,
nextRankVolume,
ethPrice,
}

// Cache the response
Expand Down
13 changes: 6 additions & 7 deletions src/app/api/analytics/user/[address]/route.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -56,26 +55,26 @@ 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 {
console.error("Analytics DB API error:", error)
}
}

const ethPrice = await getEthPrice()

return NextResponse.json({
totalTxs,
swapTxs,
totalSwapVolEth,
ethPrice,
totalSwapVolUsd,
})
} catch (error) {
console.error("Error fetching user metrics:", error)
Expand Down
5 changes: 3 additions & 2 deletions src/app/api/analytics/volume/swap/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions src/app/claim/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,16 @@ 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

return (
<ClaimPageClient
initialTotalSupply={totalSupplyString}
initialTransactions={transactionsString}
initialSwapVolume={swapVolumeString}
initialSwapVolumeUsd={swapVolumeUsdString}
initialEthPrice={ethPriceString}
initialTotalPoints={totalPointsString}
/>
Expand Down
24 changes: 8 additions & 16 deletions src/components/claim/ClaimPageClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ 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
}

export const ClaimPageClient = ({
initialTotalSupply,
initialTransactions,
initialSwapVolume,
initialSwapVolumeUsd,
initialEthPrice,
initialTotalPoints,
}: ClaimPageClientProps) => {
Expand Down Expand Up @@ -140,19 +139,12 @@ export const ClaimPageClient = ({
</div>
<div className="space-y-2">
<p className="text-3xl font-bold font-mono text-primary">
{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"}
</p>
<p className="text-sm text-muted-foreground">Swap Volume</p>
Expand Down
18 changes: 5 additions & 13 deletions src/components/dashboard/LeaderboardTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<number | null>(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(
Expand Down Expand Up @@ -117,7 +111,7 @@ export const LeaderboardTable = ({
swapCount: userSwapTxs !== null ? userSwapTxs : undefined,
change24h: 0,
isCurrentUser: true,
ethValue: ethPrice && adjustedUserVol ? adjustedUserVol / ethPrice : undefined,
ethValue: undefined,
})
}

Expand Down Expand Up @@ -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,
},
]
}
Expand Down
Loading