import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { useTradeReputation } from '@/hooks/useTradeAttestations'; import { useBatchTrustScores } from '@/hooks/useBatchTrustScores'; import { useBatchActivity, useTradeRings } from '@/hooks/useTradeIntegrity'; import { cn } from '@/lib/utils'; import { Handshake } from 'lucide-react'; import { useTranslation } from '@/contexts/language'; function Stat({ label, value, accent }: { label: string; value: number; accent?: boolean }) { return (
{value}
{label}
{t('marketUi.loadFailed')}
; } if (!data || total === 0) return null; const people = data.verifiedCounterparties; // Cap each counterparty's contribution at 3 so one partner — or a colluding // pair confirming each other over and over — can't inflate the score. // Activity factor: a fresh account with no published content contributes ~0, // so a ring of empty accounts can't move the score even if cross-confirmed. const activityFactor = (pk: string) => Math.min(1, (activity?.get(pk) ?? 0) / 3); const now = Math.floor(Date.now() / 1000); const decayFactor = (v: { lastAt: number; recentCount: number }) => { // 180-day half-life, floored so a good historical record is still visible. // This changes the ranking weight only; lifetime counts stay unchanged. const ageDays = Math.max(0, (now - v.lastAt) / 86_400); return Math.max(0.25, Math.exp(-ageDays / 180)); }; const weighted = verifiedWith.reduce( (sum, v) => sum + Math.min(v.count, 3) * ((scores?.get(v.pubkey) ?? 0) / 100) * activityFactor(v.pubkey) * decayFactor(v), 0, ); const trustLabel = weighted >= 5 ? t('marketUi.reputation.highlyTrusted') : weighted >= 1 ? t('marketUi.reputation.building') : t('marketUi.reputation.newTrader'); // The classic fake-reputation pattern: several "trades", all with one account. const singlePartnerWarning = data.verified >= 3 && people <= 1; // A bigger ring: lots of confirmations from only a couple of accounts. const lowDiversityWarning = !singlePartnerWarning && data.verified >= 5 && people <= 2; return ({data.verified > 0 ? `${data.verified} ${data.verified === 1 ? t('marketUi.reputation.tradeConfirmedOne') : t('marketUi.reputation.tradeConfirmedMany')} ${t('marketUi.reputation.with')} ${people} ${people === 1 ? t('marketUi.reputation.personOne') : t('marketUi.reputation.personMany')}` : t('marketUi.reputation.awaiting')}
{data.recentVerified > 0 && ({data.recentVerified}{' '} {t('marketUi.reputation.recentConfirmed')}
)} {data.withPaymentProof > 0 && ({data.withPaymentProof} {t('marketUi.reputation.withPaymentProof')} {t('marketUi.reputation.selfReported')}
)} {data.paymentVerified > 0 && ({data.paymentVerified} {t('marketUi.reputation.paymentVerified')} {t('marketUi.reputation.zapReceipt')}
)} {singlePartnerWarning && (⚠ {t('marketUi.reputation.singlePartnerWarning')}
)} {lowDiversityWarning && (⚠ {t('marketUi.reputation.lowDiversityWarning')}
)} {rings?.isRing && (🔁 {t('marketUi.reputation.ringWarning')}
)} {data.verified > 0 && ({t('marketUi.reputation.weighted')} {weighted.toFixed(1)} {' · '} {trustLabel}
)}{t('marketUi.reputation.footnote')}