diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index 4308c61..ce023a5 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -4,6 +4,7 @@ import PageHeader from '@/components/common/page-header' import HotAccountsBubbleMap from '@/components/hot-accounts-bubble-map' import HotSlotsBubbleMap from '@/components/hot-slots-bubble-map' import SwapTransferTracker from '@/components/swap-transfer-tracker' +import TpsTracker from '@/components/tps-tracker' export default function Home() { return ( @@ -11,6 +12,8 @@ export default function Home() {
+ + diff --git a/frontend/components/common/live-badge.tsx b/frontend/components/common/live-badge.tsx new file mode 100644 index 0000000..b227d9d --- /dev/null +++ b/frontend/components/common/live-badge.tsx @@ -0,0 +1,37 @@ +'use client' + +import { cn } from '@/lib/utils' + +interface LiveBadgeProps { + isConnected: boolean +} + +/** + * Reusable live badge component with animated ping effect. + * Used to indicate real-time connection status. + */ +export function LiveBadge({ isConnected }: LiveBadgeProps) { + return ( + + + {isConnected && ( + + )} + + + Live + + ) +} diff --git a/frontend/components/swap-transfer-tracker/index.tsx b/frontend/components/swap-transfer-tracker/index.tsx index d0b6d3b..a01b0ec 100644 --- a/frontend/components/swap-transfer-tracker/index.tsx +++ b/frontend/components/swap-transfer-tracker/index.tsx @@ -1,6 +1,7 @@ 'use client' import { ArrowLeftRight, Send } from 'lucide-react' +import { LiveBadge } from '@/components/common/live-badge' import { SectionHeader } from '@/components/ui/section-header' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { useSwapEvents } from '@/hooks/use-swap-events' @@ -18,32 +19,6 @@ const TAB_TRIGGER_CLASS = cn( 'data-[state=active]:bg-transparent data-[state=active]:shadow-none', ) -function LiveBadge({ isConnected }: { isConnected: boolean }) { - return ( - - - {isConnected && ( - - )} - - - Live - - ) -} - export default function SwapTransferTracker() { const { allSwaps, isConnected: isSwapConnected } = useSwapEvents() const { diff --git a/frontend/components/tps-tracker/index.tsx b/frontend/components/tps-tracker/index.tsx new file mode 100644 index 0000000..fcda94b --- /dev/null +++ b/frontend/components/tps-tracker/index.tsx @@ -0,0 +1,48 @@ +'use client' + +import { Activity, Hash, TrendingUp } from 'lucide-react' +import { SectionHeader } from '@/components/ui/section-header' +import { StatCard } from '@/components/ui/stat-card' +import { useTps } from '@/hooks/use-tps' +import { formatIntNumber } from '@/utils/ui' + +/** + * TPS Tracker component displaying real-time transactions per second on Monad + */ +export default function TpsTracker() { + const { currentTps, peakTps, totalTransactions } = useTps() + + return ( +
+ + +
+ + + +
+
+ ) +} diff --git a/frontend/hooks/use-tps.ts b/frontend/hooks/use-tps.ts new file mode 100644 index 0000000..db7dc89 --- /dev/null +++ b/frontend/hooks/use-tps.ts @@ -0,0 +1,60 @@ +'use client' + +import { useCallback, useEffect, useRef, useState } from 'react' +import { useEvents } from '@/hooks/use-events' +import type { SerializableEventData } from '@/types/events' + +const TPS_INTERVAL_MS = 1000 + +interface TpsData { + currentTps: number + peakTps: number + totalTransactions: number +} + +/** + * Hook to calculate and track TPS (Transactions Per Second) from execution events. + * Subscribes to TxnHeaderStart events and calculates TPS every second. + */ +export function useTps() { + const [tpsData, setTpsData] = useState({ + currentTps: 0, + peakTps: 0, + totalTransactions: 0, + }) + + const txCountRef = useRef(0) + const intervalRef = useRef(null) + + const handleEvent = useCallback((event: SerializableEventData) => { + if (event.payload.type === 'TxnHeaderStart') { + txCountRef.current += 1 + } + }, []) + + useEvents({ + filters: [{ eventName: 'TxnHeaderStart' }], + onEvent: handleEvent, + }) + + useEffect(() => { + intervalRef.current = setInterval(() => { + const count = txCountRef.current + txCountRef.current = 0 + + setTpsData((prev) => ({ + currentTps: count, + peakTps: Math.max(prev.peakTps, count), + totalTransactions: prev.totalTransactions + count, + })) + }, TPS_INTERVAL_MS) + + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current) + } + } + }, []) + + return tpsData +}