@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.
- package/dist/InventoryView.d.ts +19 -0
- package/dist/InventoryView.d.ts.map +1 -0
- package/dist/InventoryView.helpers.d.ts +32 -0
- package/dist/InventoryView.helpers.d.ts.map +1 -0
- package/dist/InventoryView.helpers.js +104 -0
- package/dist/InventoryView.helpers.js.map +1 -0
- package/dist/InventoryView.interact.d.ts +2 -0
- package/dist/InventoryView.interact.d.ts.map +1 -0
- package/dist/InventoryView.interact.js +47 -0
- package/dist/InventoryView.interact.js.map +1 -0
- package/dist/InventoryView.js +242 -0
- package/dist/InventoryView.js.map +1 -0
- package/dist/components/InventoryAppView.d.ts +2 -0
- package/dist/components/InventoryAppView.d.ts.map +1 -0
- package/dist/components/InventoryAppView.js +1744 -0
- package/dist/components/InventoryAppView.js.map +1 -0
- package/dist/components/InventorySpatialView.d.ts +86 -0
- package/dist/components/InventorySpatialView.d.ts.map +1 -0
- package/dist/components/InventorySpatialView.js +218 -0
- package/dist/components/InventorySpatialView.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/inventory/ChainIcon.d.ts +9 -0
- package/dist/inventory/ChainIcon.d.ts.map +1 -0
- package/dist/inventory/ChainIcon.js +71 -0
- package/dist/inventory/ChainIcon.js.map +1 -0
- package/dist/inventory/TokenLogo.d.ts +8 -0
- package/dist/inventory/TokenLogo.d.ts.map +1 -0
- package/dist/inventory/TokenLogo.js +52 -0
- package/dist/inventory/TokenLogo.js.map +1 -0
- package/dist/inventory/chainConfig.d.ts +89 -0
- package/dist/inventory/chainConfig.d.ts.map +1 -0
- package/dist/inventory/chainConfig.js +252 -0
- package/dist/inventory/chainConfig.js.map +1 -0
- package/dist/inventory/constants.d.ts +31 -0
- package/dist/inventory/constants.d.ts.map +1 -0
- package/dist/inventory/constants.js +59 -0
- package/dist/inventory/constants.js.map +1 -0
- package/dist/inventory/index.d.ts +5 -0
- package/dist/inventory/index.d.ts.map +1 -0
- package/dist/inventory/index.js +43 -0
- package/dist/inventory/index.js.map +1 -0
- package/dist/inventory/inventory-chain-filters.d.ts +11 -0
- package/dist/inventory/inventory-chain-filters.d.ts.map +1 -0
- package/dist/inventory/inventory-chain-filters.js +49 -0
- package/dist/inventory/inventory-chain-filters.js.map +1 -0
- package/dist/inventory/media-url.d.ts +6 -0
- package/dist/inventory/media-url.d.ts.map +1 -0
- package/dist/inventory/media-url.js +32 -0
- package/dist/inventory/media-url.js.map +1 -0
- package/dist/inventory/useInventoryData.d.ts +38 -0
- package/dist/inventory/useInventoryData.d.ts.map +1 -0
- package/dist/inventory/useInventoryData.js +311 -0
- package/dist/inventory/useInventoryData.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +55 -0
- package/dist/plugin.js.map +1 -0
- package/dist/register-routes.d.ts +9 -0
- package/dist/register-routes.d.ts.map +1 -0
- package/dist/register-routes.js +22 -0
- package/dist/register-routes.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +33 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +4 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +20 -0
- package/dist/register.js.map +1 -0
- package/dist/ui.d.ts +13 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +62 -0
- package/dist/ui.js.map +1 -0
- package/dist/views/bundle.js +1072 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/wallet-rpc.d.ts +2 -0
- package/dist/wallet-rpc.d.ts.map +1 -0
- package/dist/wallet-rpc.js +9 -0
- package/dist/wallet-rpc.js.map +1 -0
- package/dist/wallet-view-bundle.d.ts +3 -0
- package/dist/wallet-view-bundle.d.ts.map +1 -0
- package/dist/wallet-view-bundle.js +7 -0
- package/dist/wallet-view-bundle.js.map +1 -0
- package/dist/widgets/wallet-status.d.ts +3 -0
- package/dist/widgets/wallet-status.d.ts.map +1 -0
- package/dist/widgets/wallet-status.helpers.d.ts +3 -0
- package/dist/widgets/wallet-status.helpers.d.ts.map +1 -0
- package/dist/widgets/wallet-status.helpers.js +12 -0
- package/dist/widgets/wallet-status.helpers.js.map +1 -0
- package/dist/widgets/wallet-status.js +291 -0
- package/dist/widgets/wallet-status.js.map +1 -0
- 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
|