-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLeaderboard.tsx
More file actions
453 lines (406 loc) · 23.8 KB
/
Copy pathLeaderboard.tsx
File metadata and controls
453 lines (406 loc) · 23.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
import React, { useState, useEffect } from "react";
import { TrophyIcon, ClockIcon, GreenDotIcon, FlagIcon, DumbbellIcon, GoldMedalIcon, SilverMedalIcon, BronzeMedalIcon } from "./Icons";
import { ethers } from "ethers";
import { WalletState } from "../lib/wallet";
import STATIC_DEPLOYED_ADDRESSES from "../deployed-addresses.json";
import { useLanguage } from "../context/LanguageContext";
const VAULT_ABI = [
"function getCredits(address user) external view returns (uint256)",
"function users(address user) external view returns (uint256 balance, uint256 lastUpdated, uint256 accumulatedCredits, address delegatedAgent)",
"function totalStaked() external view returns (uint256)",
"event Deposited(address indexed user, uint256 amount)",
"event AgentDelegated(address indexed user, address indexed agent)"
];
// Campaign: July 6 → July 13
const CAMPAIGN_START = new Date("2026-07-06T00:00:00Z");
const CAMPAIGN_END = new Date("2026-07-13T00:00:00Z");
interface LeaderboardProps {
wallet: WalletState;
}
export const Leaderboard: React.FC<LeaderboardProps> = ({ wallet }) => {
const { language, t } = useLanguage();
const isMainnet = true;
const DEPLOYED_ADDRESSES = (STATIC_DEPLOYED_ADDRESSES as any).xlayerMainnet || STATIC_DEPLOYED_ADDRESSES;
const [leaderboard, setLeaderboard] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
// Campaign dates differ by network
// Testnet: Pre-Season warm-up (now → World Cup kickoff)
// Mainnet: Season 1 Group Stage (World Cup kickoff → end of group stage)
const campaignStart = isMainnet ? CAMPAIGN_START : new Date("2026-05-27T00:00:00Z");
const campaignEnd = isMainnet ? CAMPAIGN_END : CAMPAIGN_START; // Testnet ends when mainnet begins
// Campaign countdown
const [campaignTime, setCampaignTime] = useState({ days: 0, hours: 0, minutes: 0, seconds: 0, phase: 'pre' as 'pre' | 'live' | 'ended' });
useEffect(() => {
const updateCampaign = () => {
const now = Date.now();
const startMs = campaignStart.getTime();
const endMs = campaignEnd.getTime();
let target: number;
let phase: 'pre' | 'live' | 'ended';
if (now < startMs) {
target = startMs;
phase = 'pre';
} else if (now < endMs) {
target = endMs;
phase = 'live';
} else {
setCampaignTime({ days: 0, hours: 0, minutes: 0, seconds: 0, phase: 'ended' });
return;
}
const diff = target - now;
setCampaignTime({
days: Math.floor(diff / (1000 * 60 * 60 * 24)),
hours: Math.floor((diff / (1000 * 60 * 60)) % 24),
minutes: Math.floor((diff / 1000 / 60) % 60),
seconds: Math.floor((diff / 1000) % 60),
phase,
});
};
updateCampaign();
const iv = setInterval(updateCampaign, 1000);
return () => clearInterval(iv);
}, [isMainnet]);
useEffect(() => {
if (!DEPLOYED_ADDRESSES.NoLossVault) return;
const fetchLeaderboard = async () => {
try {
setLoading(true);
const rpcUrl = import.meta.env.VITE_XLAYER_RPC_URL || "https://rpc.xlayer.tech";
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, { batchMaxCount: 1 });
const vault = new ethers.Contract(DEPLOYED_ADDRESSES.NoLossVault, VAULT_ABI, provider);
// 1. Load cached stakers from localStorage
const storageKey = isMainnet ? "shieldsuite_stakers_mainnet" : "shieldsuite_stakers_testnet";
const cachedStakers = (() => {
try {
const raw = localStorage.getItem(storageKey);
return raw ? (JSON.parse(raw) as string[]) : [];
} catch {
return [];
}
})();
const userAddresses = new Set<string>();
cachedStakers.forEach(addr => userAddresses.add(addr.toLowerCase()));
// Load registered users and volumes from backend API
const API_BASE = import.meta.env.VITE_SCANGUARD_URL || "http://localhost:3402";
const backendVolumes: Record<string, number> = {};
try {
const res = await fetch(`${API_BASE}/api/worldcup/leaderboard`);
const data = await res.json();
if (data.success && Array.isArray(data.data)) {
data.data.forEach((item: any) => {
if (item.address) {
userAddresses.add(item.address.toLowerCase());
backendVolumes[item.address.toLowerCase()] = item.volume || 0;
}
});
}
} catch (e) {
console.error("Failed to fetch registered users from backend:", e);
}
// Query current wallet address to check if active
if (wallet.address) userAddresses.add(wallet.address.toLowerCase());
// Scan blocks to discover recent stakers/deposits.
// Limit query block range lookback to fetch history without timing out.
const currentBlock = await provider.getBlockNumber();
const lookback = 500;
const startBlock = Math.max(0, currentBlock - lookback);
const depositFilter = vault.filters.Deposited();
const depositEvents = await vault.queryFilter(depositFilter, startBlock, currentBlock).catch(() => []);
for (const event of depositEvents) {
const user = (event as any).args[0];
if (user) userAddresses.add(user.toLowerCase());
}
const delegateFilter = vault.filters.AgentDelegated();
const delegateEvents = await vault.queryFilter(delegateFilter, startBlock, currentBlock).catch(() => []);
for (const event of delegateEvents) {
const user = (event as any).args[0];
if (user) userAddresses.add(user.toLowerCase());
}
// 2. Fetch stats for each user address dynamically from the active contract
let totalVaultStaked = await vault.totalStaked().catch(() => 0n);
const usdtDecimals = isMainnet ? 6 : 18;
const managers = await Promise.all(Array.from(userAddresses).map(async (addr) => {
// Actual volume tracked from ScanGuard backend
const volumeTraded = backendVolumes[addr.toLowerCase()] || 0;
let multiplier = 1.0;
if (volumeTraded >= 50000) {
multiplier = 5.0;
} else if (volumeTraded >= 10000) {
multiplier = 3.0;
} else if (volumeTraded >= 2500) {
multiplier = 2.0;
} else if (volumeTraded >= 500) {
multiplier = 1.5;
}
// Apply elite multiplier to testnet user for demo
if (!isMainnet && addr.toLowerCase() === "0xcd0a2370f2dc12c1802707b7d9ab3fec891e3c02") {
multiplier = 5.0;
}
try {
const userInfo = await vault.users(addr);
const credits = await vault.getCredits(addr);
return {
address: addr,
staked: userInfo.balance,
credits: credits,
volumeTraded,
multiplier
};
} catch (err: any) {
console.error(`Leaderboard fetch error for ${addr}:`, err.message);
return { address: addr, staked: 0n, credits: 0n, volumeTraded, multiplier };
}
}));
// Filter out inactive stakers (must have staked balance or accumulated credits or volume)
const activeManagers = managers.filter((m) => m.staked > 0n || m.credits > 0n || m.volumeTraded > 0);
// Save active stakers back to localStorage
const activeAddresses = activeManagers.map((m) => m.address);
try {
localStorage.setItem(storageKey, JSON.stringify(activeAddresses));
} catch (e) {
console.error("Failed to save stakers to localStorage", e);
}
// 3. Sort by volume descending for Trading Competition
const sorted = activeManagers.sort((a, b) => {
if (b.volumeTraded > a.volumeTraded) return 1;
if (b.volumeTraded < a.volumeTraded) return -1;
return 0;
});
// 4. Map to display formats
const mapped = sorted.map((item, index) => {
let name = language === "zh" ? `特工经理 #${index + 1}` : `Scout Manager #${index + 1}`;
if (item.address.toLowerCase() === DEPLOYED_ADDRESSES.deployer.toLowerCase()) {
name = language === "zh" ? "部署者管理员" : "Deployer Admin";
} else if (wallet.address && item.address.toLowerCase() === wallet.address.toLowerCase()) {
name = language === "zh" ? "您" : "You";
}
let sharePercent = "0%";
if (totalVaultStaked > 0n) {
const pct = (item.staked * 10000n) / totalVaultStaked; // basis points for precision
sharePercent = (Number(pct) / 100).toFixed(1) + "%";
}
return {
rank: index + 1,
address: item.address,
name: name,
credits: parseFloat(ethers.formatEther(item.credits)).toLocaleString(undefined, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}),
portfolio: parseFloat(ethers.formatUnits(item.staked, usdtDecimals)).toFixed(2) + " USDT",
share: sharePercent,
volumeFormatted: "$" + item.volumeTraded.toLocaleString(undefined, { maximumFractionDigits: 0 }),
multiplier: item.multiplier
};
});
// Show up to 100 top managers
const top100 = mapped.slice(0, 100);
setLeaderboard(top100);
} catch (err) {
console.error("Failed to compile dynamic leaderboard:", err);
} finally {
setLoading(false);
}
};
fetchLeaderboard();
const interval = setInterval(fetchLeaderboard, 30000);
return () => clearInterval(interval);
}, [wallet.provider, wallet.address, wallet.chainId, DEPLOYED_ADDRESSES.NoLossVault]);
return (
<div className="leaderboard-panel glass-card" style={{ padding: "24px", marginTop: "24px" }}>
<div className="panel-header" style={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: "8px" }}>
<span className="panel-icon" style={{ display: "flex", alignItems: "center" }}><TrophyIcon size={20} style={{ marginRight: 0 }} /></span>
<h3 className="panel-title" style={{ fontSize: "1.15rem", fontWeight: "700", color: "#fff", margin: 0 }}>
{language === "zh" ? "Pitchside AI 积分排行榜 (第二阶段)" : "Pitchside AI Credit Leaderboard (Phase 2)"} ({isMainnet ? (language === "zh" ? "主网" : "Mainnet") : (language === "zh" ? "测试网沙盒" : "Testnet Sandbox")})
</h3>
</div>
{/* ── Campaign Countdown Banner ──────────────────────────────────────── */}
<div style={{
margin: '0 0 16px',
padding: '14px 16px',
borderRadius: '10px',
border: `1px solid ${campaignTime.phase === 'live' ? 'rgba(0,255,136,0.25)' : 'rgba(255,215,0,0.2)'}`,
background: campaignTime.phase === 'live'
? 'linear-gradient(135deg, rgba(0,255,136,0.04), rgba(0,200,106,0.02))'
: 'linear-gradient(135deg, rgba(255,215,0,0.04), rgba(255,170,0,0.02))',
}}>
{/* Campaign Header */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
<span style={{ fontSize: '0.78rem', fontWeight: '700', color: '#fff', display: 'flex', alignItems: 'center', gap: '6px' }}>
{campaignTime.phase === 'pre' ? (
<><ClockIcon /> {language === "zh" ? "距离活动开始" : "Campaign Starts In"}</>
) : campaignTime.phase === 'live' ? (
<>🟢 {language === "zh" ? "活动进行中 - 结束倒计时" : "Campaign LIVE - Ends In"}</>
) : (
<><FlagIcon /> {language === "zh" ? "活动已结束" : "Campaign Ended"}</>
)}
</span>
<span style={{
fontSize: '0.62rem', fontWeight: '700', padding: '2px 10px', borderRadius: '20px',
background: campaignTime.phase === 'live' ? 'rgba(0,255,136,0.12)' : 'rgba(255,215,0,0.1)',
color: campaignTime.phase === 'live' ? '#00ff88' : '#FFD700',
border: `1px solid ${campaignTime.phase === 'live' ? 'rgba(0,255,136,0.3)' : 'rgba(255,215,0,0.25)'}`,
}}>
{isMainnet ? (language === "zh" ? "积分收益竞赛 · 第二阶段" : "CREDIT YIELD CAMPAIGN · PHASE 2") : (language === "zh" ? "季前交易测试" : "PRE-SEASON TRADING TEST")}
</span>
</div>
{/* Countdown Timer */}
{campaignTime.phase !== 'ended' && (
<div style={{ display: 'flex', gap: '6px', marginBottom: '10px' }}>
{[
{ v: campaignTime.days, l: language === "zh" ? "天" : "D" },
{ v: campaignTime.hours, l: language === "zh" ? "时" : "H" },
{ v: campaignTime.minutes, l: language === "zh" ? "分" : "M" },
{ v: campaignTime.seconds, l: language === "zh" ? "秒" : "S" },
].map((u, i) => (
<React.Fragment key={u.l}>
{i > 0 && <span style={{ alignSelf: 'center', fontSize: '0.9rem', fontWeight: 'bold', color: 'var(--text-tertiary)' }}>:</span>}
<div style={{
background: 'rgba(0,0,0,0.3)', border: '1px solid var(--border-default)',
borderRadius: '6px', padding: '4px 8px', minWidth: '38px', textAlign: 'center',
}}>
<span className="font-mono" style={{ fontSize: '1rem', fontWeight: '800', color: campaignTime.phase === 'live' ? '#00ff88' : '#FFD700' }}>
{String(u.v).padStart(2, '0')}
</span>
<span style={{ fontSize: '0.5rem', display: 'block', color: 'var(--text-tertiary)', letterSpacing: '0.05em' }}>{u.l}</span>
</div>
</React.Fragment>
))}
</div>
)}
{/* Prize Info */}
<div style={{ fontSize: '0.72rem', color: 'var(--text-secondary)', lineHeight: '1.6' }}>
{isMainnet ? (
<>
<strong style={{ color: '#FFD700', display: "inline-flex", alignItems: "center", gap: "4px" }}><TrophyIcon size={14} style={{ marginRight: 0 }} /> {language === "zh" ? "活动规则与奖励 (第二阶段):" : "Campaign Rules & Prizes (Phase 2):"}</strong> {language === "zh" ? "公平竞争!最多质押 10 USDT 并交易至少 2 份球员指数。想更快赚取积分?交易 $PSAI 解锁收益倍数:1.5x (≥$500 交易额) | 2.0x (≥$2.5k) | 3.0x (≥$10k) | 👑 5.0x (≥$50k)。7天冲刺结束后,积分前5名将瓜分 $500 奖金池!" : "Level the playing field! Stake max 10 USDT and trade at least 2 Player Shares to qualify. Trade $PSAI for massive yield boosts: 1.5x (≥$500 Vol) | 2.0x (≥$2.5k) | 3.0x (≥$10k) | 👑 5.0x (≥$50k). After the 7-day sprint, the top 5 credit earners split a $500 Prize Pool!"}
</>
) : (
<>
<strong style={{ color: 'var(--accent-blue)', display: "inline-flex", alignItems: "center", gap: "4px" }}><DumbbellIcon size={14} style={{ marginRight: 0 }} /> {language === "zh" ? "季前交易测试活动:" : "Pre-Season Trading Campaign:"}</strong> {language === "zh" ? "使用测试网体验 $PSAI 交易 volume 排行榜。第一名可模拟获得 $250 的 USDT/PSAI 分成 —— 主网交易大奖赛正式上线中!" : "Practice generating trading volume on the leaderboard. Top ranked users win proportional mock rewards of the $500 pool. X Layer Mainnet Trading Campaign is live!"}
</>
)}
</div>
</div>
{/* ── PSAI Boost Tiers Legend ────────────────────────────────────────── */}
<div style={{
margin: '0 0 16px',
padding: '12px 16px',
borderRadius: '10px',
border: '1px solid rgba(168, 85, 247, 0.2)',
background: 'rgba(168, 85, 247, 0.03)',
fontSize: '0.75rem',
}}>
<div style={{ fontWeight: '700', color: '#fff', marginBottom: '8px', display: 'flex', alignItems: 'center', gap: '4px' }}>
<span>{language === "zh" ? "⚡ 特工收益乘数 (持有 $PSAI 提升虚拟收益倍数)" : "⚡ Scout Multipliers (Hold $PSAI to Boost Virtual Yield)"}</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', textAlign: 'center' }}>
<div style={{ padding: '6px', background: 'rgba(0,0,0,0.2)', borderRadius: '6px', border: '1px solid rgba(168, 85, 247, 0.15)' }}>
<div style={{ color: '#c084fc', fontWeight: 'bold' }}>{language === "zh" ? "1.5倍加速" : "1.5x Boost"}</div>
<div style={{ color: 'var(--text-tertiary)', fontSize: '0.65rem', marginTop: '2px' }}>≥ $500 Vol</div>
</div>
<div style={{ padding: '6px', background: 'rgba(0,0,0,0.2)', borderRadius: '6px', border: '1px solid rgba(14, 165, 233, 0.15)' }}>
<div style={{ color: '#38bdf8', fontWeight: 'bold' }}>{language === "zh" ? "2.0倍加速" : "2.0x Boost"}</div>
<div style={{ color: 'var(--text-tertiary)', fontSize: '0.65rem', marginTop: '2px' }}>≥ $2.5k Vol</div>
</div>
<div style={{ padding: '6px', background: 'rgba(0,0,0,0.2)', borderRadius: '6px', border: '1px solid rgba(245, 158, 11, 0.15)' }}>
<div style={{ color: '#fbbf24', fontWeight: 'bold' }}>{language === "zh" ? "3.0倍加速" : "3.0x Boost"}</div>
<div style={{ color: 'var(--text-tertiary)', fontSize: '0.65rem', marginTop: '2px' }}>≥ $10k Vol</div>
</div>
<div style={{ padding: '6px', background: 'rgba(0,0,0,0.2)', borderRadius: '6px', border: '1px solid rgba(34, 197, 94, 0.15)' }}>
<div style={{ color: '#4ade80', fontWeight: 'bold' }}>{language === "zh" ? "👑 5.0倍加速" : "👑 5.0x Boost"}</div>
<div style={{ color: 'var(--text-tertiary)', fontSize: '0.65rem', marginTop: '2px' }}>≥ $50k Vol</div>
</div>
</div>
</div>
{loading && leaderboard.length === 0 ? (
<div style={{ textAlign: "center", color: "var(--text-tertiary)", fontSize: "0.8rem", padding: "20px" }}>
{language === "zh" ? `⏳ 正在扫描 ${isMainnet ? "主网" : "测试网"} 区块以寻找参与者...` : `⏳ Scanning ${isMainnet ? "Mainnet" : "Testnet"} blocks for participants...`}
</div>
) : (
<div className="leaderboard-table" style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
<div className="table-header" style={{ display: "grid", gridTemplateColumns: "0.5fr 1.8fr 1.4fr 1.1fr 1fr", fontSize: "0.72rem", color: "var(--text-tertiary)", fontWeight: "700", textTransform: "uppercase", paddingBottom: "8px", borderBottom: "1px solid var(--border-default)" }}>
<span>{language === "zh" ? "排名" : "Rank"}</span>
<span>{language === "zh" ? "经理" : "Manager"}</span>
<span style={{ textAlign: "right" }}>{language === "zh" ? "特工积分" : "Scout Credits"}</span>
<span style={{ textAlign: "right" }}>{language === "zh" ? "交易量" : "Volume"}</span>
<span style={{ textAlign: "right" }}>{language === "zh" ? "质押 / 份额" : "Staked / Share"}</span>
</div>
{leaderboard.map((item) => {
const isCurrentUser = wallet.address && item.address.toLowerCase() === wallet.address.toLowerCase();
return (
<div
key={item.rank}
className="leaderboard-row"
style={{
display: "grid",
gridTemplateColumns: "0.5fr 1.8fr 1.4fr 1.1fr 1fr",
fontSize: "0.82rem",
alignItems: "center",
padding: "10px 0",
borderBottom: "1px solid rgba(255, 255, 255, 0.02)",
background: isCurrentUser ? "rgba(0, 255, 136, 0.05)" : "transparent",
borderRadius: isCurrentUser ? "6px" : "0",
paddingLeft: isCurrentUser ? "8px" : "0",
paddingRight: isCurrentUser ? "8px" : "0"
}}
>
<span className="rank-badge" style={{
fontWeight: "800",
color: item.rank === 1 ? "#FFD700" : item.rank === 2 ? "#C0C0C0" : item.rank === 3 ? "#CD7F32" : "var(--text-secondary)"
}}>
{item.rank === 1 ? <GoldMedalIcon size={18} style={{ marginRight: 0 }} /> : item.rank === 2 ? <SilverMedalIcon size={18} style={{ marginRight: 0 }} /> : item.rank === 3 ? <BronzeMedalIcon size={18} style={{ marginRight: 0 }} /> : `#${item.rank}`}
</span>
<span className="manager-info" style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
<span style={{ fontWeight: "600", color: isCurrentUser ? "var(--accent-safe)" : "#fff", display: "flex", alignItems: "center", gap: "6px" }}>
{item.name} {isCurrentUser && (language === "zh" ? "(您)" : "(You)")}
{item.multiplier > 1.0 && (
<span className="badge" style={{
fontSize: "0.6rem",
padding: "1px 6px",
borderRadius: "8px",
fontWeight: item.multiplier >= 3.0 ? "bold" : "normal",
display: "inline-flex",
alignItems: "center",
gap: "2px",
background:
item.multiplier === 5.0 ? "rgba(34, 197, 94, 0.2)" :
item.multiplier === 3.0 ? "rgba(245, 158, 11, 0.2)" :
item.multiplier === 2.0 ? "rgba(14, 165, 233, 0.2)" :
"rgba(168, 85, 247, 0.2)",
color:
item.multiplier === 5.0 ? "#4ade80" :
item.multiplier === 3.0 ? "#fbbf24" :
item.multiplier === 2.0 ? "#38bdf8" :
"#c084fc",
border:
item.multiplier === 5.0 ? "1px solid rgba(34, 197, 94, 0.4)" :
item.multiplier === 3.0 ? "1px solid rgba(245, 158, 11, 0.4)" :
item.multiplier === 2.0 ? "1px solid rgba(14, 165, 233, 0.4)" :
"1px solid rgba(168, 85, 247, 0.4)"
}}>
{item.multiplier === 5.0 ? (language === "zh" ? "👑 5.0倍" : "👑 5.0x") : (language === "zh" ? `⚡ ${item.multiplier.toFixed(1)}倍` : `⚡ ${item.multiplier.toFixed(1)}x`)}
</span>
)}
</span>
<span className="font-mono" style={{ fontSize: "0.68rem", color: "var(--text-tertiary)" }}>
{item.address === "anonymous" ? "—" : `${item.address.slice(0, 6)}...${item.address.slice(-4)}`}
</span>
</span>
<span className="font-mono" style={{ textAlign: "right", color: "var(--accent-safe)", fontWeight: "600" }}>
{item.credits}
</span>
<span className="font-mono" style={{ textAlign: "right", color: "#c084fc", fontWeight: "600" }}>
{item.volumeFormatted}
</span>
<span className="font-mono" style={{ textAlign: "right", color: "var(--text-secondary)", display: "flex", flexDirection: "column", gap: "1px", fontSize: "0.75rem" }}>
<span>{item.portfolio}</span>
<span style={{ fontSize: "0.65rem", color: "var(--text-tertiary)" }}>{language === "zh" ? "份额: " : "Share: "}{item.share}</span>
</span>
</div>
);
})}
</div>
)}
</div>
);
};