@elizaos/plugin-wallet-ui 2.0.3-beta.6 → 2.0.3-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/InventoryView.d.ts +19 -0
  2. package/dist/InventoryView.d.ts.map +1 -0
  3. package/dist/InventoryView.helpers.d.ts +32 -0
  4. package/dist/InventoryView.helpers.d.ts.map +1 -0
  5. package/dist/InventoryView.helpers.js +104 -0
  6. package/dist/InventoryView.helpers.js.map +1 -0
  7. package/dist/InventoryView.interact.d.ts +2 -0
  8. package/dist/InventoryView.interact.d.ts.map +1 -0
  9. package/dist/InventoryView.interact.js +47 -0
  10. package/dist/InventoryView.interact.js.map +1 -0
  11. package/dist/InventoryView.js +242 -0
  12. package/dist/InventoryView.js.map +1 -0
  13. package/dist/components/InventoryAppView.d.ts +2 -0
  14. package/dist/components/InventoryAppView.d.ts.map +1 -0
  15. package/dist/components/InventoryAppView.js +1744 -0
  16. package/dist/components/InventoryAppView.js.map +1 -0
  17. package/dist/components/InventorySpatialView.d.ts +86 -0
  18. package/dist/components/InventorySpatialView.d.ts.map +1 -0
  19. package/dist/components/InventorySpatialView.js +218 -0
  20. package/dist/components/InventorySpatialView.js.map +1 -0
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +65 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/inventory/ChainIcon.d.ts +9 -0
  26. package/dist/inventory/ChainIcon.d.ts.map +1 -0
  27. package/dist/inventory/ChainIcon.js +71 -0
  28. package/dist/inventory/ChainIcon.js.map +1 -0
  29. package/dist/inventory/TokenLogo.d.ts +8 -0
  30. package/dist/inventory/TokenLogo.d.ts.map +1 -0
  31. package/dist/inventory/TokenLogo.js +52 -0
  32. package/dist/inventory/TokenLogo.js.map +1 -0
  33. package/dist/inventory/chainConfig.d.ts +89 -0
  34. package/dist/inventory/chainConfig.d.ts.map +1 -0
  35. package/dist/inventory/chainConfig.js +252 -0
  36. package/dist/inventory/chainConfig.js.map +1 -0
  37. package/dist/inventory/constants.d.ts +31 -0
  38. package/dist/inventory/constants.d.ts.map +1 -0
  39. package/dist/inventory/constants.js +59 -0
  40. package/dist/inventory/constants.js.map +1 -0
  41. package/dist/inventory/index.d.ts +5 -0
  42. package/dist/inventory/index.d.ts.map +1 -0
  43. package/dist/inventory/index.js +43 -0
  44. package/dist/inventory/index.js.map +1 -0
  45. package/dist/inventory/inventory-chain-filters.d.ts +11 -0
  46. package/dist/inventory/inventory-chain-filters.d.ts.map +1 -0
  47. package/dist/inventory/inventory-chain-filters.js +49 -0
  48. package/dist/inventory/inventory-chain-filters.js.map +1 -0
  49. package/dist/inventory/media-url.d.ts +6 -0
  50. package/dist/inventory/media-url.d.ts.map +1 -0
  51. package/dist/inventory/media-url.js +32 -0
  52. package/dist/inventory/media-url.js.map +1 -0
  53. package/dist/inventory/useInventoryData.d.ts +38 -0
  54. package/dist/inventory/useInventoryData.d.ts.map +1 -0
  55. package/dist/inventory/useInventoryData.js +311 -0
  56. package/dist/inventory/useInventoryData.js.map +1 -0
  57. package/dist/plugin.d.ts +3 -0
  58. package/dist/plugin.d.ts.map +1 -0
  59. package/dist/plugin.js +55 -0
  60. package/dist/plugin.js.map +1 -0
  61. package/dist/register-routes.d.ts +9 -0
  62. package/dist/register-routes.d.ts.map +1 -0
  63. package/dist/register-routes.js +22 -0
  64. package/dist/register-routes.js.map +1 -0
  65. package/dist/register-terminal-view.d.ts +15 -0
  66. package/dist/register-terminal-view.d.ts.map +1 -0
  67. package/dist/register-terminal-view.js +33 -0
  68. package/dist/register-terminal-view.js.map +1 -0
  69. package/dist/register.d.ts +4 -0
  70. package/dist/register.d.ts.map +1 -0
  71. package/dist/register.js +20 -0
  72. package/dist/register.js.map +1 -0
  73. package/dist/ui.d.ts +13 -0
  74. package/dist/ui.d.ts.map +1 -0
  75. package/dist/ui.js +62 -0
  76. package/dist/ui.js.map +1 -0
  77. package/dist/views/bundle.js +1072 -0
  78. package/dist/views/bundle.js.map +1 -0
  79. package/dist/wallet-rpc.d.ts +2 -0
  80. package/dist/wallet-rpc.d.ts.map +1 -0
  81. package/dist/wallet-rpc.js +9 -0
  82. package/dist/wallet-rpc.js.map +1 -0
  83. package/dist/wallet-view-bundle.d.ts +3 -0
  84. package/dist/wallet-view-bundle.d.ts.map +1 -0
  85. package/dist/wallet-view-bundle.js +7 -0
  86. package/dist/wallet-view-bundle.js.map +1 -0
  87. package/dist/widgets/wallet-status.d.ts +3 -0
  88. package/dist/widgets/wallet-status.d.ts.map +1 -0
  89. package/dist/widgets/wallet-status.helpers.d.ts +3 -0
  90. package/dist/widgets/wallet-status.helpers.d.ts.map +1 -0
  91. package/dist/widgets/wallet-status.helpers.js +12 -0
  92. package/dist/widgets/wallet-status.helpers.js.map +1 -0
  93. package/dist/widgets/wallet-status.js +291 -0
  94. package/dist/widgets/wallet-status.js.map +1 -0
  95. package/package.json +7 -7
@@ -0,0 +1,1744 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useAgentElement } from "@elizaos/ui/agent-surface";
3
+ import { client } from "@elizaos/ui/api";
4
+ import { Button } from "@elizaos/ui/components";
5
+ import { useActivityEvents } from "@elizaos/ui/hooks";
6
+ import { useAppSelectorShallow } from "@elizaos/ui/state";
7
+ import { cn } from "@elizaos/ui/utils";
8
+ import {
9
+ Activity,
10
+ AlertTriangle,
11
+ ArrowLeftRight,
12
+ BarChart3,
13
+ CheckCircle2,
14
+ Copy,
15
+ EyeOff,
16
+ Image as ImageIcon,
17
+ Layers3,
18
+ Sparkles,
19
+ TrendingDown,
20
+ TrendingUp,
21
+ Wallet
22
+ } from "lucide-react";
23
+ import {
24
+ memo,
25
+ useCallback,
26
+ useEffect,
27
+ useMemo,
28
+ useRef,
29
+ useState
30
+ } from "react";
31
+ import { resolveWalletAddresses } from "../InventoryView.helpers";
32
+ import { getNativeLogoUrl } from "../inventory/chainConfig.js";
33
+ import {
34
+ formatBalance
35
+ } from "../inventory/constants.js";
36
+ import { TokenLogo } from "../inventory/TokenLogo.js";
37
+ import { useInventoryData } from "../inventory/useInventoryData.js";
38
+ const ALL_INVENTORY_FILTERS = {
39
+ ethereum: true,
40
+ base: true,
41
+ bsc: true,
42
+ avax: true,
43
+ solana: true
44
+ };
45
+ const SUPPORTED_WALLET_CHAINS = Object.keys(ALL_INVENTORY_FILTERS);
46
+ const DASHBOARD_WINDOWS = ["24h", "7d", "30d"];
47
+ const HIDDEN_TOKEN_IDS_KEY = "eliza:wallet:hidden-token-ids:v1";
48
+ const WALLET_REFRESH_INTERVAL_MS = 2e4;
49
+ const usdFormatter = new Intl.NumberFormat("en-US", {
50
+ style: "currency",
51
+ currency: "USD",
52
+ maximumFractionDigits: 2
53
+ });
54
+ const compactFormatter = new Intl.NumberFormat("en-US", {
55
+ maximumFractionDigits: 4
56
+ });
57
+ function readHiddenTokenIds() {
58
+ if (typeof window === "undefined") return /* @__PURE__ */ new Set();
59
+ try {
60
+ const raw = window.localStorage.getItem(HIDDEN_TOKEN_IDS_KEY);
61
+ if (!raw) return /* @__PURE__ */ new Set();
62
+ const parsed = JSON.parse(raw);
63
+ if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
64
+ return new Set(
65
+ parsed.filter((item) => typeof item === "string")
66
+ );
67
+ } catch {
68
+ return /* @__PURE__ */ new Set();
69
+ }
70
+ }
71
+ function writeHiddenTokenIds(next) {
72
+ if (typeof window === "undefined") return;
73
+ try {
74
+ window.localStorage.setItem(
75
+ HIDDEN_TOKEN_IDS_KEY,
76
+ JSON.stringify([...next])
77
+ );
78
+ } catch {
79
+ return;
80
+ }
81
+ }
82
+ function tokenId(row) {
83
+ const address = row.contractAddress && row.contractAddress.length > 0 ? row.contractAddress.toLowerCase() : `native:${row.symbol.toLowerCase()}`;
84
+ return `${row.chain.toLowerCase()}:${address}`;
85
+ }
86
+ function tokenAgentSlug(row) {
87
+ return tokenId(row).replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
88
+ }
89
+ function normalizeTokenAddress(address) {
90
+ return address ? address.toLowerCase() : null;
91
+ }
92
+ function formatUsd(value) {
93
+ if (!Number.isFinite(value)) return usdFormatter.format(0);
94
+ return usdFormatter.format(value);
95
+ }
96
+ function formatMarketUsd(value) {
97
+ if (!Number.isFinite(value)) return usdFormatter.format(0);
98
+ const fractionDigits = value >= 1e3 ? 0 : value >= 1 ? 2 : value >= 0.01 ? 4 : 6;
99
+ const minimumFractionDigits = value >= 1 ? Math.min(2, fractionDigits) : 0;
100
+ return value.toLocaleString("en-US", {
101
+ style: "currency",
102
+ currency: "USD",
103
+ minimumFractionDigits,
104
+ maximumFractionDigits: fractionDigits
105
+ });
106
+ }
107
+ function formatPercentDelta(value) {
108
+ if (!Number.isFinite(value)) return "0.0%";
109
+ const magnitude = Math.abs(value).toLocaleString("en-US", {
110
+ minimumFractionDigits: 1,
111
+ maximumFractionDigits: 1
112
+ });
113
+ const sign = value > 0 ? "+" : value < 0 ? "-" : "";
114
+ return `${sign}${magnitude}%`;
115
+ }
116
+ function formatCompactAddress(address) {
117
+ if (address.length <= 12) return address;
118
+ return `${address.slice(0, 5)}...${address.slice(-4)}`;
119
+ }
120
+ function formatBnb(value) {
121
+ if (!value) return "0 BNB";
122
+ const parsed = Number.parseFloat(value);
123
+ if (!Number.isFinite(parsed)) return `${value} BNB`;
124
+ return `${compactFormatter.format(parsed)} BNB`;
125
+ }
126
+ function parseAmount(value) {
127
+ if (!value) return null;
128
+ const parsed = Number.parseFloat(value);
129
+ return Number.isFinite(parsed) ? parsed : null;
130
+ }
131
+ function formatSignedBnb(value) {
132
+ const sign = value > 0 ? "+" : value < 0 ? "-" : "";
133
+ return `${sign}${compactFormatter.format(Math.abs(value))} BNB`;
134
+ }
135
+ function hasClosedTradePnl(profile) {
136
+ return (profile?.summary.evaluatedTrades ?? 0) > 0;
137
+ }
138
+ function providerLabel(provider, chain) {
139
+ switch (provider) {
140
+ case "eliza-cloud":
141
+ return chain === "solana" ? "Eliza Cloud / Helius" : "Eliza Cloud";
142
+ case "alchemy":
143
+ return "Alchemy";
144
+ case "quicknode":
145
+ return "QuickNode";
146
+ case "helius-birdeye":
147
+ return "Helius + Birdeye";
148
+ case "custom":
149
+ return "Custom";
150
+ default:
151
+ return "Not configured";
152
+ }
153
+ }
154
+ function formatRelativeTimestamp(timestamp) {
155
+ const diff = Date.now() - timestamp;
156
+ if (diff < 0) return "now";
157
+ if (diff < 6e4) return "just now";
158
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
159
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
160
+ if (diff < 6048e5) return `${Math.floor(diff / 864e5)}d ago`;
161
+ return new Date(timestamp).toLocaleDateString();
162
+ }
163
+ function tradingProfileWindow(window2) {
164
+ return window2 === "24h" ? "24h" : window2;
165
+ }
166
+ function tokenHasInventory(row) {
167
+ return row.balanceRaw > 0 || row.valueUsd > 0;
168
+ }
169
+ function assetAllocationRows(rows) {
170
+ return rows.filter((row) => row.valueUsd > 0).sort((left, right) => right.valueUsd - left.valueUsd).slice(0, 5);
171
+ }
172
+ function looksLikeLpPosition(value) {
173
+ const text = ` ${value.toLowerCase()} `;
174
+ return text.includes(" liquidity ") || text.includes(" lp ") || text.includes("-lp") || text.includes("/lp") || text.includes(" pool ") || text.includes(" position ") || text.includes(" clmm ") || text.includes(" amm ");
175
+ }
176
+ function deriveInventoryPositionAssets({
177
+ tokenRows,
178
+ nfts
179
+ }) {
180
+ const positions = [];
181
+ for (const row of tokenRows) {
182
+ if (!looksLikeLpPosition(`${row.name} ${row.symbol}`)) continue;
183
+ positions.push({
184
+ id: `token:${tokenId(row)}`,
185
+ kind: "token",
186
+ label: row.symbol,
187
+ detail: `${formatBalance(row.balance)} ${row.symbol}`,
188
+ valueUsd: row.valueUsd,
189
+ imageUrl: row.logoUrl
190
+ });
191
+ }
192
+ for (const nft of nfts) {
193
+ if (!looksLikeLpPosition(`${nft.collectionName} ${nft.name}`)) continue;
194
+ positions.push({
195
+ id: `nft:${nft.collectionName}:${nft.name}:${nft.imageUrl}`,
196
+ kind: "nft",
197
+ label: nft.name,
198
+ detail: nft.collectionName,
199
+ valueUsd: null,
200
+ imageUrl: nft.imageUrl
201
+ });
202
+ }
203
+ return positions;
204
+ }
205
+ function tokenBreakdownForRow(row, profile) {
206
+ const normalizedAddress = normalizeTokenAddress(row.contractAddress);
207
+ if (!normalizedAddress || !profile) return null;
208
+ return profile.tokenBreakdown.find(
209
+ (item) => item.tokenAddress.toLowerCase() === normalizedAddress
210
+ ) ?? null;
211
+ }
212
+ function portfolioMovers(rows, profile) {
213
+ if (!profile) return [];
214
+ return rows.map((row) => {
215
+ const breakdown = tokenBreakdownForRow(row, profile);
216
+ const realizedPnlBnb = parseAmount(breakdown?.realizedPnlBnb);
217
+ if (realizedPnlBnb === null || realizedPnlBnb === 0) return null;
218
+ return {
219
+ row,
220
+ realizedPnlBnb
221
+ };
222
+ }).filter((mover) => mover !== null);
223
+ }
224
+ function TokenPerformance({
225
+ row,
226
+ profile,
227
+ maxAbsPnl
228
+ }) {
229
+ const breakdown = tokenBreakdownForRow(row, profile);
230
+ if (!breakdown) {
231
+ return null;
232
+ }
233
+ const pnl = parseAmount(breakdown.realizedPnlBnb);
234
+ if (pnl === null) return null;
235
+ const width = maxAbsPnl > 0 ? Math.max(18, Math.abs(pnl) / maxAbsPnl * 56) : 18;
236
+ const TrendIcon = pnl >= 0 ? TrendingUp : TrendingDown;
237
+ const tone = pnl === 0 ? "text-muted" : pnl > 0 ? "text-txt" : "text-danger";
238
+ const barTone = pnl === 0 ? "bg-border" : pnl > 0 ? "bg-txt/70" : "bg-danger/80";
239
+ return /* @__PURE__ */ jsxs("span", { className: "flex min-w-[4.5rem] flex-col items-end gap-1", children: [
240
+ /* @__PURE__ */ jsxs(
241
+ "span",
242
+ {
243
+ className: cn(
244
+ "inline-flex items-center gap-1 text-[0.68rem] font-medium",
245
+ tone
246
+ ),
247
+ children: [
248
+ /* @__PURE__ */ jsx(TrendIcon, { className: "h-3 w-3" }),
249
+ pnl > 0 ? "+" : "",
250
+ formatBnb(breakdown.realizedPnlBnb)
251
+ ]
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsx(
255
+ "span",
256
+ {
257
+ className: "flex h-1.5 w-14 justify-end overflow-hidden rounded-full bg-border/45",
258
+ "aria-hidden": "true",
259
+ children: /* @__PURE__ */ jsx(
260
+ "span",
261
+ {
262
+ className: cn("h-full rounded-full", barTone),
263
+ style: { width }
264
+ }
265
+ )
266
+ }
267
+ )
268
+ ] });
269
+ }
270
+ function maxAbsTokenPnl(rows, profile) {
271
+ if (!profile) return 0;
272
+ let max = 0;
273
+ for (const row of rows) {
274
+ const breakdown = tokenBreakdownForRow(row, profile);
275
+ const pnl = parseAmount(breakdown?.realizedPnlBnb);
276
+ if (pnl !== null) max = Math.max(max, Math.abs(pnl));
277
+ }
278
+ return max;
279
+ }
280
+ function ChainLogoBadge({
281
+ chain,
282
+ size = 18,
283
+ className
284
+ }) {
285
+ const [errored, setErrored] = useState(false);
286
+ const logoUrl = errored ? null : getNativeLogoUrl(chain);
287
+ return /* @__PURE__ */ jsx(
288
+ "span",
289
+ {
290
+ className: cn(
291
+ "inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full bg-bg ring-2 ring-bg",
292
+ className
293
+ ),
294
+ style: { width: size, height: size },
295
+ title: chain,
296
+ role: "img",
297
+ "aria-label": chain,
298
+ children: logoUrl ? /* @__PURE__ */ jsx(
299
+ "img",
300
+ {
301
+ src: logoUrl,
302
+ alt: "",
303
+ className: "h-full w-full object-cover",
304
+ onError: () => setErrored(true)
305
+ }
306
+ ) : /* @__PURE__ */ jsx("span", { className: "font-mono text-[0.58rem] font-bold uppercase text-muted", children: chain.charAt(0) })
307
+ }
308
+ );
309
+ }
310
+ function TokenIdentityIcon({
311
+ row,
312
+ size = 46
313
+ }) {
314
+ const badgeSize = Math.max(16, Math.round(size * 0.38));
315
+ return /* @__PURE__ */ jsxs(
316
+ "span",
317
+ {
318
+ className: "relative inline-flex shrink-0",
319
+ style: { width: size, height: size },
320
+ children: [
321
+ /* @__PURE__ */ jsx(
322
+ TokenLogo,
323
+ {
324
+ symbol: row.symbol,
325
+ chain: row.chain,
326
+ contractAddress: row.contractAddress,
327
+ preferredLogoUrl: row.logoUrl,
328
+ size
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx(
332
+ ChainLogoBadge,
333
+ {
334
+ chain: row.chain,
335
+ size: badgeSize,
336
+ className: "-bottom-0.5 -right-0.5 absolute"
337
+ }
338
+ )
339
+ ]
340
+ }
341
+ );
342
+ }
343
+ function allocationToneClass(index) {
344
+ return index === 0 ? "bg-accent" : index === 1 ? "bg-accent/70" : index === 2 ? "bg-accent/45" : index === 3 ? "bg-muted/60" : "bg-muted/35";
345
+ }
346
+ function AssetAllocationStrip({
347
+ rows,
348
+ compact = false
349
+ }) {
350
+ const allocationRows = useMemo(() => assetAllocationRows(rows), [rows]);
351
+ const total = allocationRows.reduce((sum, row) => sum + row.valueUsd, 0);
352
+ if (total <= 0 || allocationRows.length === 0) return null;
353
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", compact && "space-y-3"), children: [
354
+ /* @__PURE__ */ jsx(
355
+ "div",
356
+ {
357
+ className: cn(
358
+ "flex overflow-hidden rounded-full bg-border/40",
359
+ compact ? "h-2.5" : "h-2"
360
+ ),
361
+ children: allocationRows.map((row, index) => /* @__PURE__ */ jsx(
362
+ "span",
363
+ {
364
+ className: cn("h-full", allocationToneClass(index)),
365
+ style: { width: `${row.valueUsd / total * 100}%` },
366
+ title: `${row.symbol}: ${formatUsd(row.valueUsd)}`
367
+ },
368
+ tokenId(row)
369
+ ))
370
+ }
371
+ ),
372
+ compact ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: allocationRows.slice(0, 3).map((row, index) => /* @__PURE__ */ jsxs(
373
+ "div",
374
+ {
375
+ className: "inline-flex items-center gap-1.5 text-[0.68rem] font-medium text-txt",
376
+ children: [
377
+ /* @__PURE__ */ jsx(
378
+ "span",
379
+ {
380
+ className: cn(
381
+ "h-1.5 w-1.5 rounded-full",
382
+ allocationToneClass(index)
383
+ )
384
+ }
385
+ ),
386
+ /* @__PURE__ */ jsx("span", { children: row.symbol })
387
+ ]
388
+ },
389
+ tokenId(row)
390
+ )) }) : /* @__PURE__ */ jsx("div", { className: "grid gap-1", children: allocationRows.slice(0, 3).map((row) => /* @__PURE__ */ jsxs(
391
+ "div",
392
+ {
393
+ className: "flex items-center justify-between gap-2 text-[0.68rem]",
394
+ children: [
395
+ /* @__PURE__ */ jsx("span", { className: "truncate text-muted", children: row.symbol }),
396
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-txt", children: formatUsd(row.valueUsd) })
397
+ ]
398
+ },
399
+ tokenId(row)
400
+ )) })
401
+ ] });
402
+ }
403
+ function PortfolioMoverRow({
404
+ mover,
405
+ maxAbsPnl
406
+ }) {
407
+ const isGain = mover.realizedPnlBnb > 0;
408
+ const width = maxAbsPnl > 0 ? Math.max(18, Math.abs(mover.realizedPnlBnb) / maxAbsPnl * 100) : 18;
409
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-1 py-2", children: [
410
+ /* @__PURE__ */ jsx(TokenIdentityIcon, { row: mover.row, size: 34 }),
411
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
412
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-semibold text-txt", children: mover.row.symbol }),
413
+ /* @__PURE__ */ jsx("div", { className: "mt-1 h-1.5 overflow-hidden rounded-full bg-border/45", children: /* @__PURE__ */ jsx(
414
+ "div",
415
+ {
416
+ className: cn(
417
+ "h-full rounded-full",
418
+ isGain ? "bg-txt/70" : "bg-danger/85"
419
+ ),
420
+ style: { width: `${width}%` }
421
+ }
422
+ ) })
423
+ ] }),
424
+ /* @__PURE__ */ jsx(
425
+ "div",
426
+ {
427
+ className: cn(
428
+ "shrink-0 text-right font-mono text-xs font-semibold",
429
+ isGain ? "text-txt" : "text-danger"
430
+ ),
431
+ children: formatSignedBnb(mover.realizedPnlBnb)
432
+ }
433
+ )
434
+ ] });
435
+ }
436
+ function PortfolioMoverColumn({
437
+ title,
438
+ movers,
439
+ maxAbsPnl,
440
+ tone
441
+ }) {
442
+ return /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-2", children: [
443
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-semibold text-txt", children: [
444
+ tone === "gain" ? /* @__PURE__ */ jsx(TrendingUp, { className: "h-3.5 w-3.5 text-muted" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "h-3.5 w-3.5 text-danger" }),
445
+ title
446
+ ] }),
447
+ movers.length > 0 ? /* @__PURE__ */ jsx("div", { className: "space-y-1", children: movers.map((mover) => /* @__PURE__ */ jsx(
448
+ PortfolioMoverRow,
449
+ {
450
+ mover,
451
+ maxAbsPnl
452
+ },
453
+ `${tokenId(mover.row)}:${mover.realizedPnlBnb}`
454
+ )) }) : /* @__PURE__ */ jsx("div", { className: "flex h-[3.75rem] items-center px-1 text-xs-tight text-muted", children: "None" })
455
+ ] });
456
+ }
457
+ function PortfolioMoversPanel({
458
+ rows,
459
+ profile,
460
+ marketOverview
461
+ }) {
462
+ const movers = useMemo(() => portfolioMovers(rows, profile), [rows, profile]);
463
+ const gainers = useMemo(
464
+ () => movers.filter((mover) => mover.realizedPnlBnb > 0).sort((left, right) => right.realizedPnlBnb - left.realizedPnlBnb).slice(0, 3),
465
+ [movers]
466
+ );
467
+ const losers = useMemo(
468
+ () => movers.filter((mover) => mover.realizedPnlBnb < 0).sort((left, right) => left.realizedPnlBnb - right.realizedPnlBnb).slice(0, 3),
469
+ [movers]
470
+ );
471
+ const maxAbsPnl = useMemo(
472
+ () => movers.reduce(
473
+ (max, mover) => Math.max(max, Math.abs(mover.realizedPnlBnb)),
474
+ 0
475
+ ),
476
+ [movers]
477
+ );
478
+ if (movers.length === 0) {
479
+ if (marketOverview?.movers.length) {
480
+ return /* @__PURE__ */ jsx(
481
+ MarketMoverList,
482
+ {
483
+ movers: marketOverview.movers,
484
+ source: marketOverview.sources.movers
485
+ }
486
+ );
487
+ }
488
+ return /* @__PURE__ */ jsx(EmptyState, { icon: TrendingUp, title: "None" });
489
+ }
490
+ return /* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [
491
+ /* @__PURE__ */ jsx(
492
+ PortfolioMoverColumn,
493
+ {
494
+ title: "Gainers",
495
+ movers: gainers,
496
+ maxAbsPnl,
497
+ tone: "gain"
498
+ }
499
+ ),
500
+ /* @__PURE__ */ jsx(
501
+ PortfolioMoverColumn,
502
+ {
503
+ title: "Losers",
504
+ movers: losers,
505
+ maxAbsPnl,
506
+ tone: "loss"
507
+ }
508
+ )
509
+ ] });
510
+ }
511
+ function EmptyState({
512
+ icon: Icon,
513
+ title,
514
+ body
515
+ }) {
516
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-[8rem] flex-col items-center justify-center px-4 py-6 text-center", children: [
517
+ /* @__PURE__ */ jsx(Icon, { className: "mb-3 h-5 w-5 text-muted" }),
518
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-txt", children: title }),
519
+ body ? /* @__PURE__ */ jsx("div", { className: "sr-only mt-1 max-w-sm text-xs-tight text-muted", children: body }) : null
520
+ ] });
521
+ }
522
+ function MarketAvatar({
523
+ imageUrl,
524
+ label
525
+ }) {
526
+ if (imageUrl) {
527
+ return /* @__PURE__ */ jsx(
528
+ "img",
529
+ {
530
+ src: imageUrl,
531
+ alt: label,
532
+ className: "h-11 w-11 shrink-0 object-cover",
533
+ loading: "lazy"
534
+ }
535
+ );
536
+ }
537
+ return /* @__PURE__ */ jsx("div", { className: "flex h-11 w-11 shrink-0 items-center justify-center text-sm font-semibold text-txt", children: label.slice(0, 1).toUpperCase() });
538
+ }
539
+ function MarketSourceBadge({ source }) {
540
+ return /* @__PURE__ */ jsx(
541
+ "a",
542
+ {
543
+ href: source.providerUrl,
544
+ target: "_blank",
545
+ rel: "noreferrer",
546
+ className: "text-[0.68rem] font-medium text-muted transition-colors hover:text-txt",
547
+ children: source.providerName
548
+ }
549
+ );
550
+ }
551
+ function MarketSectionHeader({
552
+ icon: Icon,
553
+ title,
554
+ source
555
+ }) {
556
+ return /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center gap-2 text-sm font-semibold text-txt", children: [
557
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-accent" }),
558
+ /* @__PURE__ */ jsx("span", { children: title }),
559
+ /* @__PURE__ */ jsx(MarketSourceBadge, { source })
560
+ ] });
561
+ }
562
+ function MarketDataUnavailable({
563
+ title,
564
+ source
565
+ }) {
566
+ return /* @__PURE__ */ jsxs("div", { className: "px-1 py-2", title: `${title} unavailable`, children: [
567
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-warn", children: "Unavailable" }),
568
+ /* @__PURE__ */ jsx("div", { className: "sr-only mt-1 text-xs text-muted", children: source.error ?? `${source.providerName} did not return live data.` })
569
+ ] });
570
+ }
571
+ function MajorPriceCard({ snapshot }) {
572
+ const isPositive = snapshot.change24hPct >= 0;
573
+ return /* @__PURE__ */ jsxs("div", { className: "min-w-0 p-2", children: [
574
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
575
+ /* @__PURE__ */ jsx(MarketAvatar, { imageUrl: snapshot.imageUrl, label: snapshot.symbol }),
576
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
577
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-txt", children: snapshot.symbol }),
578
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs-tight text-muted", children: snapshot.name })
579
+ ] })
580
+ ] }),
581
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex flex-wrap items-end justify-between gap-x-3 gap-y-1", children: [
582
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 font-mono text-lg font-semibold text-txt sm:text-xl", children: formatMarketUsd(snapshot.priceUsd) }),
583
+ /* @__PURE__ */ jsx(
584
+ "div",
585
+ {
586
+ className: cn(
587
+ "shrink-0 text-sm font-semibold",
588
+ isPositive ? "text-txt" : "text-danger"
589
+ ),
590
+ children: formatPercentDelta(snapshot.change24hPct)
591
+ }
592
+ )
593
+ ] })
594
+ ] });
595
+ }
596
+ function MarketPriceGrid({
597
+ prices,
598
+ source
599
+ }) {
600
+ if (!source.available) {
601
+ return /* @__PURE__ */ jsx(MarketDataUnavailable, { title: "Spot prices", source });
602
+ }
603
+ if (prices.length === 0) {
604
+ return /* @__PURE__ */ jsx(EmptyState, { icon: BarChart3, title: "None" });
605
+ }
606
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fit,minmax(min(100%,13.5rem),1fr))] gap-3", children: prices.map((snapshot) => /* @__PURE__ */ jsx(MajorPriceCard, { snapshot }, snapshot.id)) });
607
+ }
608
+ function MarketMoverList({
609
+ movers,
610
+ source
611
+ }) {
612
+ if (!source.available) {
613
+ return /* @__PURE__ */ jsx(MarketDataUnavailable, { title: "Top movers", source });
614
+ }
615
+ if (movers.length === 0) {
616
+ return /* @__PURE__ */ jsx(EmptyState, { icon: TrendingUp, title: "None" });
617
+ }
618
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: movers.map((mover) => {
619
+ const isPositive = mover.change24hPct >= 0;
620
+ return /* @__PURE__ */ jsxs(
621
+ "div",
622
+ {
623
+ className: "flex min-w-0 items-center gap-3 px-1 py-2.5",
624
+ children: [
625
+ /* @__PURE__ */ jsx(MarketAvatar, { imageUrl: mover.imageUrl, label: mover.symbol }),
626
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
627
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [
628
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-txt", children: mover.symbol }),
629
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs-tight text-muted", children: mover.name })
630
+ ] }),
631
+ mover.marketCapRank !== null ? /* @__PURE__ */ jsxs("div", { className: "mt-1 text-[0.68rem] font-medium text-muted", children: [
632
+ "Cap rank #",
633
+ mover.marketCapRank
634
+ ] }) : null
635
+ ] }),
636
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right", children: [
637
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-sm font-semibold text-txt", children: formatMarketUsd(mover.priceUsd) }),
638
+ /* @__PURE__ */ jsx(
639
+ "div",
640
+ {
641
+ className: cn(
642
+ "text-xs font-semibold",
643
+ isPositive ? "text-txt" : "text-danger"
644
+ ),
645
+ children: formatPercentDelta(mover.change24hPct)
646
+ }
647
+ )
648
+ ] })
649
+ ]
650
+ },
651
+ mover.id
652
+ );
653
+ }) });
654
+ }
655
+ function WalletMotif() {
656
+ return /* @__PURE__ */ jsxs(
657
+ "svg",
658
+ {
659
+ viewBox: "0 0 120 120",
660
+ role: "img",
661
+ "aria-label": "Empty wallet",
662
+ className: "h-24 w-24",
663
+ children: [
664
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "walletMotifFill", x1: "0", y1: "0", x2: "1", y2: "1", children: [
665
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--accent)", stopOpacity: "0.9" }),
666
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "var(--accent)", stopOpacity: "0.35" })
667
+ ] }) }),
668
+ /* @__PURE__ */ jsx(
669
+ "circle",
670
+ {
671
+ cx: "60",
672
+ cy: "60",
673
+ r: "56",
674
+ fill: "url(#walletMotifFill)",
675
+ opacity: "0.12"
676
+ }
677
+ ),
678
+ /* @__PURE__ */ jsx(
679
+ "rect",
680
+ {
681
+ x: "30",
682
+ y: "42",
683
+ width: "60",
684
+ height: "40",
685
+ rx: "10",
686
+ fill: "url(#walletMotifFill)",
687
+ opacity: "0.85"
688
+ }
689
+ ),
690
+ /* @__PURE__ */ jsx(
691
+ "rect",
692
+ {
693
+ x: "30",
694
+ y: "42",
695
+ width: "60",
696
+ height: "14",
697
+ rx: "7",
698
+ fill: "var(--accent)",
699
+ opacity: "0.5"
700
+ }
701
+ ),
702
+ /* @__PURE__ */ jsx("circle", { cx: "78", cy: "62", r: "6", fill: "var(--bg)", opacity: "0.85" }),
703
+ /* @__PURE__ */ jsx("circle", { cx: "78", cy: "62", r: "2.5", fill: "var(--accent)" })
704
+ ]
705
+ }
706
+ );
707
+ }
708
+ function WalletEmptyHero({
709
+ hasKeys,
710
+ onConfigureKeys
711
+ }) {
712
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4 px-6 py-10 text-center", children: [
713
+ /* @__PURE__ */ jsx(WalletMotif, {}),
714
+ /* @__PURE__ */ jsx("div", { className: "text-base font-semibold text-txt", children: hasKeys ? "None" : "Wallet" }),
715
+ hasKeys ? null : /* @__PURE__ */ jsx(
716
+ Button,
717
+ {
718
+ type: "button",
719
+ variant: "surfaceAccent",
720
+ size: "sm",
721
+ onClick: onConfigureKeys,
722
+ children: "Keys"
723
+ }
724
+ )
725
+ ] });
726
+ }
727
+ function MarketPulseHero({
728
+ overview,
729
+ loading,
730
+ hasKeys,
731
+ onConfigureKeys
732
+ }) {
733
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-6", children: [
734
+ /* @__PURE__ */ jsx(WalletEmptyHero, { hasKeys, onConfigureKeys }),
735
+ overview ? /* @__PURE__ */ jsx("div", { className: "grid gap-4 xl:grid-cols-[minmax(0,1.2fr)_minmax(0,0.92fr)]", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
736
+ /* @__PURE__ */ jsxs("div", { children: [
737
+ /* @__PURE__ */ jsx(
738
+ MarketSectionHeader,
739
+ {
740
+ icon: BarChart3,
741
+ title: "Spot prices",
742
+ source: overview.sources.prices
743
+ }
744
+ ),
745
+ /* @__PURE__ */ jsx(
746
+ MarketPriceGrid,
747
+ {
748
+ prices: overview.prices,
749
+ source: overview.sources.prices
750
+ }
751
+ )
752
+ ] }),
753
+ /* @__PURE__ */ jsxs("div", { children: [
754
+ /* @__PURE__ */ jsx(
755
+ MarketSectionHeader,
756
+ {
757
+ icon: TrendingUp,
758
+ title: "Top movers",
759
+ source: overview.sources.movers
760
+ }
761
+ ),
762
+ /* @__PURE__ */ jsx(
763
+ MarketMoverList,
764
+ {
765
+ movers: overview.movers,
766
+ source: overview.sources.movers
767
+ }
768
+ )
769
+ ] })
770
+ ] }) }) : loading ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-[repeat(auto-fit,minmax(min(100%,13.5rem),1fr))] gap-3", children: ["btc", "eth", "sol"].map((loadingCardId) => /* @__PURE__ */ jsx("div", { className: "h-28 animate-pulse bg-bg/20" }, loadingCardId)) }) : null
771
+ ] });
772
+ }
773
+ function activityEventMeta(eventType) {
774
+ if (eventType === "task_complete" || eventType === "blocked_auto_resolved") {
775
+ return { icon: Sparkles, tone: "ok" };
776
+ }
777
+ if (eventType === "blocked" || eventType === "escalation") {
778
+ return { icon: Activity, tone: "warn" };
779
+ }
780
+ if (eventType === "error") {
781
+ return { icon: Activity, tone: "danger" };
782
+ }
783
+ return { icon: Activity, tone: "default" };
784
+ }
785
+ function walletTimelineEntries({
786
+ profile,
787
+ events
788
+ }) {
789
+ const swapEntries = (profile?.recentSwaps ?? []).reduce((entries, swap) => {
790
+ const timestamp = Date.parse(swap.createdAt);
791
+ if (!Number.isFinite(timestamp)) return entries;
792
+ entries.push({
793
+ id: `swap:${swap.hash}`,
794
+ timestamp,
795
+ title: `${swap.side === "buy" ? "Bought" : "Sold"} ${swap.tokenSymbol}`,
796
+ detail: `${swap.inputAmount} ${swap.inputSymbol} -> ${swap.outputAmount} ${swap.outputSymbol}`,
797
+ href: swap.explorerUrl,
798
+ icon: ArrowLeftRight,
799
+ tone: swap.status === "success" ? "ok" : swap.status === "pending" ? "warn" : "danger"
800
+ });
801
+ return entries;
802
+ }, []);
803
+ const agentEntries = events.map((event) => {
804
+ const meta = activityEventMeta(event.eventType);
805
+ return {
806
+ id: `agent:${event.id}`,
807
+ timestamp: event.timestamp,
808
+ title: event.summary,
809
+ icon: meta.icon,
810
+ tone: meta.tone
811
+ };
812
+ });
813
+ return [...swapEntries, ...agentEntries].sort((left, right) => right.timestamp - left.timestamp).slice(0, 18);
814
+ }
815
+ function PnlChart({
816
+ profile
817
+ }) {
818
+ const points = profile?.pnlSeries ?? [];
819
+ const values = points.map((point) => parseAmount(point.realizedPnlBnb)).filter((value) => value !== null);
820
+ if (values.length < 2) {
821
+ return /* @__PURE__ */ jsx("div", { className: "flex h-40 items-center justify-center text-xs text-muted", children: "P&L pending" });
822
+ }
823
+ const min = Math.min(...values);
824
+ const max = Math.max(...values);
825
+ const span = max - min || 1;
826
+ const svgPoints = values.map((value, index) => {
827
+ const x = index / (values.length - 1) * 100;
828
+ const y = 88 - (value - min) / span * 72;
829
+ return `${x},${y}`;
830
+ }).join(" ");
831
+ const latest = values[values.length - 1];
832
+ const stroke = latest >= 0 ? "var(--muted-strong)" : "var(--danger)";
833
+ return /* @__PURE__ */ jsx(
834
+ "svg",
835
+ {
836
+ className: "h-40 w-full",
837
+ viewBox: "0 0 100 100",
838
+ preserveAspectRatio: "none",
839
+ "aria-label": "Trade P&L chart",
840
+ children: /* @__PURE__ */ jsx(
841
+ "polyline",
842
+ {
843
+ fill: "none",
844
+ stroke,
845
+ strokeWidth: "3",
846
+ strokeLinecap: "round",
847
+ strokeLinejoin: "round",
848
+ points: svgPoints,
849
+ vectorEffect: "non-scaling-stroke"
850
+ }
851
+ )
852
+ }
853
+ );
854
+ }
855
+ function SummaryChip({
856
+ icon: Icon,
857
+ value,
858
+ tone = "default",
859
+ title
860
+ }) {
861
+ return /* @__PURE__ */ jsxs(
862
+ "div",
863
+ {
864
+ className: cn(
865
+ "inline-flex items-center gap-2 px-1 py-1.5 text-sm font-medium",
866
+ tone === "loss" ? "text-danger" : "text-txt"
867
+ ),
868
+ title,
869
+ children: [
870
+ /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
871
+ /* @__PURE__ */ jsx("span", { children: value })
872
+ ]
873
+ }
874
+ );
875
+ }
876
+ function WalletRailAddress({
877
+ address,
878
+ chains,
879
+ emptyLabel,
880
+ label,
881
+ agentId,
882
+ agentLabel
883
+ }) {
884
+ const [copied, setCopied] = useState(false);
885
+ const handleCopy = useCallback(() => {
886
+ if (!address) return;
887
+ void navigator.clipboard.writeText(address).then(() => {
888
+ setCopied(true);
889
+ window.setTimeout(() => setCopied(false), 1200);
890
+ });
891
+ }, [address]);
892
+ const { ref, agentProps } = useAgentElement({
893
+ id: agentId,
894
+ role: "button",
895
+ label: agentLabel,
896
+ group: "wallet-account",
897
+ status: address ? void 0 : "inactive",
898
+ description: `Copy the ${agentLabel} to the clipboard`
899
+ });
900
+ return /* @__PURE__ */ jsxs(
901
+ "button",
902
+ {
903
+ ref,
904
+ type: "button",
905
+ className: cn(
906
+ "group inline-flex min-w-0 items-center gap-2 px-1 py-1.5 text-left transition-colors",
907
+ address ? "text-txt hover:text-accent" : "text-muted"
908
+ ),
909
+ onClick: handleCopy,
910
+ disabled: !address,
911
+ title: address ?? emptyLabel,
912
+ "aria-label": address ? `Copy ${emptyLabel} address` : `${emptyLabel} unavailable`,
913
+ ...agentProps,
914
+ children: [
915
+ /* @__PURE__ */ jsx("span", { className: "flex shrink-0 -space-x-1.5", children: chains.map((chain) => /* @__PURE__ */ jsx(
916
+ ChainLogoBadge,
917
+ {
918
+ chain,
919
+ size: 18,
920
+ className: "ring-1 ring-bg"
921
+ },
922
+ chain
923
+ )) }),
924
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[0.68rem] font-medium text-muted", children: label }),
925
+ /* @__PURE__ */ jsx(
926
+ "span",
927
+ {
928
+ className: cn(
929
+ "min-w-0 truncate font-mono text-xs font-semibold",
930
+ address ? "max-w-24 text-txt" : "max-w-20 text-muted"
931
+ ),
932
+ children: address ? formatCompactAddress(address) : emptyLabel
933
+ }
934
+ ),
935
+ address ? copied ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 shrink-0 text-accent" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5 shrink-0 text-muted transition-colors group-hover:text-txt" }) : /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0 text-warn" })
936
+ ]
937
+ }
938
+ );
939
+ }
940
+ function WalletConnectionChip({
941
+ label,
942
+ ready
943
+ }) {
944
+ return /* @__PURE__ */ jsxs(
945
+ "span",
946
+ {
947
+ className: "inline-flex items-center gap-1.5 text-[0.68rem] font-medium text-muted",
948
+ title: `${label} ${ready ? "ready" : "needs RPC"}`,
949
+ children: [
950
+ /* @__PURE__ */ jsx(
951
+ "span",
952
+ {
953
+ className: cn(
954
+ "h-1.5 w-1.5 rounded-full",
955
+ ready ? "bg-muted/60" : "bg-warn"
956
+ )
957
+ }
958
+ ),
959
+ label
960
+ ]
961
+ }
962
+ );
963
+ }
964
+ function WalletChainCluster() {
965
+ return /* @__PURE__ */ jsx("span", { className: "flex shrink-0 -space-x-1.5", children: SUPPORTED_WALLET_CHAINS.map((chain) => /* @__PURE__ */ jsx(
966
+ ChainLogoBadge,
967
+ {
968
+ chain,
969
+ size: 18,
970
+ className: "ring-1 ring-bg"
971
+ },
972
+ chain
973
+ )) });
974
+ }
975
+ function WalletAddressCluster({
976
+ addresses
977
+ }) {
978
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap gap-2", children: [
979
+ /* @__PURE__ */ jsx(
980
+ WalletRailAddress,
981
+ {
982
+ address: addresses.evmAddress,
983
+ chains: SUPPORTED_WALLET_CHAINS.filter((chain) => chain !== "solana"),
984
+ emptyLabel: "EVM",
985
+ label: "EVM",
986
+ agentId: "account-copy-evm-address",
987
+ agentLabel: "EVM address"
988
+ }
989
+ ),
990
+ /* @__PURE__ */ jsx(
991
+ WalletRailAddress,
992
+ {
993
+ address: addresses.solanaAddress,
994
+ chains: ["solana"],
995
+ emptyLabel: "SOL",
996
+ label: "SOL",
997
+ agentId: "account-copy-solana-address",
998
+ agentLabel: "Solana address"
999
+ }
1000
+ )
1001
+ ] });
1002
+ }
1003
+ function WalletProviderDots({
1004
+ walletConfig
1005
+ }) {
1006
+ const allReady = Boolean(walletConfig?.evmBalanceReady) && Boolean(walletConfig?.solanaBalanceReady);
1007
+ return /* @__PURE__ */ jsx(
1008
+ "span",
1009
+ {
1010
+ className: cn(
1011
+ "h-2 w-2 rounded-full",
1012
+ allReady ? "bg-muted/60" : "bg-warn"
1013
+ )
1014
+ }
1015
+ );
1016
+ }
1017
+ function WalletRailRpcButton({
1018
+ walletConfig,
1019
+ onOpenSettings
1020
+ }) {
1021
+ const evmProvider = providerLabel(
1022
+ walletConfig?.selectedRpcProviders?.evm,
1023
+ "evm"
1024
+ );
1025
+ const solanaProvider = providerLabel(
1026
+ walletConfig?.selectedRpcProviders?.solana,
1027
+ "solana"
1028
+ );
1029
+ const { ref, agentProps } = useAgentElement({
1030
+ id: "account-rpc-settings",
1031
+ role: "button",
1032
+ label: "RPC settings",
1033
+ group: "wallet-account",
1034
+ description: `Open RPC provider settings (EVM ${evmProvider}, Solana ${solanaProvider})`
1035
+ });
1036
+ return /* @__PURE__ */ jsxs(
1037
+ "button",
1038
+ {
1039
+ ref,
1040
+ type: "button",
1041
+ className: "inline-flex h-9 items-center gap-2 px-2 text-xs font-semibold text-txt transition-colors hover:text-accent",
1042
+ onClick: onOpenSettings,
1043
+ title: `RPC providers: EVM ${evmProvider}, Solana ${solanaProvider}`,
1044
+ "aria-label": "Open RPC settings",
1045
+ ...agentProps,
1046
+ children: [
1047
+ /* @__PURE__ */ jsx(WalletProviderDots, { walletConfig }),
1048
+ "RPC"
1049
+ ]
1050
+ }
1051
+ );
1052
+ }
1053
+ function WalletRailAccount({
1054
+ addresses,
1055
+ portfolioValueUsd,
1056
+ walletConfig,
1057
+ onOpenSettings
1058
+ }) {
1059
+ const evmReady = Boolean(walletConfig?.evmBalanceReady);
1060
+ const solanaReady = Boolean(walletConfig?.solanaBalanceReady);
1061
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1062
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start gap-4", children: [
1063
+ /* @__PURE__ */ jsx("div", { className: "relative flex h-16 w-16 items-center justify-center", children: /* @__PURE__ */ jsx(Wallet, { className: "h-7 w-7 text-accent" }) }),
1064
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 basis-64", children: [
1065
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2", children: [
1066
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-2xl font-semibold leading-none text-txt", children: formatUsd(portfolioValueUsd) }),
1067
+ /* @__PURE__ */ jsx(WalletChainCluster, {})
1068
+ ] }),
1069
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
1070
+ /* @__PURE__ */ jsx(WalletConnectionChip, { label: "EVM", ready: evmReady }),
1071
+ /* @__PURE__ */ jsx(WalletConnectionChip, { label: "SOL", ready: solanaReady })
1072
+ ] })
1073
+ ] }),
1074
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(
1075
+ WalletRailRpcButton,
1076
+ {
1077
+ walletConfig,
1078
+ onOpenSettings
1079
+ }
1080
+ ) })
1081
+ ] }),
1082
+ /* @__PURE__ */ jsx(WalletAddressCluster, { addresses })
1083
+ ] });
1084
+ }
1085
+ function WalletRailTabButton({
1086
+ tab,
1087
+ active,
1088
+ onSelect
1089
+ }) {
1090
+ const { ref, agentProps } = useAgentElement({
1091
+ id: `tab-${tab.id}`,
1092
+ role: "tab",
1093
+ label: tab.label,
1094
+ group: "wallet-tabs",
1095
+ status: active ? "active" : "inactive",
1096
+ description: `Show the ${tab.label} list`
1097
+ });
1098
+ return /* @__PURE__ */ jsxs(
1099
+ "button",
1100
+ {
1101
+ ref,
1102
+ type: "button",
1103
+ className: cn(
1104
+ "inline-flex min-w-0 items-center justify-center gap-1.5 px-2 py-2 text-sm font-semibold transition-colors",
1105
+ active ? "text-txt" : "text-muted hover:text-txt"
1106
+ ),
1107
+ onClick: () => onSelect(tab.id),
1108
+ "aria-label": tab.label,
1109
+ "aria-current": active ? "true" : void 0,
1110
+ title: tab.label,
1111
+ ...agentProps,
1112
+ children: [
1113
+ /* @__PURE__ */ jsx(tab.icon, { className: "h-3.5 w-3.5 shrink-0" }),
1114
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: tab.label }),
1115
+ /* @__PURE__ */ jsx(
1116
+ "span",
1117
+ {
1118
+ className: cn("ml-0.5 px-1 py-0.5 font-mono text-[0.62rem] text-muted"),
1119
+ children: tab.count
1120
+ }
1121
+ )
1122
+ ]
1123
+ }
1124
+ );
1125
+ }
1126
+ function WalletRailEmpty({
1127
+ icon: Icon,
1128
+ title,
1129
+ body
1130
+ }) {
1131
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-[13rem] flex-col items-center justify-center px-5 text-center", children: [
1132
+ /* @__PURE__ */ jsx(Icon, { className: "mb-3 h-5 w-5 text-muted" }),
1133
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-semibold text-txt", children: title }),
1134
+ body ? /* @__PURE__ */ jsx("div", { className: "sr-only", children: body }) : null
1135
+ ] });
1136
+ }
1137
+ function TokenRailRowImpl({
1138
+ row,
1139
+ profile,
1140
+ maxPnl,
1141
+ onHideToken
1142
+ }) {
1143
+ const slug = tokenAgentSlug(row);
1144
+ const { ref: hideRef, agentProps: hideAgentProps } = useAgentElement({
1145
+ id: `token-${slug}-hide`,
1146
+ role: "button",
1147
+ label: `Hide ${row.symbol}`,
1148
+ group: "token-list",
1149
+ description: `Hide the ${row.symbol} token from the list`
1150
+ });
1151
+ return /* @__PURE__ */ jsxs("div", { className: "group flex min-w-0 items-center gap-3 px-2 py-2 transition-colors hover:bg-bg-muted/20", children: [
1152
+ /* @__PURE__ */ jsx(TokenIdentityIcon, { row, size: 46 }),
1153
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1154
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-semibold text-txt", children: row.symbol }),
1155
+ /* @__PURE__ */ jsxs("div", { className: "truncate text-xs-tight text-muted", children: [
1156
+ formatBalance(row.balance),
1157
+ " ",
1158
+ row.symbol
1159
+ ] }),
1160
+ /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(TokenPerformance, { row, profile, maxAbsPnl: maxPnl }) })
1161
+ ] }),
1162
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col items-end gap-2", children: [
1163
+ /* @__PURE__ */ jsx("div", { className: "font-mono text-sm font-semibold text-txt", children: formatUsd(row.valueUsd) }),
1164
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1 opacity-70 transition-opacity group-hover:opacity-100", children: /* @__PURE__ */ jsx(
1165
+ "button",
1166
+ {
1167
+ ref: hideRef,
1168
+ type: "button",
1169
+ className: "flex h-7 w-7 items-center justify-center text-muted transition-colors hover:text-danger",
1170
+ onClick: () => onHideToken(row),
1171
+ "aria-label": `Hide ${row.symbol}`,
1172
+ title: `Hide ${row.symbol}`,
1173
+ ...hideAgentProps,
1174
+ children: /* @__PURE__ */ jsx(EyeOff, { className: "h-3.5 w-3.5" })
1175
+ }
1176
+ ) })
1177
+ ] })
1178
+ ] });
1179
+ }
1180
+ const TokenRailRow = memo(
1181
+ TokenRailRowImpl,
1182
+ (prev, next) => prev.onHideToken === next.onHideToken && prev.maxPnl === next.maxPnl && prev.profile === next.profile && prev.row.chain === next.row.chain && prev.row.symbol === next.row.symbol && prev.row.name === next.row.name && prev.row.contractAddress === next.row.contractAddress && prev.row.logoUrl === next.row.logoUrl && prev.row.balance === next.row.balance && prev.row.valueUsd === next.row.valueUsd && prev.row.balanceRaw === next.row.balanceRaw && prev.row.isNative === next.row.isNative
1183
+ );
1184
+ function RailNftList({ nfts }) {
1185
+ if (nfts.length === 0) {
1186
+ return /* @__PURE__ */ jsx(WalletRailEmpty, { icon: ImageIcon, title: "None" });
1187
+ }
1188
+ return /* @__PURE__ */ jsx("div", { className: "space-y-1", children: nfts.slice(0, 20).map((nft) => /* @__PURE__ */ jsxs(
1189
+ "div",
1190
+ {
1191
+ className: "flex min-w-0 items-center gap-3 px-2 py-2 transition-colors hover:bg-bg-muted/20",
1192
+ children: [
1193
+ nft.imageUrl ? /* @__PURE__ */ jsx(
1194
+ "img",
1195
+ {
1196
+ src: nft.imageUrl,
1197
+ alt: nft.name,
1198
+ className: "h-11 w-11 shrink-0 object-cover",
1199
+ loading: "lazy"
1200
+ }
1201
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-11 w-11 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-4 w-4 text-muted" }) }),
1202
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1203
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-semibold text-txt", children: nft.name }),
1204
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs-tight text-muted", children: nft.collectionName })
1205
+ ] })
1206
+ ]
1207
+ },
1208
+ `${nft.chain}:${nft.collectionName}:${nft.name}:${nft.imageUrl}`
1209
+ )) });
1210
+ }
1211
+ function RailPositionList({
1212
+ positions
1213
+ }) {
1214
+ if (positions.length === 0) {
1215
+ return /* @__PURE__ */ jsx(WalletRailEmpty, { icon: Layers3, title: "None" });
1216
+ }
1217
+ return /* @__PURE__ */ jsx("div", { className: "space-y-1", children: positions.map((position) => /* @__PURE__ */ jsxs(
1218
+ "div",
1219
+ {
1220
+ className: "flex min-w-0 items-center gap-3 px-2 py-2 transition-colors hover:bg-bg-muted/20",
1221
+ children: [
1222
+ position.imageUrl ? /* @__PURE__ */ jsx(
1223
+ "img",
1224
+ {
1225
+ src: position.imageUrl,
1226
+ alt: position.label,
1227
+ className: "h-11 w-11 shrink-0 object-cover",
1228
+ loading: "lazy"
1229
+ }
1230
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-11 w-11 shrink-0 items-center justify-center", children: /* @__PURE__ */ jsx(Layers3, { className: "h-4 w-4 text-muted" }) }),
1231
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1232
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-semibold text-txt", children: position.label }),
1233
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs-tight text-muted", children: position.detail })
1234
+ ] }),
1235
+ position.valueUsd !== null && position.valueUsd > 0 ? /* @__PURE__ */ jsx("div", { className: "shrink-0 font-mono text-sm font-semibold text-txt", children: formatUsd(position.valueUsd) }) : null
1236
+ ]
1237
+ },
1238
+ position.id
1239
+ )) });
1240
+ }
1241
+ function WalletHoldingsSection({
1242
+ rows,
1243
+ nfts,
1244
+ positions,
1245
+ addresses,
1246
+ hiddenTokenIds,
1247
+ walletConfig,
1248
+ profile,
1249
+ onHideToken,
1250
+ onOpenRpcSettings,
1251
+ walletEnabled,
1252
+ onEnableWallet
1253
+ }) {
1254
+ const [activeTab, setActiveTab] = useState("tokens");
1255
+ const visibleRows = useMemo(
1256
+ () => rows.filter((row) => {
1257
+ if (hiddenTokenIds.has(tokenId(row))) return false;
1258
+ return tokenHasInventory(row);
1259
+ }),
1260
+ [hiddenTokenIds, rows]
1261
+ );
1262
+ const totalUsd = useMemo(
1263
+ () => visibleRows.reduce((sum, row) => sum + row.valueUsd, 0),
1264
+ [visibleRows]
1265
+ );
1266
+ const maxPnl = useMemo(
1267
+ () => maxAbsTokenPnl(visibleRows, profile),
1268
+ [visibleRows, profile]
1269
+ );
1270
+ const tabs = [
1271
+ {
1272
+ id: "tokens",
1273
+ label: "Tokens",
1274
+ icon: Wallet,
1275
+ count: visibleRows.length
1276
+ },
1277
+ { id: "defi", label: "DeFi", icon: Layers3, count: positions.length },
1278
+ { id: "nfts", label: "NFTs", icon: ImageIcon, count: nfts.length }
1279
+ ];
1280
+ const { ref: enableWalletRef, agentProps: enableWalletAgentProps } = useAgentElement({
1281
+ id: "action-enable-wallet",
1282
+ role: "button",
1283
+ label: "Enable wallet",
1284
+ group: "wallet-actions",
1285
+ description: "Turn on the wallet to load balances and trading data"
1286
+ });
1287
+ return /* @__PURE__ */ jsxs("section", { "data-testid": "wallets-sidebar", className: "px-3 py-3 md:px-4", children: [
1288
+ /* @__PURE__ */ jsx(
1289
+ WalletRailAccount,
1290
+ {
1291
+ addresses,
1292
+ portfolioValueUsd: totalUsd,
1293
+ walletConfig,
1294
+ onOpenSettings: onOpenRpcSettings
1295
+ }
1296
+ ),
1297
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-4", children: [
1298
+ visibleRows.length > 0 ? /* @__PURE__ */ jsx(AssetAllocationStrip, { rows: visibleRows, compact: true }) : null,
1299
+ walletEnabled === false ? /* @__PURE__ */ jsx(
1300
+ Button,
1301
+ {
1302
+ ref: enableWalletRef,
1303
+ className: "w-full",
1304
+ onClick: onEnableWallet,
1305
+ ...enableWalletAgentProps,
1306
+ children: "Enable wallet"
1307
+ }
1308
+ ) : null,
1309
+ /* @__PURE__ */ jsx("div", { className: "grid min-w-0 grid-cols-3 gap-1", children: tabs.map((tab) => /* @__PURE__ */ jsx(
1310
+ WalletRailTabButton,
1311
+ {
1312
+ tab,
1313
+ active: activeTab === tab.id,
1314
+ onSelect: setActiveTab
1315
+ },
1316
+ tab.id
1317
+ )) }),
1318
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: activeTab === "tokens" ? visibleRows.length === 0 ? /* @__PURE__ */ jsx(WalletRailEmpty, { icon: Wallet, title: "None" }) : visibleRows.map((row) => /* @__PURE__ */ jsx(
1319
+ TokenRailRow,
1320
+ {
1321
+ row,
1322
+ profile,
1323
+ maxPnl,
1324
+ onHideToken
1325
+ },
1326
+ tokenId(row)
1327
+ )) : activeTab === "defi" ? /* @__PURE__ */ jsx(RailPositionList, { positions }) : activeTab === "nfts" ? /* @__PURE__ */ jsx(RailNftList, { nfts }) : null })
1328
+ ] })
1329
+ ] });
1330
+ }
1331
+ function DashboardWindowButton({
1332
+ window: window2,
1333
+ active,
1334
+ onSelect
1335
+ }) {
1336
+ const { ref, agentProps } = useAgentElement({
1337
+ id: `pnl-window-${window2}`,
1338
+ role: "tab",
1339
+ label: `P&L window ${window2}`,
1340
+ group: "pnl-window",
1341
+ status: active ? "active" : "inactive",
1342
+ description: `Show profit and loss over the ${window2} window`
1343
+ });
1344
+ return /* @__PURE__ */ jsx(
1345
+ "button",
1346
+ {
1347
+ ref,
1348
+ type: "button",
1349
+ className: cn(
1350
+ "px-2 py-1.5 text-xs font-medium transition-colors",
1351
+ active ? "text-accent" : "text-muted hover:text-txt"
1352
+ ),
1353
+ onClick: () => onSelect(window2),
1354
+ "aria-current": active ? "true" : void 0,
1355
+ ...agentProps,
1356
+ children: window2
1357
+ }
1358
+ );
1359
+ }
1360
+ function DashboardSection({
1361
+ title,
1362
+ icon: Icon,
1363
+ action,
1364
+ children
1365
+ }) {
1366
+ return /* @__PURE__ */ jsxs("section", { className: "space-y-4", children: [
1367
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
1368
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-semibold text-txt", children: [
1369
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 text-accent" }),
1370
+ title
1371
+ ] }),
1372
+ action
1373
+ ] }),
1374
+ children
1375
+ ] });
1376
+ }
1377
+ function ActivityLog({
1378
+ profile,
1379
+ events
1380
+ }) {
1381
+ const entries = useMemo(
1382
+ () => walletTimelineEntries({ profile, events }),
1383
+ [events, profile]
1384
+ );
1385
+ if (entries.length === 0) {
1386
+ return /* @__PURE__ */ jsx(EmptyState, { icon: Activity, title: "None" });
1387
+ }
1388
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: entries.map((entry) => {
1389
+ const toneClass = entry.tone === "ok" ? "bg-ok/10 text-ok" : entry.tone === "warn" ? "bg-warn/10 text-warn" : entry.tone === "danger" ? "bg-danger/10 text-danger" : "bg-bg/55 text-muted";
1390
+ const body = /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-3 px-2 py-2 text-sm transition-colors hover:bg-bg-muted/20", children: [
1391
+ /* @__PURE__ */ jsx(
1392
+ "span",
1393
+ {
1394
+ className: cn(
1395
+ "flex h-8 w-8 shrink-0 items-center justify-center",
1396
+ toneClass
1397
+ ),
1398
+ children: /* @__PURE__ */ jsx(entry.icon, { className: "h-4 w-4" })
1399
+ }
1400
+ ),
1401
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
1402
+ /* @__PURE__ */ jsx("span", { className: "block truncate font-medium text-txt", children: entry.title }),
1403
+ entry.detail ? /* @__PURE__ */ jsx("span", { className: "block truncate text-xs-tight text-muted", children: entry.detail }) : null
1404
+ ] }),
1405
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[0.68rem] font-medium text-muted", children: formatRelativeTimestamp(entry.timestamp) })
1406
+ ] });
1407
+ if (entry.href) {
1408
+ return /* @__PURE__ */ jsx(
1409
+ "a",
1410
+ {
1411
+ href: entry.href,
1412
+ target: "_blank",
1413
+ rel: "noreferrer",
1414
+ children: body
1415
+ },
1416
+ entry.id
1417
+ );
1418
+ }
1419
+ return /* @__PURE__ */ jsx("div", { children: body }, entry.id);
1420
+ }) });
1421
+ }
1422
+ function NftPreview({ nfts }) {
1423
+ const visible = nfts.slice(0, 6);
1424
+ if (visible.length === 0) {
1425
+ return /* @__PURE__ */ jsx(EmptyState, { icon: ImageIcon, title: "None" });
1426
+ }
1427
+ return /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3 md:grid-cols-3", children: visible.map((nft) => /* @__PURE__ */ jsxs(
1428
+ "div",
1429
+ {
1430
+ className: "overflow-hidden",
1431
+ children: [
1432
+ nft.imageUrl ? /* @__PURE__ */ jsx(
1433
+ "img",
1434
+ {
1435
+ src: nft.imageUrl,
1436
+ alt: nft.name,
1437
+ className: "aspect-square w-full object-cover",
1438
+ loading: "lazy"
1439
+ }
1440
+ ) : /* @__PURE__ */ jsx("div", { className: "flex aspect-square items-center justify-center", children: /* @__PURE__ */ jsx(ImageIcon, { className: "h-5 w-5 text-muted" }) }),
1441
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 p-2", children: [
1442
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs font-medium text-txt", children: nft.name }),
1443
+ /* @__PURE__ */ jsx("div", { className: "truncate text-[0.68rem] text-muted", children: nft.collectionName })
1444
+ ] })
1445
+ ]
1446
+ },
1447
+ `${nft.chain}:${nft.collectionName}:${nft.name}:${nft.imageUrl}`
1448
+ )) });
1449
+ }
1450
+ function LpPositionsPanel({
1451
+ positions
1452
+ }) {
1453
+ if (positions.length === 0) {
1454
+ return /* @__PURE__ */ jsx(EmptyState, { icon: Layers3, title: "None" });
1455
+ }
1456
+ return /* @__PURE__ */ jsx("div", { className: "grid gap-1", children: positions.map((position) => /* @__PURE__ */ jsxs(
1457
+ "div",
1458
+ {
1459
+ className: "flex min-w-0 items-center gap-3 px-2 py-2 transition-colors hover:bg-bg-muted/20",
1460
+ children: [
1461
+ position.imageUrl ? /* @__PURE__ */ jsx(
1462
+ "img",
1463
+ {
1464
+ src: position.imageUrl,
1465
+ alt: position.label,
1466
+ className: "h-10 w-10 shrink-0 object-cover",
1467
+ loading: "lazy"
1468
+ }
1469
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center", children: position.kind === "nft" ? /* @__PURE__ */ jsx(ImageIcon, { className: "h-4 w-4 text-muted" }) : /* @__PURE__ */ jsx(Layers3, { className: "h-4 w-4 text-muted" }) }),
1470
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1471
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-semibold text-txt", children: position.label }),
1472
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs-tight text-muted", children: position.detail })
1473
+ ] }),
1474
+ position.valueUsd !== null && position.valueUsd > 0 ? /* @__PURE__ */ jsx("div", { className: "shrink-0 font-mono text-sm font-semibold text-txt", children: formatUsd(position.valueUsd) }) : null
1475
+ ]
1476
+ },
1477
+ position.id
1478
+ )) });
1479
+ }
1480
+ function InventoryAppView() {
1481
+ const {
1482
+ walletEnabled,
1483
+ walletAddresses,
1484
+ walletConfig,
1485
+ walletBalances,
1486
+ walletNfts,
1487
+ walletLoading,
1488
+ walletNftsLoading,
1489
+ walletError,
1490
+ loadWalletConfig,
1491
+ loadBalances,
1492
+ loadNfts,
1493
+ setState,
1494
+ setTab,
1495
+ setActionNotice
1496
+ } = useAppSelectorShallow((s) => ({
1497
+ walletEnabled: s.walletEnabled,
1498
+ walletAddresses: s.walletAddresses,
1499
+ walletConfig: s.walletConfig,
1500
+ walletBalances: s.walletBalances,
1501
+ walletNfts: s.walletNfts,
1502
+ walletLoading: s.walletLoading,
1503
+ walletNftsLoading: s.walletNftsLoading,
1504
+ walletError: s.walletError,
1505
+ loadWalletConfig: s.loadWalletConfig,
1506
+ loadBalances: s.loadBalances,
1507
+ loadNfts: s.loadNfts,
1508
+ setState: s.setState,
1509
+ setTab: s.setTab,
1510
+ setActionNotice: s.setActionNotice
1511
+ }));
1512
+ const { events: activityEvents } = useActivityEvents();
1513
+ const [hiddenTokenIds, setHiddenTokenIds] = useState(
1514
+ () => readHiddenTokenIds()
1515
+ );
1516
+ const [dashboardWindow, setDashboardWindow] = useState("30d");
1517
+ const [tradingProfile, setTradingProfile] = useState(null);
1518
+ const [tradingProfileLoading, setTradingProfileLoading] = useState(false);
1519
+ const [tradingProfileError, setTradingProfileError] = useState(
1520
+ null
1521
+ );
1522
+ const [marketOverview, setMarketOverview] = useState(null);
1523
+ const [marketOverviewLoading, setMarketOverviewLoading] = useState(false);
1524
+ const initialLoadRef = useRef(false);
1525
+ const tradingProfileRequestRef = useRef(0);
1526
+ const marketOverviewRequestRef = useRef(0);
1527
+ const loadTradingProfile = useCallback(async () => {
1528
+ const requestId = tradingProfileRequestRef.current + 1;
1529
+ tradingProfileRequestRef.current = requestId;
1530
+ setTradingProfileLoading(true);
1531
+ setTradingProfileError(null);
1532
+ try {
1533
+ const profile = await client.getWalletTradingProfile(
1534
+ tradingProfileWindow(dashboardWindow)
1535
+ );
1536
+ if (tradingProfileRequestRef.current === requestId) {
1537
+ setTradingProfile(profile);
1538
+ }
1539
+ } catch (cause) {
1540
+ const message = cause instanceof Error && cause.message.trim().length > 0 ? cause.message.trim() : "Failed to load trading profile.";
1541
+ if (tradingProfileRequestRef.current === requestId) {
1542
+ setTradingProfile(null);
1543
+ setTradingProfileError(message);
1544
+ }
1545
+ } finally {
1546
+ if (tradingProfileRequestRef.current === requestId) {
1547
+ setTradingProfileLoading(false);
1548
+ }
1549
+ }
1550
+ }, [dashboardWindow]);
1551
+ const loadMarketOverview = useCallback(async () => {
1552
+ const requestId = marketOverviewRequestRef.current + 1;
1553
+ marketOverviewRequestRef.current = requestId;
1554
+ setMarketOverviewLoading(true);
1555
+ try {
1556
+ const overview = await client.getWalletMarketOverview();
1557
+ if (marketOverviewRequestRef.current === requestId) {
1558
+ setMarketOverview(overview);
1559
+ }
1560
+ } catch {
1561
+ if (marketOverviewRequestRef.current === requestId) {
1562
+ setMarketOverview(null);
1563
+ }
1564
+ } finally {
1565
+ if (marketOverviewRequestRef.current === requestId) {
1566
+ setMarketOverviewLoading(false);
1567
+ }
1568
+ }
1569
+ }, []);
1570
+ useEffect(() => {
1571
+ if (initialLoadRef.current) return;
1572
+ initialLoadRef.current = true;
1573
+ void loadWalletConfig();
1574
+ void loadMarketOverview();
1575
+ if (walletEnabled === false) return;
1576
+ void loadBalances();
1577
+ void loadNfts();
1578
+ }, [
1579
+ loadBalances,
1580
+ loadMarketOverview,
1581
+ loadNfts,
1582
+ loadWalletConfig,
1583
+ walletEnabled
1584
+ ]);
1585
+ useEffect(() => {
1586
+ void loadTradingProfile();
1587
+ }, [loadTradingProfile]);
1588
+ useEffect(() => {
1589
+ if (walletEnabled === false) return;
1590
+ const interval = window.setInterval(() => {
1591
+ void loadWalletConfig();
1592
+ void loadBalances();
1593
+ void loadNfts();
1594
+ void loadTradingProfile();
1595
+ void loadMarketOverview();
1596
+ }, WALLET_REFRESH_INTERVAL_MS);
1597
+ return () => window.clearInterval(interval);
1598
+ }, [
1599
+ loadBalances,
1600
+ loadMarketOverview,
1601
+ loadNfts,
1602
+ loadTradingProfile,
1603
+ loadWalletConfig,
1604
+ walletEnabled
1605
+ ]);
1606
+ const inventoryData = useInventoryData({
1607
+ walletBalances,
1608
+ walletAddresses,
1609
+ walletConfig,
1610
+ walletNfts,
1611
+ inventorySort: "value",
1612
+ inventorySortDirection: "desc",
1613
+ inventoryChainFilters: ALL_INVENTORY_FILTERS
1614
+ });
1615
+ const addresses = useMemo(
1616
+ () => resolveWalletAddresses({ walletAddresses, walletConfig }),
1617
+ [walletAddresses, walletConfig]
1618
+ );
1619
+ const visibleAssetRows = useMemo(
1620
+ () => inventoryData.tokenRowsAllChains.filter(tokenHasInventory),
1621
+ [inventoryData.tokenRowsAllChains]
1622
+ );
1623
+ const displayedAssetRows = useMemo(
1624
+ () => visibleAssetRows.filter((row) => !hiddenTokenIds.has(tokenId(row))),
1625
+ [hiddenTokenIds, visibleAssetRows]
1626
+ );
1627
+ const lpPositions = useMemo(
1628
+ () => deriveInventoryPositionAssets({
1629
+ tokenRows: displayedAssetRows,
1630
+ nfts: inventoryData.allNfts
1631
+ }),
1632
+ [displayedAssetRows, inventoryData.allNfts]
1633
+ );
1634
+ const pnlValue = parseAmount(tradingProfile?.summary.realizedPnlBnb);
1635
+ const showTradePnl = hasClosedTradePnl(tradingProfile);
1636
+ const hasWalletTimeline = activityEvents.length > 0 || (tradingProfile?.recentSwaps.length ?? 0) > 0;
1637
+ const showMarketPulseHero = walletEnabled === false || !walletLoading && !walletNftsLoading && !tradingProfileLoading && displayedAssetRows.length === 0 && lpPositions.length === 0 && inventoryData.allNfts.length === 0 && !showTradePnl && !hasWalletTimeline;
1638
+ const handleHideToken = useCallback(
1639
+ (row) => {
1640
+ const next = new Set(hiddenTokenIds);
1641
+ next.add(tokenId(row));
1642
+ setHiddenTokenIds(next);
1643
+ writeHiddenTokenIds(next);
1644
+ setActionNotice(`${row.symbol} hidden from this wallet view.`);
1645
+ },
1646
+ [hiddenTokenIds, setActionNotice]
1647
+ );
1648
+ const handleOpenRpcSettings = useCallback(() => {
1649
+ setTab("settings");
1650
+ if (typeof window !== "undefined") {
1651
+ window.location.hash = "wallet-rpc";
1652
+ }
1653
+ }, [setTab]);
1654
+ const handleEnableWallet = useCallback(() => {
1655
+ setState("walletEnabled", true);
1656
+ void loadWalletConfig();
1657
+ void loadBalances();
1658
+ void loadNfts();
1659
+ }, [loadBalances, loadNfts, loadWalletConfig, setState]);
1660
+ return /* @__PURE__ */ jsx(
1661
+ "main",
1662
+ {
1663
+ "data-testid": "wallet-shell",
1664
+ className: "h-full min-h-0 w-full overflow-y-auto bg-bg",
1665
+ children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-4xl flex-col gap-6 px-5 pt-6 pb-28", children: [
1666
+ walletError ? /* @__PURE__ */ jsx("div", { className: "px-1 py-2 text-sm text-danger", children: walletError }) : null,
1667
+ /* @__PURE__ */ jsx(
1668
+ WalletHoldingsSection,
1669
+ {
1670
+ rows: visibleAssetRows,
1671
+ nfts: inventoryData.allNfts,
1672
+ positions: lpPositions,
1673
+ addresses,
1674
+ hiddenTokenIds,
1675
+ walletConfig,
1676
+ profile: tradingProfile,
1677
+ onHideToken: handleHideToken,
1678
+ onOpenRpcSettings: handleOpenRpcSettings,
1679
+ walletEnabled,
1680
+ onEnableWallet: handleEnableWallet
1681
+ }
1682
+ ),
1683
+ showMarketPulseHero ? /* @__PURE__ */ jsx(
1684
+ MarketPulseHero,
1685
+ {
1686
+ overview: marketOverview,
1687
+ loading: marketOverviewLoading,
1688
+ hasKeys: addresses.evmAddress !== null || addresses.solanaAddress !== null,
1689
+ onConfigureKeys: handleOpenRpcSettings
1690
+ }
1691
+ ) : null,
1692
+ !showMarketPulseHero ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8", children: [
1693
+ /* @__PURE__ */ jsxs(
1694
+ DashboardSection,
1695
+ {
1696
+ title: "P&L",
1697
+ icon: BarChart3,
1698
+ action: /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: DASHBOARD_WINDOWS.map((window2) => /* @__PURE__ */ jsx(
1699
+ DashboardWindowButton,
1700
+ {
1701
+ window: window2,
1702
+ active: dashboardWindow === window2,
1703
+ onSelect: setDashboardWindow
1704
+ },
1705
+ window2
1706
+ )) }),
1707
+ children: [
1708
+ showTradePnl && pnlValue !== null || displayedAssetRows.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mb-4 flex flex-wrap items-center gap-3", children: [
1709
+ showTradePnl && pnlValue !== null ? /* @__PURE__ */ jsx(
1710
+ SummaryChip,
1711
+ {
1712
+ icon: pnlValue >= 0 ? TrendingUp : TrendingDown,
1713
+ value: `${pnlValue > 0 ? "+" : ""}${formatBnb(tradingProfile?.summary.realizedPnlBnb)}`,
1714
+ tone: pnlValue >= 0 ? "gain" : "loss",
1715
+ title: "Realized P&L"
1716
+ }
1717
+ ) : null,
1718
+ displayedAssetRows.length > 0 ? /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx(AssetAllocationStrip, { rows: displayedAssetRows, compact: true }) }) : null
1719
+ ] }) : null,
1720
+ /* @__PURE__ */ jsx(PnlChart, { profile: tradingProfile }),
1721
+ tradingProfileError ? /* @__PURE__ */ jsx("div", { className: "mt-3 text-xs-tight text-danger", children: tradingProfileError }) : null
1722
+ ]
1723
+ }
1724
+ ),
1725
+ /* @__PURE__ */ jsx(DashboardSection, { title: "Activity", icon: Activity, children: /* @__PURE__ */ jsx(ActivityLog, { profile: tradingProfile, events: activityEvents }) }),
1726
+ /* @__PURE__ */ jsx(DashboardSection, { title: "Movers", icon: TrendingUp, children: /* @__PURE__ */ jsx(
1727
+ PortfolioMoversPanel,
1728
+ {
1729
+ rows: displayedAssetRows,
1730
+ profile: tradingProfile,
1731
+ marketOverview
1732
+ }
1733
+ ) }),
1734
+ /* @__PURE__ */ jsx(DashboardSection, { title: "LP positions", icon: Layers3, children: /* @__PURE__ */ jsx(LpPositionsPanel, { positions: lpPositions }) }),
1735
+ /* @__PURE__ */ jsx(DashboardSection, { title: "NFTs", icon: ImageIcon, children: /* @__PURE__ */ jsx(NftPreview, { nfts: inventoryData.allNfts }) })
1736
+ ] }) : null
1737
+ ] })
1738
+ }
1739
+ );
1740
+ }
1741
+ export {
1742
+ InventoryAppView
1743
+ };
1744
+ //# sourceMappingURL=InventoryAppView.js.map