'use client' import { cn, fmtUsd, shortAddr } from '@/lib/utils' import { wallets } from '@/lib/mock-data' import { RiskBadge } from '@/components/ui/RiskBadge' import { RecoveryCard } from '@/components/ui/RecoveryCard' import { useToast } from '@/components/ui/Toast' import { Search, Flame, ExternalLink, ChevronRight, Zap, CheckCircle2, Loader2, Siren, Share2, AlertTriangle, Radio } from 'lucide-react' import { useState, useCallback, useMemo } from 'react' import type { ChurnRisk, Wallet } from '@/lib/types' import { classifyWalletType } from '@/lib/agent-engine' // --- Live Wallet Analyzer (Helius) --- interface LiveResult { address: string; score: number; risk: string; detectedSignals: string[] signals: { daysInactive: number; protocols: string[]; currentStreak: number; volumeDropPct: number; totalTxLast30d: number; lastActiveDaysAgo: string } torque: { fired: boolean; eventId?: string; eventName?: string } } function LiveAnalyzer() { const [addr, setAddr] = useState('') const [loading, setLoading] = useState(false) const [result, setResult] = useState(null) const [err, setErr] = useState(null) const [autoFire, setAutoFire] = useState(false) const { fire: toast } = useToast() const analyze = useCallback(async () => { if (!addr.trim() || loading) return setLoading(true); setErr(null); setResult(null) try { const res = await fetch('/api/helius/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: addr.trim(), autoFire }), }) const data = await res.json() if (!res.ok) { setErr(data.error); return } setResult(data) if (data.torque?.fired) { toast({ type: 'event', title: `${data.torque.eventName} → ${addr.slice(0,8)}...`, body: data.torque.eventId?.slice(0,10) }) } } catch (e: any) { setErr(e.message) } finally { setLoading(false) } }, [addr, autoFire, loading, toast]) const RISK_CLR: Record = { critical:'text-trading-down', high:'text-[#ff9500]', medium:'text-brand-yellow', low:'text-trading-up', safe:'text-muted' } const RISK_BG: Record = { critical:'border-trading-down/30 bg-trading-down/5', high:'border-[#ff9500]/30 bg-[#ff9500]/5', medium:'border-brand-yellow/30 bg-brand-yellow/5', low:'border-trading-up/30 bg-trading-up/5', safe:'border-hairline-dark bg-surface-elevated' } return (

Live Wallet Analyzer

HELIUS

Enter any Solana address — real on-chain churn score

setAddr(e.target.value)} onKeyDown={e => e.key === 'Enter' && analyze()} placeholder="Enter Solana wallet address..." className="flex-1 h-10 px-4 rounded-lg bg-surface-elevated border border-hairline-dark text-body-sm text-[#eaecef] placeholder:text-muted focus:outline-none focus:ring-1 focus:ring-brand-yellow/50 font-mono" />
{err &&

Error: {err}

} {result && (

{result.address.slice(0,8)}...{result.address.slice(-6)}

View on Solscan

{result.score}

{result.risk}

Inactive

{result.signals.lastActiveDaysAgo}

Streak

{result.signals.currentStreak}d

Vol drop

{result.signals.volumeDropPct}%

30d txs

{result.signals.totalTxLast30d}

{result.signals.protocols.length > 0 && (
{result.signals.protocols.map(p => {p})}
)} {result.detectedSignals.length > 0 && (
{result.detectedSignals.map((s, i) =>

{s}

)}
)} {result.torque.fired && (

{result.torque.eventName} fired via Torque

{result.torque.eventId}

)}
)}
) } type Filter = ChurnRisk | 'all' const filters: Filter[] = ['all', 'critical', 'high', 'medium', 'low', 'safe'] const EVENT_MAP: Record = { critical: 'churn_risk_high', high: 'churn_risk_high', medium: 'churn_risk_medium', low: 'inactivity_detected', safe: 'streak_maintained', } const ACTION_LABEL: Record = { critical: 'Send Gift', high: 'Enter Raffle', medium: 'Activate Rebate', low: 'Flag Inactive', safe: 'Reward Streak', } const RISK_CAN_INTERVENE: ChurnRisk[] = ['critical', 'high', 'medium'] // Wallets with savedCount > 0 have prior rescues — show recovery card option const RESCUED_WALLETS = new Set(wallets.filter(w => w.savedCount > 0).map(w => w.address)) // Pre-churn: "low" risk wallets with streak ≤ 2 and recent inactivity — early warning function isPreChurnWarning(w: Wallet): boolean { const daysMatch = w.lastActive.match(/(\d+)d/) const days = daysMatch ? parseInt(daysMatch[1]) : 0 const wtype = classifyWalletType(w.protocols) const threshold = wtype === 'trader' ? 3 : wtype === 'lp' ? 5 : 7 return w.churnRisk === 'low' && days >= threshold && w.streak <= 2 } export default function WalletsPage() { const { fire: toast } = useToast() const [f, setF] = useState('all') const [q, setQ] = useState('') const [sort, setSort] = useState<'risk' | 'volume' | 'streak'>('risk') const [firing, setFiring] = useState(null) const [fired, setFired] = useState>(new Map()) const [bulkFiring, setBulkFiring] = useState(false) const [bulkProgress, setBulkProgress] = useState<{ done: number; total: number } | null>(null) const [recoveryWallet, setRecoveryWallet] = useState(null) const preChurnCount = useMemo(() => wallets.filter(isPreChurnWarning).length, []) const list = wallets .filter(w => f === 'all' || w.churnRisk === f) .filter(w => !q || w.address.toLowerCase().includes(q.toLowerCase())) .sort((a, b) => sort === 'risk' ? b.riskScore - a.riskScore : sort === 'volume' ? b.totalVolume - a.totalVolume : b.streak - a.streak) const criticalUnfired = wallets.filter(w => (w.churnRisk === 'critical' || w.churnRisk === 'high') && !fired.has(w.address) ) const intervene = useCallback(async (w: Wallet) => { if (firing || fired.has(w.address)) return setFiring(w.address) try { const res = await fetch('/api/torque/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ wallet: w.address, eventName: EVENT_MAP[w.churnRisk], data: { risk: w.churnRisk, score: w.riskScore, detectedBy: 'flowstate-dashboard' }, risk: w.churnRisk, score: w.riskScore, }), }) const data = await res.json() const eventId = data.eventId || 'sent' setFired(prev => new Map(prev).set(w.address, eventId)) toast({ type: 'event', title: `${ACTION_LABEL[w.churnRisk]} → ${shortAddr(w.address)}`, body: `${EVENT_MAP[w.churnRisk]} · ${eventId.slice(0, 10)}`, }) } catch { setFired(prev => new Map(prev).set(w.address, 'error')) toast({ type: 'error', title: 'Event failed', body: w.address.slice(0, 12) }) } finally { setFiring(null) } }, [firing, fired, toast]) const bulkRescue = useCallback(async () => { if (bulkFiring || criticalUnfired.length === 0) return setBulkFiring(true) setBulkProgress({ done: 0, total: criticalUnfired.length }) const targets = criticalUnfired.map(w => ({ wallet: w.address, eventName: EVENT_MAP[w.churnRisk], risk: w.churnRisk, score: w.riskScore, })) try { const res = await fetch('/api/torque/bulk-fire', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ targets }), }) const data = await res.json() if (data.details) { const newFired = new Map(fired) data.details.forEach((d: any, i: number) => { if (d.success) newFired.set(criticalUnfired[i].address, d.eventId || 'sent') }) setFired(newFired) setBulkProgress({ done: data.fired, total: data.total }) toast({ type: data.fired > 0 ? 'success' : 'error', title: `Bulk rescue: ${data.fired}/${data.total} fired`, body: data.fired > 0 ? 'All critical wallets targeted via Torque' : data.details[0]?.error, }) } } catch (e: any) { toast({ type: 'error', title: 'Bulk fire failed', body: e.message }) } finally { setBulkFiring(false) setTimeout(() => setBulkProgress(null), 3000) } }, [bulkFiring, criticalUnfired, fired, toast]) return (
{recoveryWallet && ( setRecoveryWallet(null)} /> )}

Wallets

Monitor wallet health, churn risk & activity patterns

{preChurnCount > 0 && (
{preChurnCount} pre-churn
)} {fired.size > 0 && (
{fired.size} events sent
)} {criticalUnfired.length > 0 && ( )}
{filters.map(r => ( ))}
setQ(e.target.value)} className="w-full h-9 pl-9 pr-4 rounded-lg bg-surface-card border border-hairline-dark text-body-sm placeholder:text-muted focus:outline-none focus:ring-1 focus:ring-brand-yellow/50" />
{(['risk', 'volume', 'streak'] as const).map(s => )}
{list.map(w => { const isFiring = firing === w.address const eventId = fired.get(w.address) const canIntervene = RISK_CAN_INTERVENE.includes(w.churnRisk) const preChurn = isPreChurnWarning(w) const rescued = RESCUED_WALLETS.has(w.address) const wtype = classifyWalletType(w.protocols) return ( ) })}
Wallet Risk Score Volume Streak Protocols Last Active Action
{shortAddr(w.address)} {preChurn && ⚡ pre-churn}
{wtype} {eventId && eventId !== 'error' && (

✓ {eventId.slice(0, 10)}...

)}
= 80 ? 'bg-trading-down' : w.riskScore >= 60 ? 'bg-[#ff9500]' : w.riskScore >= 40 ? 'bg-brand-yellow' : w.riskScore >= 20 ? 'bg-trading-up' : 'bg-brand-turquoise')} style={{ width: w.riskScore + '%' }} />
{w.riskScore}
{fmtUsd(w.totalVolume)}
= 30 ? 'text-brand-yellow' : w.streak >= 7 ? 'text-trading-up' : w.streak === 0 ? 'text-trading-down' : 'text-muted')} /> {w.streak}d
{w.protocols.slice(0, 3).map(p => {p})} {w.protocols.length > 3 && +{w.protocols.length - 3}}
{w.lastActive}
{rescued && ( )} {canIntervene && ( eventId ? ( Sent ) : ( ) )} {!canIntervene && !rescued && }
) }