@avail-project/widgets 0.0.1

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 (143) hide show
  1. package/README.md +34 -0
  2. package/dist/common/components/ErrorBoundary.d.ts +44 -0
  3. package/dist/common/components/ErrorBoundary.d.ts.map +1 -0
  4. package/dist/common/components/ErrorBoundary.js +65 -0
  5. package/dist/common/hooks/useDebouncedCallback.d.ts +13 -0
  6. package/dist/common/hooks/useDebouncedCallback.d.ts.map +1 -0
  7. package/dist/common/hooks/useDebouncedCallback.js +42 -0
  8. package/dist/common/hooks/useDebouncedValue.d.ts +5 -0
  9. package/dist/common/hooks/useDebouncedValue.d.ts.map +1 -0
  10. package/dist/common/hooks/useDebouncedValue.js +15 -0
  11. package/dist/common/hooks/useInterval.d.ts +11 -0
  12. package/dist/common/hooks/useInterval.d.ts.map +1 -0
  13. package/dist/common/hooks/useInterval.js +25 -0
  14. package/dist/common/hooks/useLatest.d.ts +12 -0
  15. package/dist/common/hooks/useLatest.d.ts.map +1 -0
  16. package/dist/common/hooks/useLatest.js +19 -0
  17. package/dist/common/hooks/useNexusError.d.ts +20 -0
  18. package/dist/common/hooks/useNexusError.d.ts.map +1 -0
  19. package/dist/common/hooks/useNexusError.js +120 -0
  20. package/dist/common/hooks/usePolling.d.ts +7 -0
  21. package/dist/common/hooks/usePolling.d.ts.map +1 -0
  22. package/dist/common/hooks/usePolling.js +29 -0
  23. package/dist/common/hooks/useStableCallback.d.ts +6 -0
  24. package/dist/common/hooks/useStableCallback.d.ts.map +1 -0
  25. package/dist/common/hooks/useStableCallback.js +13 -0
  26. package/dist/common/hooks/useStopwatch.d.ts +17 -0
  27. package/dist/common/hooks/useStopwatch.d.ts.map +1 -0
  28. package/dist/common/hooks/useStopwatch.js +44 -0
  29. package/dist/common/hooks/useTransactionExecution.d.ts +62 -0
  30. package/dist/common/hooks/useTransactionExecution.d.ts.map +1 -0
  31. package/dist/common/hooks/useTransactionExecution.js +273 -0
  32. package/dist/common/hooks/useTransactionFlow.d.ts +71 -0
  33. package/dist/common/hooks/useTransactionFlow.d.ts.map +1 -0
  34. package/dist/common/hooks/useTransactionFlow.js +435 -0
  35. package/dist/common/index.d.ts +17 -0
  36. package/dist/common/index.d.ts.map +1 -0
  37. package/dist/common/index.js +16 -0
  38. package/dist/common/tx/steps.d.ts +19 -0
  39. package/dist/common/tx/steps.d.ts.map +1 -0
  40. package/dist/common/tx/steps.js +89 -0
  41. package/dist/common/tx/types.d.ts +11 -0
  42. package/dist/common/tx/types.d.ts.map +1 -0
  43. package/dist/common/tx/types.js +19 -0
  44. package/dist/common/tx/useTransactionSteps.d.ts +21 -0
  45. package/dist/common/tx/useTransactionSteps.d.ts.map +1 -0
  46. package/dist/common/tx/useTransactionSteps.js +40 -0
  47. package/dist/common/types/transaction-flow.d.ts +48 -0
  48. package/dist/common/types/transaction-flow.d.ts.map +1 -0
  49. package/dist/common/types/transaction-flow.js +2 -0
  50. package/dist/common/utils/constant.d.ts +99 -0
  51. package/dist/common/utils/constant.d.ts.map +1 -0
  52. package/dist/common/utils/constant.js +370 -0
  53. package/dist/common/utils/token-pricing.d.ts +33 -0
  54. package/dist/common/utils/token-pricing.d.ts.map +1 -0
  55. package/dist/common/utils/token-pricing.js +255 -0
  56. package/dist/common/utils/transaction-flow.d.ts +35 -0
  57. package/dist/common/utils/transaction-flow.d.ts.map +1 -0
  58. package/dist/common/utils/transaction-flow.js +65 -0
  59. package/dist/index.d.ts +5 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +2 -0
  62. package/dist/lib/utils.d.ts +4 -0
  63. package/dist/lib/utils.d.ts.map +1 -0
  64. package/dist/lib/utils.js +8 -0
  65. package/dist/nexus/NexusProvider.d.ts +50 -0
  66. package/dist/nexus/NexusProvider.d.ts.map +1 -0
  67. package/dist/nexus/NexusProvider.js +572 -0
  68. package/dist/nexus-widget/components/address-identicon.d.ts +5 -0
  69. package/dist/nexus-widget/components/address-identicon.d.ts.map +1 -0
  70. package/dist/nexus-widget/components/address-identicon.js +39 -0
  71. package/dist/nexus-widget/components/amount-input-unified.d.ts +18 -0
  72. package/dist/nexus-widget/components/amount-input-unified.d.ts.map +1 -0
  73. package/dist/nexus-widget/components/amount-input-unified.js +88 -0
  74. package/dist/nexus-widget/components/deposit-idle-form.d.ts +29 -0
  75. package/dist/nexus-widget/components/deposit-idle-form.d.ts.map +1 -0
  76. package/dist/nexus-widget/components/deposit-idle-form.js +506 -0
  77. package/dist/nexus-widget/components/nexus-widget-progress-screen.d.ts +36 -0
  78. package/dist/nexus-widget/components/nexus-widget-progress-screen.d.ts.map +1 -0
  79. package/dist/nexus-widget/components/nexus-widget-progress-screen.js +737 -0
  80. package/dist/nexus-widget/components/pay-using-selector.d.ts +9 -0
  81. package/dist/nexus-widget/components/pay-using-selector.d.ts.map +1 -0
  82. package/dist/nexus-widget/components/pay-using-selector.js +37 -0
  83. package/dist/nexus-widget/components/pay-with-sources.d.ts +11 -0
  84. package/dist/nexus-widget/components/pay-with-sources.d.ts.map +1 -0
  85. package/dist/nexus-widget/components/pay-with-sources.js +266 -0
  86. package/dist/nexus-widget/components/receive-asset-selector.d.ts +28 -0
  87. package/dist/nexus-widget/components/receive-asset-selector.d.ts.map +1 -0
  88. package/dist/nexus-widget/components/receive-asset-selector.js +1178 -0
  89. package/dist/nexus-widget/components/recipient-input.d.ts +10 -0
  90. package/dist/nexus-widget/components/recipient-input.d.ts.map +1 -0
  91. package/dist/nexus-widget/components/recipient-input.js +44 -0
  92. package/dist/nexus-widget/components/send-idle-form.d.ts +29 -0
  93. package/dist/nexus-widget/components/send-idle-form.d.ts.map +1 -0
  94. package/dist/nexus-widget/components/send-idle-form.js +548 -0
  95. package/dist/nexus-widget/components/status-alerts.d.ts +7 -0
  96. package/dist/nexus-widget/components/status-alerts.d.ts.map +1 -0
  97. package/dist/nexus-widget/components/status-alerts.js +17 -0
  98. package/dist/nexus-widget/components/swap-asset-selector.d.ts +74 -0
  99. package/dist/nexus-widget/components/swap-asset-selector.d.ts.map +1 -0
  100. package/dist/nexus-widget/components/swap-asset-selector.js +1910 -0
  101. package/dist/nexus-widget/components/swap-idle-form.d.ts +29 -0
  102. package/dist/nexus-widget/components/swap-idle-form.d.ts.map +1 -0
  103. package/dist/nexus-widget/components/swap-idle-form.js +1289 -0
  104. package/dist/nexus-widget/components/swap-intent-preview.d.ts +95 -0
  105. package/dist/nexus-widget/components/swap-intent-preview.d.ts.map +1 -0
  106. package/dist/nexus-widget/components/swap-intent-preview.js +1050 -0
  107. package/dist/nexus-widget/nexus-widget.d.ts +4 -0
  108. package/dist/nexus-widget/nexus-widget.d.ts.map +1 -0
  109. package/dist/nexus-widget/nexus-widget.js +7819 -0
  110. package/dist/nexus-widget/sdk-types.d.ts +13 -0
  111. package/dist/nexus-widget/sdk-types.d.ts.map +1 -0
  112. package/dist/nexus-widget/sdk-types.js +1 -0
  113. package/dist/nexus-widget/theme.d.ts +156 -0
  114. package/dist/nexus-widget/theme.d.ts.map +1 -0
  115. package/dist/nexus-widget/theme.js +159 -0
  116. package/dist/nexus-widget/types.d.ts +141 -0
  117. package/dist/nexus-widget/types.d.ts.map +1 -0
  118. package/dist/nexus-widget/types.js +1 -0
  119. package/dist/nexus-widget/utils/citrea-tokens.d.ts +82 -0
  120. package/dist/nexus-widget/utils/citrea-tokens.d.ts.map +1 -0
  121. package/dist/nexus-widget/utils/citrea-tokens.js +136 -0
  122. package/dist/nexus-widget/utils/deposit-source-selection.d.ts +55 -0
  123. package/dist/nexus-widget/utils/deposit-source-selection.d.ts.map +1 -0
  124. package/dist/nexus-widget/utils/deposit-source-selection.js +219 -0
  125. package/dist/swaps/components/stacked-token-icons.d.ts +15 -0
  126. package/dist/swaps/components/stacked-token-icons.d.ts.map +1 -0
  127. package/dist/swaps/components/stacked-token-icons.js +38 -0
  128. package/dist/swaps/components/step-flow.d.ts +32 -0
  129. package/dist/swaps/components/step-flow.d.ts.map +1 -0
  130. package/dist/swaps/components/step-flow.js +54 -0
  131. package/dist/swaps/components/token-icon.d.ts +11 -0
  132. package/dist/swaps/components/token-icon.d.ts.map +1 -0
  133. package/dist/swaps/components/token-icon.js +16 -0
  134. package/dist/swaps/components/transaction-progress.d.ts +43 -0
  135. package/dist/swaps/components/transaction-progress.d.ts.map +1 -0
  136. package/dist/swaps/components/transaction-progress.js +94 -0
  137. package/dist/ui/button.d.ts +11 -0
  138. package/dist/ui/button.d.ts.map +1 -0
  139. package/dist/ui/button.js +34 -0
  140. package/dist/ui/dialog.d.ts +17 -0
  141. package/dist/ui/dialog.d.ts.map +1 -0
  142. package/dist/ui/dialog.js +47 -0
  143. package/package.json +60 -0
@@ -0,0 +1,1178 @@
1
+ // biome-ignore-all lint: NexusWidget registry component from shadcn registry.
2
+ "use client";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ import { formatTokenBalance } from "@avail-project/nexus-core/utils";
5
+ import { Check, ChevronDown, Copy, Globe, Info, Search, X } from "lucide-react";
6
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
7
+ import { createPortal } from "react-dom";
8
+ import { CHAIN_METADATA, getSdkSwapSupportedChainIds, getShortChainName, isSwapSupportedBySdkChainList, } from "../../common/utils/constant";
9
+ import { useNexus } from "../../nexus/NexusProvider";
10
+ import { nexusWidgetTheme } from "../theme";
11
+ import { CITREA_CHAIN_ID, CITREA_STABLE_SYMBOLS, getCitreaChainMeta, getCitreaReceiveTokenOptions, } from "../utils/citrea-tokens";
12
+ import { getTokenSearchRank, RadioDot, SWAP_CHAIN_DISPLAY_ORDER, sortChainIdsBySwapDisplayOrder, } from "./swap-asset-selector";
13
+ const SUPPORTED_RECEIVE_CHAIN_IDS = new Set(SWAP_CHAIN_DISPLAY_ORDER);
14
+ const CHAIN_SELECTOR_CLOSE_MS = 220;
15
+ const MODAL_HEIGHT_TRANSITION_MS = 260;
16
+ const modalHeightTransitionStyle = {
17
+ interpolateSize: "allow-keywords",
18
+ };
19
+ const modalHeightTransition = `height ${MODAL_HEIGHT_TRANSITION_MS}ms ease, max-height ${MODAL_HEIGHT_TRANSITION_MS}ms ease`;
20
+ const AVATAR_COLORS = [
21
+ "#FF6B6B",
22
+ "#4ECDC4",
23
+ "#45B7D1",
24
+ "#96CEB4",
25
+ "#D4A5A5",
26
+ "#9B59B6",
27
+ "#3498DB",
28
+ "#E67E22",
29
+ "#1ABC9C",
30
+ "#F39C12",
31
+ "#34495E",
32
+ ];
33
+ const getAvatarColor = (str) => {
34
+ let hash = 0;
35
+ for (let i = 0; i < str.length; i++) {
36
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
37
+ }
38
+ return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
39
+ };
40
+ const TokenLogo = ({ token, size = 40, fontSize = 16, }) => {
41
+ const [error, setError] = useState(false);
42
+ if (!token.logo || error) {
43
+ return (_jsx("div", { style: {
44
+ position: "absolute",
45
+ inset: 0,
46
+ borderRadius: "999px",
47
+ display: "flex",
48
+ alignItems: "center",
49
+ justifyContent: "center",
50
+ backgroundColor: getAvatarColor(token.symbol),
51
+ color: "#fff",
52
+ fontWeight: 600,
53
+ fontSize,
54
+ }, children: token.symbol.charAt(0).toUpperCase() }));
55
+ }
56
+ return (_jsx("img", { alt: token.symbol, onError: () => setError(true), src: token.logo, style: {
57
+ position: "absolute",
58
+ inset: 0,
59
+ width: size,
60
+ height: size,
61
+ borderRadius: "999px",
62
+ objectFit: "cover",
63
+ } }));
64
+ };
65
+ const parseFiatValue = (value) => {
66
+ const parsed = Number(String(value ?? "0").replace(/[^0-9.-]/g, ""));
67
+ return Number.isFinite(parsed) ? parsed : 0;
68
+ };
69
+ const STABLE_SYMBOLS = new Set([
70
+ "USDC",
71
+ "USDT",
72
+ "DAI",
73
+ "FRAX",
74
+ "LUSD",
75
+ "TUSD",
76
+ "USDD",
77
+ "GHO",
78
+ "crvUSD",
79
+ "sUSD",
80
+ "USDe",
81
+ ...CITREA_STABLE_SYMBOLS,
82
+ ]);
83
+ const FILTER_TABS = [
84
+ { label: "All", key: "all" },
85
+ { label: "Native", key: "native" },
86
+ { label: "Stables", key: "stables" },
87
+ ];
88
+ const getTokenBalanceKey = (chainId, address) => {
89
+ if (!chainId || !address)
90
+ return null;
91
+ return `${chainId}-${address.toLowerCase()}`;
92
+ };
93
+ const getNativeAddressAlias = (address) => {
94
+ if (!address)
95
+ return null;
96
+ const lower = address.toLowerCase();
97
+ if (lower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") {
98
+ return "0x0000000000000000000000000000000000000000";
99
+ }
100
+ if (lower === "0x0000000000000000000000000000000000000000") {
101
+ return "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
102
+ }
103
+ return null;
104
+ };
105
+ const EMPTY_RECEIVE_TOKENS_DATA = {
106
+ stableSymbols: [],
107
+ tokens: {},
108
+ };
109
+ const LEGACY_RECEIVE_TOKEN_STORAGE_KEYS = [
110
+ "nexus_receive_tokens_cache",
111
+ "nexus_receive_tokens_time",
112
+ "nexus_receive_tokens_cache_v1",
113
+ "nexus_receive_tokens_time_v1",
114
+ "nexus_receive_tokens_cache_v2",
115
+ "nexus_receive_tokens_time_v2",
116
+ ];
117
+ const LEGACY_RECEIVE_TOKEN_STORAGE_PREFIX = "nexus_receive_tokens_";
118
+ let rawTokensCache = null;
119
+ let rawTokensPromise = null;
120
+ let legacyReceiveTokenStorageCleared = false;
121
+ const clearLegacyReceiveTokenStorageCache = () => {
122
+ if (legacyReceiveTokenStorageCleared || typeof window === "undefined") {
123
+ return;
124
+ }
125
+ legacyReceiveTokenStorageCleared = true;
126
+ try {
127
+ const matchingKeys = [];
128
+ for (let index = 0; index < window.localStorage.length; index++) {
129
+ const key = window.localStorage.key(index);
130
+ if (key?.startsWith(LEGACY_RECEIVE_TOKEN_STORAGE_PREFIX)) {
131
+ matchingKeys.push(key);
132
+ }
133
+ }
134
+ for (const key of LEGACY_RECEIVE_TOKEN_STORAGE_KEYS) {
135
+ window.localStorage.removeItem(key);
136
+ }
137
+ for (const key of matchingKeys) {
138
+ window.localStorage.removeItem(key);
139
+ }
140
+ }
141
+ catch {
142
+ // localStorage can be unavailable; the in-memory token cache still works.
143
+ }
144
+ };
145
+ const normalizeReceiveTokenAddress = (address) => {
146
+ if (!address)
147
+ return "";
148
+ const lower = address.toLowerCase();
149
+ if (lower === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" ||
150
+ lower === "0x0000000000000000000000000000000000000000") {
151
+ return "0x0000000000000000000000000000000000000000";
152
+ }
153
+ return lower;
154
+ };
155
+ export const getCachedReceiveTokenMatch = (token) => {
156
+ if (!token?.chainId || !rawTokensCache)
157
+ return null;
158
+ const chainTokens = rawTokensCache.tokens[String(token.chainId)] ?? [];
159
+ const tokenAddress = normalizeReceiveTokenAddress(token.contractAddress);
160
+ const addressMatch = chainTokens.find((candidate) => normalizeReceiveTokenAddress(candidate.address) === tokenAddress);
161
+ const symbolMatches = chainTokens.filter((candidate) => candidate.symbol?.toUpperCase() === token.symbol.toUpperCase());
162
+ const matchedToken = addressMatch ?? (symbolMatches.length === 1 ? symbolMatches[0] : undefined);
163
+ if (!matchedToken)
164
+ return null;
165
+ return {
166
+ ...token,
167
+ decimals: matchedToken.decimals ?? token.decimals,
168
+ logo: matchedToken.logoURI || token.logo,
169
+ name: matchedToken.name || token.name,
170
+ priceUSD: matchedToken.priceUSD ?? token.priceUSD,
171
+ symbol: matchedToken.symbol || token.symbol,
172
+ };
173
+ };
174
+ export const preloadReceiveTokens = () => {
175
+ console.log("[preloadReceiveTokens] Function invoked. Current state: hasCache =", !!rawTokensCache, "hasPromise =", !!rawTokensPromise);
176
+ if (typeof window === "undefined") {
177
+ console.log("[preloadReceiveTokens] Aborted preload: window is undefined (SSR).");
178
+ return null;
179
+ }
180
+ clearLegacyReceiveTokenStorageCache();
181
+ if (rawTokensCache) {
182
+ console.log("[preloadReceiveTokens] Using in-memory token cache.");
183
+ return Promise.resolve(rawTokensCache);
184
+ }
185
+ if (!rawTokensPromise) {
186
+ console.log("[preloadReceiveTokens] No active promise found. Creating a new promise to load tokens...");
187
+ rawTokensPromise = (async () => {
188
+ let data = EMPTY_RECEIVE_TOKENS_DATA;
189
+ try {
190
+ console.log("[preloadReceiveTokens] Initiating network request to li.quest...");
191
+ const [resAll, resStables] = await Promise.all([
192
+ fetch("https://li.quest/v1/tokens"),
193
+ fetch("https://li.quest/v1/tokens?tags=stablecoin"),
194
+ ]);
195
+ console.log("[preloadReceiveTokens] li.quest APIs responded. Status resAll =", resAll.status, "status resStables =", resStables.status);
196
+ let allTokens = {};
197
+ if (resAll.ok) {
198
+ try {
199
+ const allData = await resAll.json();
200
+ allTokens = allData.tokens || {};
201
+ console.log("[preloadReceiveTokens] Successfully parsed all tokens. Count of chains =", Object.keys(allTokens).length);
202
+ }
203
+ catch (jsonErr) {
204
+ console.error("[preloadReceiveTokens] Failed to parse all tokens JSON response:", jsonErr);
205
+ }
206
+ }
207
+ else {
208
+ console.warn("[preloadReceiveTokens] resAll response was not ok:", resAll.status, resAll.statusText);
209
+ }
210
+ const stableSymbols = new Set();
211
+ if (resStables.ok) {
212
+ try {
213
+ const stablesData = await resStables.json();
214
+ const stableChains = stablesData.tokens || {};
215
+ for (const chainId of Object.keys(stableChains)) {
216
+ for (const t of stableChains[chainId]) {
217
+ stableSymbols.add(t.symbol);
218
+ }
219
+ }
220
+ console.log("[preloadReceiveTokens] Successfully parsed stable tokens. Stable symbols count =", stableSymbols.size);
221
+ }
222
+ catch (jsonErr) {
223
+ console.error("[preloadReceiveTokens] Failed to parse stable tokens JSON response:", jsonErr);
224
+ }
225
+ }
226
+ else {
227
+ console.warn("[preloadReceiveTokens] resStables response was not ok:", resStables.status, resStables.statusText);
228
+ }
229
+ data = {
230
+ tokens: allTokens,
231
+ stableSymbols: Array.from(stableSymbols),
232
+ };
233
+ console.log("[preloadReceiveTokens] Finished composing network token data.", {
234
+ chainsCount: Object.keys(data.tokens).length,
235
+ stablesCount: data.stableSymbols.length,
236
+ });
237
+ }
238
+ catch (err) {
239
+ console.error("[preloadReceiveTokens] Failed to fetch/parse tokens from li.quest:", err);
240
+ }
241
+ if (Object.keys(data.tokens).length > 0) {
242
+ rawTokensCache = data;
243
+ console.log("[preloadReceiveTokens] Cached non-empty token data in memory.");
244
+ }
245
+ else {
246
+ console.warn("[preloadReceiveTokens] Token data is empty (likely due to fetch failure). Resetting rawTokensPromise to allow retry.");
247
+ rawTokensPromise = null;
248
+ }
249
+ return data;
250
+ })();
251
+ }
252
+ else {
253
+ console.log("[preloadReceiveTokens] Using existing promise (single-flight / in-flight request).");
254
+ }
255
+ return rawTokensPromise;
256
+ };
257
+ // Start preloading immediately in the background
258
+ if (typeof window !== "undefined") {
259
+ setTimeout(() => {
260
+ console.log("[preloadReceiveTokens] Calling preloadReceiveTokens from initial background timeout (1s)");
261
+ preloadReceiveTokens();
262
+ }, 1000);
263
+ }
264
+ export function ReceiveAssetSelector({ allowedChainIds, allowedPairs, additionalTokens, onSelect, onBack, }) {
265
+ const selectorRef = useRef(null);
266
+ const listRef = useRef(null);
267
+ const stableListHeightRef = useRef(0);
268
+ const chainCloseTimerRef = useRef(null);
269
+ const [portalRoot, setPortalRoot] = useState(null);
270
+ const [stableListHeight, setStableListHeight] = useState(null);
271
+ const { supportedChainsAndTokens, swapBalance, swapSupportedChainsAndTokens, } = useNexus();
272
+ const sdkSwapSupportedChainIds = useMemo(() => getSdkSwapSupportedChainIds(swapSupportedChainsAndTokens), [swapSupportedChainsAndTokens]);
273
+ const [query, setQuery] = useState("");
274
+ const [activeTab, setActiveTab] = useState("all");
275
+ const [selectedChainFilter, setSelectedChainFilter] = useState(null);
276
+ const [isSearchFocused, setIsSearchFocused] = useState(false);
277
+ const [showChainSelector, setShowChainSelector] = useState(false);
278
+ const [isChainSelectorClosing, setIsChainSelectorClosing] = useState(false);
279
+ const [chainQuery, setChainQuery] = useState("");
280
+ const [isChainSearchFocused, setIsChainSearchFocused] = useState(false);
281
+ const [selectedTokenHash, setSelectedTokenHash] = useState(null);
282
+ const [selectedTokenFull, setSelectedTokenFull] = useState(null);
283
+ const [hoveredHash, setHoveredHash] = useState(null);
284
+ const [copiedHash, setCopiedHash] = useState(null);
285
+ const [visibleCount, setVisibleCount] = useState(30);
286
+ const [tooltipState, setTooltipState] = useState(null);
287
+ const [apiTokens, setApiTokens] = useState([]);
288
+ const [isLoading, setIsLoading] = useState(true);
289
+ const [dynamicStableSymbols, setDynamicStableSymbols] = useState(STABLE_SYMBOLS);
290
+ const hasConfiguredTokenList = Boolean(allowedPairs?.length);
291
+ const showFilterTabs = !hasConfiguredTokenList;
292
+ useEffect(() => {
293
+ setPortalRoot(selectorRef.current?.closest("[data-nexus-widget-root]"));
294
+ }, []);
295
+ useEffect(() => {
296
+ return () => {
297
+ if (chainCloseTimerRef.current) {
298
+ clearTimeout(chainCloseTimerRef.current);
299
+ }
300
+ };
301
+ }, []);
302
+ const openChainSelector = () => {
303
+ if (chainCloseTimerRef.current) {
304
+ clearTimeout(chainCloseTimerRef.current);
305
+ chainCloseTimerRef.current = null;
306
+ }
307
+ setChainQuery("");
308
+ setIsChainSelectorClosing(false);
309
+ setShowChainSelector(true);
310
+ };
311
+ const closeChainSelector = () => {
312
+ if (chainCloseTimerRef.current) {
313
+ clearTimeout(chainCloseTimerRef.current);
314
+ }
315
+ setIsChainSelectorClosing(true);
316
+ chainCloseTimerRef.current = setTimeout(() => {
317
+ setShowChainSelector(false);
318
+ setIsChainSelectorClosing(false);
319
+ chainCloseTimerRef.current = null;
320
+ }, CHAIN_SELECTOR_CLOSE_MS);
321
+ };
322
+ const balanceMap = useMemo(() => {
323
+ const map = new Map();
324
+ for (const asset of swapBalance ?? []) {
325
+ for (const bd of asset.breakdown ?? []) {
326
+ if (!isSwapSupportedBySdkChainList(bd.chain?.id, swapSupportedChainsAndTokens)) {
327
+ continue;
328
+ }
329
+ const key = getTokenBalanceKey(bd.chain?.id, bd.contractAddress);
330
+ if (!key)
331
+ continue;
332
+ const fiatBalance = parseFiatValue(bd.balanceInFiat);
333
+ if (fiatBalance < 1)
334
+ continue;
335
+ const symbol = bd.symbol ?? asset.symbol;
336
+ const decimals = bd.decimals ?? asset.decimals ?? 18;
337
+ map.set(key, {
338
+ balance: bd.balance ?? "0",
339
+ balanceInFiat: bd.balanceInFiat != null ? `$${fiatBalance.toFixed(2)}` : "$0.00",
340
+ });
341
+ const nativeAlias = getNativeAddressAlias(bd.contractAddress);
342
+ const aliasKey = getTokenBalanceKey(bd.chain?.id, nativeAlias ?? undefined);
343
+ if (aliasKey) {
344
+ map.set(aliasKey, map.get(key));
345
+ }
346
+ }
347
+ }
348
+ return map;
349
+ }, [swapBalance, swapSupportedChainsAndTokens]);
350
+ const tokensWithBalances = useMemo(() => {
351
+ const tokensByKey = new Map();
352
+ for (const token of [...apiTokens, ...(additionalTokens ?? [])]) {
353
+ const address = token.contractAddress.toLowerCase() ===
354
+ "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
355
+ ? "0x0000000000000000000000000000000000000000"
356
+ : token.contractAddress.toLowerCase();
357
+ const key = `${token.chainId ?? 0}-${address}`;
358
+ const existing = tokensByKey.get(key);
359
+ tokensByKey.set(key, {
360
+ ...existing,
361
+ ...token,
362
+ priceUSD: token.priceUSD ?? existing?.priceUSD,
363
+ });
364
+ }
365
+ return Array.from(tokensByKey.values()).map((token) => {
366
+ const balance = balanceMap.get(getTokenBalanceKey(token.chainId, token.contractAddress) ?? "");
367
+ return balance ? { ...token, ...balance } : token;
368
+ });
369
+ }, [additionalTokens, apiTokens, balanceMap]);
370
+ useEffect(() => {
371
+ const handleGlobalClick = () => setTooltipState(null);
372
+ if (tooltipState) {
373
+ window.addEventListener("click", handleGlobalClick);
374
+ }
375
+ return () => {
376
+ window.removeEventListener("click", handleGlobalClick);
377
+ };
378
+ }, [tooltipState]);
379
+ // Reset visible count when filters change
380
+ useEffect(() => {
381
+ setVisibleCount(30);
382
+ if (listRef.current) {
383
+ listRef.current.scrollTop = 0;
384
+ }
385
+ }, [query, activeTab, selectedChainFilter]);
386
+ useEffect(() => {
387
+ if (!showFilterTabs && activeTab !== "all") {
388
+ setActiveTab("all");
389
+ }
390
+ }, [activeTab, showFilterTabs]);
391
+ const preserveListHeight = useCallback(() => {
392
+ const listEl = listRef.current;
393
+ if (!listEl)
394
+ return;
395
+ const nextHeight = Math.ceil(listEl.getBoundingClientRect().height);
396
+ if (nextHeight <= stableListHeightRef.current)
397
+ return;
398
+ stableListHeightRef.current = nextHeight;
399
+ setStableListHeight(nextHeight);
400
+ }, []);
401
+ useLayoutEffect(() => {
402
+ preserveListHeight();
403
+ const listEl = listRef.current;
404
+ if (!listEl || typeof ResizeObserver === "undefined")
405
+ return;
406
+ const observer = new ResizeObserver(() => {
407
+ preserveListHeight();
408
+ });
409
+ observer.observe(listEl);
410
+ return () => observer.disconnect();
411
+ }, [preserveListHeight]);
412
+ // Cross-reference map for chain names & logos, and balances
413
+ const chainMetaMap = useMemo(() => {
414
+ const map = new Map();
415
+ if (supportedChainsAndTokens) {
416
+ for (const c of supportedChainsAndTokens) {
417
+ map.set(c.id, { name: getShortChainName(c.id, c.name), logo: c.logo });
418
+ }
419
+ }
420
+ if (swapSupportedChainsAndTokens) {
421
+ for (const c of swapSupportedChainsAndTokens) {
422
+ if (!isSwapSupportedBySdkChainList(c.id, swapSupportedChainsAndTokens)) {
423
+ continue;
424
+ }
425
+ map.set(c.id, { name: getShortChainName(c.id, c.name), logo: c.logo });
426
+ }
427
+ }
428
+ if (!map.has(CITREA_CHAIN_ID)) {
429
+ map.set(CITREA_CHAIN_ID, getCitreaChainMeta());
430
+ }
431
+ return map;
432
+ }, [supportedChainsAndTokens, swapSupportedChainsAndTokens]);
433
+ const chainFilterIds = useMemo(() => {
434
+ const supportedIds = sdkSwapSupportedChainIds
435
+ ? Array.from(sdkSwapSupportedChainIds)
436
+ : swapSupportedChainsAndTokens
437
+ ?.map((chain) => chain.id)
438
+ .filter((id) => SUPPORTED_RECEIVE_CHAIN_IDS.has(id) &&
439
+ isSwapSupportedBySdkChainList(id, swapSupportedChainsAndTokens));
440
+ const nextIds = new Set(supportedIds ? supportedIds : Array.from(SUPPORTED_RECEIVE_CHAIN_IDS));
441
+ if (!sdkSwapSupportedChainIds ||
442
+ sdkSwapSupportedChainIds.has(CITREA_CHAIN_ID)) {
443
+ nextIds.add(CITREA_CHAIN_ID);
444
+ }
445
+ const allowedChainSet = allowedChainIds?.length
446
+ ? new Set(allowedChainIds)
447
+ : null;
448
+ return sortChainIdsBySwapDisplayOrder(Array.from(nextIds).filter((id) => (!allowedChainSet || allowedChainSet.has(id)) &&
449
+ (sdkSwapSupportedChainIds
450
+ ? sdkSwapSupportedChainIds.has(id)
451
+ : SUPPORTED_RECEIVE_CHAIN_IDS.has(id) &&
452
+ isSwapSupportedBySdkChainList(id, swapSupportedChainsAndTokens))));
453
+ }, [allowedChainIds, sdkSwapSupportedChainIds, swapSupportedChainsAndTokens]);
454
+ useEffect(() => {
455
+ if (selectedChainFilter && !chainFilterIds.includes(selectedChainFilter)) {
456
+ setSelectedChainFilter(null);
457
+ }
458
+ }, [chainFilterIds, selectedChainFilter]);
459
+ useEffect(() => {
460
+ let active = true;
461
+ const fetchTokens = async () => {
462
+ try {
463
+ setIsLoading(true);
464
+ console.log("[preloadReceiveTokens] Calling preloadReceiveTokens from ReceiveAssetSelector useEffect (fetchTokens)");
465
+ const data = await preloadReceiveTokens();
466
+ if (!active)
467
+ return;
468
+ if (!data)
469
+ return;
470
+ if (data.stableSymbols && Array.isArray(data.stableSymbols)) {
471
+ setDynamicStableSymbols(new Set([
472
+ ...Array.from(STABLE_SYMBOLS),
473
+ ...data.stableSymbols,
474
+ ...CITREA_STABLE_SYMBOLS,
475
+ ]));
476
+ }
477
+ const allParsed = [];
478
+ const chains = data.tokens || {};
479
+ for (const chainIdStr of Object.keys(chains)) {
480
+ const chainId = parseInt(chainIdStr, 10);
481
+ if (sdkSwapSupportedChainIds
482
+ ? !sdkSwapSupportedChainIds.has(chainId)
483
+ : !SUPPORTED_RECEIVE_CHAIN_IDS.has(chainId)) {
484
+ continue;
485
+ }
486
+ if (!isSwapSupportedBySdkChainList(chainId, swapSupportedChainsAndTokens)) {
487
+ continue;
488
+ }
489
+ const meta = chainMetaMap.get(chainId) || {
490
+ name: getShortChainName(chainId, `Chain ${chainId}`),
491
+ logo: "",
492
+ };
493
+ for (const t of chains[chainIdStr]) {
494
+ if (!t.address || !t.symbol)
495
+ continue;
496
+ allParsed.push({
497
+ contractAddress: t.address,
498
+ symbol: t.symbol,
499
+ name: t.name || t.symbol,
500
+ logo: t.logoURI || "",
501
+ decimals: t.decimals ?? 18,
502
+ priceUSD: t.priceUSD,
503
+ chainId,
504
+ chainName: meta.name,
505
+ chainLogo: meta.logo,
506
+ balance: "0",
507
+ balanceInFiat: "$0.00",
508
+ });
509
+ }
510
+ }
511
+ const tokensByKey = new Map();
512
+ for (const token of [...allParsed, ...getCitreaReceiveTokenOptions()]) {
513
+ const address = token.contractAddress.toLowerCase() ===
514
+ "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
515
+ ? "0x0000000000000000000000000000000000000000"
516
+ : token.contractAddress.toLowerCase();
517
+ const key = `${token.chainId ?? 0}-${address}`;
518
+ const existing = tokensByKey.get(key);
519
+ tokensByKey.set(key, {
520
+ ...existing,
521
+ ...token,
522
+ priceUSD: token.priceUSD ?? existing?.priceUSD,
523
+ });
524
+ }
525
+ setApiTokens(Array.from(tokensByKey.values()));
526
+ }
527
+ catch (err) {
528
+ console.error("Failed to fetch receive tokens", err);
529
+ }
530
+ finally {
531
+ if (active)
532
+ setIsLoading(false);
533
+ }
534
+ };
535
+ fetchTokens();
536
+ return () => {
537
+ active = false;
538
+ };
539
+ }, [chainMetaMap, sdkSwapSupportedChainIds, swapSupportedChainsAndTokens]);
540
+ const isNativeToken = (t) => t.contractAddress.toLowerCase() ===
541
+ "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee" ||
542
+ t.contractAddress.toLowerCase() ===
543
+ "0x0000000000000000000000000000000000000000";
544
+ const allowedPairKeys = useMemo(() => {
545
+ if (!allowedPairs?.length)
546
+ return null;
547
+ const keys = new Set();
548
+ for (const pair of allowedPairs) {
549
+ const address = pair.token.toLowerCase();
550
+ keys.add(`${pair.chain}-${address}`);
551
+ if (address === "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") {
552
+ keys.add(`${pair.chain}-0x0000000000000000000000000000000000000000`);
553
+ }
554
+ if (address === "0x0000000000000000000000000000000000000000") {
555
+ keys.add(`${pair.chain}-0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`);
556
+ }
557
+ }
558
+ return keys;
559
+ }, [allowedPairs]);
560
+ const allowedChainSet = useMemo(() => (allowedChainIds?.length ? new Set(allowedChainIds) : null), [allowedChainIds]);
561
+ const filtered = useMemo(() => {
562
+ let result = tokensWithBalances.filter((t) => t.chainId &&
563
+ (sdkSwapSupportedChainIds
564
+ ? sdkSwapSupportedChainIds.has(t.chainId)
565
+ : isSwapSupportedBySdkChainList(t.chainId, swapSupportedChainsAndTokens)));
566
+ if (allowedChainSet) {
567
+ result = result.filter((t) => t.chainId && allowedChainSet.has(t.chainId));
568
+ }
569
+ if (allowedPairKeys) {
570
+ result = result.filter((t) => allowedPairKeys.has(`${t.chainId ?? 0}-${t.contractAddress.toLowerCase()}`));
571
+ }
572
+ if (selectedChainFilter)
573
+ result = result.filter((t) => t.chainId === selectedChainFilter);
574
+ if (query.trim()) {
575
+ result = result.filter((t) => getTokenSearchRank(t, query) !== null);
576
+ }
577
+ if (showFilterTabs) {
578
+ if (activeTab === "native")
579
+ result = result.filter(isNativeToken);
580
+ else if (activeTab === "stables")
581
+ result = result.filter((t) => dynamicStableSymbols.has(t.symbol));
582
+ }
583
+ return result;
584
+ }, [
585
+ tokensWithBalances,
586
+ allowedChainSet,
587
+ allowedPairKeys,
588
+ selectedChainFilter,
589
+ query,
590
+ activeTab,
591
+ showFilterTabs,
592
+ dynamicStableSymbols,
593
+ sdkSwapSupportedChainIds,
594
+ swapSupportedChainsAndTokens,
595
+ ]);
596
+ const sortedFiltered = useMemo(() => {
597
+ return [...filtered].sort((a, b) => {
598
+ if (query.trim()) {
599
+ const aRank = getTokenSearchRank(a, query);
600
+ const bRank = getTokenSearchRank(b, query);
601
+ const aScore = aRank?.score ?? Number.MAX_SAFE_INTEGER;
602
+ const bScore = bRank?.score ?? Number.MAX_SAFE_INTEGER;
603
+ if (aScore !== bScore)
604
+ return aScore - bScore;
605
+ const aMatched = aRank?.matchedTerms ?? 0;
606
+ const bMatched = bRank?.matchedTerms ?? 0;
607
+ if (aMatched !== bMatched)
608
+ return bMatched - aMatched;
609
+ }
610
+ const aFiat = parseFiatValue(a.balanceInFiat);
611
+ const bFiat = parseFiatValue(b.balanceInFiat);
612
+ if (aFiat !== bFiat)
613
+ return bFiat - aFiat;
614
+ return `${a.symbol} ${a.chainName}`.localeCompare(`${b.symbol} ${b.chainName}`);
615
+ });
616
+ }, [filtered, query]);
617
+ const selectedChainMeta = selectedChainFilter === null
618
+ ? undefined
619
+ : chainMetaMap.get(selectedChainFilter);
620
+ const selectedChainLabel = selectedChainFilter === null
621
+ ? "All chains"
622
+ : selectedChainMeta?.name || "Chain";
623
+ return (_jsxs("div", { ref: selectorRef, style: {
624
+ ...modalHeightTransitionStyle,
625
+ boxSizing: "border-box",
626
+ display: "flex",
627
+ flex: "1 1 auto",
628
+ flexDirection: "column",
629
+ height: "100%",
630
+ maxHeight: "100%",
631
+ minHeight: 0,
632
+ overflow: "hidden",
633
+ padding: "12px",
634
+ position: "relative",
635
+ transition: modalHeightTransition,
636
+ width: "100%",
637
+ willChange: "height, max-height",
638
+ }, children: [_jsx("div", { style: {
639
+ width: "100%",
640
+ display: "flex",
641
+ justifyContent: "center",
642
+ marginBottom: 10,
643
+ }, children: _jsx("div", { style: {
644
+ width: 32,
645
+ height: 4,
646
+ borderRadius: 2,
647
+ backgroundColor: "#E8E8E7",
648
+ } }) }), _jsxs("div", { style: {
649
+ display: "flex",
650
+ alignItems: "flex-start",
651
+ gap: 12,
652
+ marginBottom: 12,
653
+ }, children: [_jsx("button", { onClick: onBack, style: {
654
+ width: 32,
655
+ height: 32,
656
+ borderRadius: 8,
657
+ border: "1px solid #E8E8E7",
658
+ display: "flex",
659
+ alignItems: "center",
660
+ justifyContent: "center",
661
+ backgroundColor: "#FFFFFE",
662
+ cursor: "pointer",
663
+ flexShrink: 0,
664
+ }, children: _jsx(ChevronDown, { style: { width: 16, height: 16, transform: "rotate(90deg)" } }) }), _jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: _jsx("span", { style: {
665
+ fontFamily: '"Geist", system-ui, sans-serif',
666
+ fontSize: 18,
667
+ fontWeight: 600,
668
+ color: "#161615",
669
+ }, children: "Select token to receive" }) })] }), _jsxs("div", { style: {
670
+ padding: "0 0 12px",
671
+ display: "flex",
672
+ flexDirection: "column",
673
+ gap: 10,
674
+ position: "relative",
675
+ zIndex: 10,
676
+ }, children: [_jsxs("div", { style: {
677
+ display: "flex",
678
+ alignItems: "center",
679
+ height: 42,
680
+ gap: 8,
681
+ borderRadius: 12,
682
+ border: `1px solid ${isSearchFocused ? "#A8C9FF" : "#E8E8E7"}`,
683
+ boxShadow: isSearchFocused
684
+ ? "0 0 0 1px rgba(0,107,244,0.16)"
685
+ : "none",
686
+ padding: "0 8px 0 14px",
687
+ backgroundColor: "#F0F0EF",
688
+ }, children: [_jsx(Search, { style: { width: 20, height: 20, color: "#848483", flexShrink: 0 } }), _jsx("input", { onBlur: () => setIsSearchFocused(false), onChange: (e) => setQuery(e.target.value), onFocus: () => setIsSearchFocused(true), placeholder: "Search token, chain or address", style: {
689
+ flex: 1,
690
+ backgroundColor: "transparent",
691
+ border: "none",
692
+ outline: "none",
693
+ fontFamily: '"Geist", system-ui, sans-serif',
694
+ fontSize: 14,
695
+ color: "#161615",
696
+ minWidth: 0,
697
+ }, value: query }), query && (_jsx("button", { onClick: () => setQuery(""), style: {
698
+ background: "none",
699
+ border: "none",
700
+ cursor: "pointer",
701
+ padding: 0,
702
+ }, children: _jsx(X, { style: { width: 16, height: 16, color: "#848483" } }) })), _jsxs("button", { onClick: openChainSelector, style: {
703
+ display: "flex",
704
+ alignItems: "center",
705
+ gap: 5,
706
+ padding: "4px 8px 4px 5px",
707
+ borderRadius: 999,
708
+ backgroundColor: "#FFFFFE",
709
+ border: "1px solid #E8E8E7",
710
+ cursor: "pointer",
711
+ height: 38,
712
+ flexShrink: 0,
713
+ boxShadow: "0px 1px 2px rgba(0,0,0,0.05)",
714
+ }, children: [selectedChainFilter === null ? (_jsx(Globe, { style: {
715
+ width: 16,
716
+ height: 16,
717
+ color: "#161615",
718
+ flexShrink: 0,
719
+ } })) : (_jsx("img", { alt: selectedChainLabel, src: selectedChainMeta?.logo, style: {
720
+ width: 18,
721
+ height: 18,
722
+ borderRadius: "999px",
723
+ objectFit: "cover",
724
+ flexShrink: 0,
725
+ } })), _jsx("span", { style: {
726
+ color: "#161615",
727
+ fontFamily: '"Geist", system-ui, sans-serif',
728
+ fontSize: "14px",
729
+ fontWeight: 500,
730
+ lineHeight: "18px",
731
+ maxWidth: "86px",
732
+ overflow: "hidden",
733
+ textOverflow: "ellipsis",
734
+ whiteSpace: "nowrap",
735
+ }, children: selectedChainLabel }), _jsx(ChevronDown, { style: { width: 14, height: 14, color: "#848483" } })] })] }), showFilterTabs && (_jsx("div", { style: {
736
+ display: "flex",
737
+ gap: 0,
738
+ backgroundColor: "#F0F0EF",
739
+ borderRadius: 8,
740
+ padding: 4,
741
+ }, children: FILTER_TABS.map((tab) => (_jsx("button", { onClick: () => setActiveTab(tab.key), style: {
742
+ flex: 1,
743
+ padding: "6px 0",
744
+ backgroundColor: activeTab === tab.key ? "#FFFFFE" : "transparent",
745
+ border: "none",
746
+ borderRadius: 6,
747
+ cursor: "pointer",
748
+ fontFamily: '"Geist", system-ui, sans-serif',
749
+ fontSize: 13,
750
+ fontWeight: 500,
751
+ color: activeTab === tab.key ? "#161615" : "#848483",
752
+ boxShadow: activeTab === tab.key
753
+ ? "0px 1px 2px rgba(0,0,0,0.05)"
754
+ : "none",
755
+ transition: "all 0.15s",
756
+ }, children: tab.label }, tab.key))) }))] }), _jsx("div", { onScroll: (e) => {
757
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
758
+ if (scrollHeight - scrollTop - clientHeight < 200) {
759
+ setVisibleCount((prev) => prev + 30);
760
+ }
761
+ }, ref: listRef, style: {
762
+ flex: "1 1 auto",
763
+ minHeight: stableListHeight ? `${stableListHeight}px` : 0,
764
+ overflowY: "auto",
765
+ position: "relative",
766
+ zIndex: hoveredHash || tooltipState ? 20 : 1,
767
+ }, children: isLoading ? (_jsx("div", { style: {
768
+ textAlign: "center",
769
+ padding: "40px",
770
+ color: "#848483",
771
+ fontFamily: '"Geist", system-ui, sans-serif',
772
+ }, children: "Loading..." })) : sortedFiltered.length === 0 ? (_jsx("div", { style: {
773
+ textAlign: "center",
774
+ padding: "40px",
775
+ color: "#848483",
776
+ fontFamily: '"Geist", system-ui, sans-serif',
777
+ }, children: "No tokens found" })) : (_jsx("div", { style: { display: "flex", flexDirection: "column" }, children: sortedFiltered.slice(0, visibleCount).map((t) => {
778
+ const hash = `${t.chainId}-${t.contractAddress}`;
779
+ const isSelected = selectedTokenHash === hash;
780
+ const isHovered = hoveredHash === hash;
781
+ const isInfoOpen = tooltipState?.hash === hash;
782
+ const isDetailActive = isHovered || isInfoOpen;
783
+ const numericBalance = Number.parseFloat(String(t.balance ?? "0").replace(/[^0-9.]/g, ""));
784
+ const hasBalance = Number.isFinite(numericBalance) && numericBalance > 0;
785
+ return (_jsxs("button", { onClick: () => {
786
+ setSelectedTokenHash(hash);
787
+ setSelectedTokenFull(t);
788
+ onSelect(t);
789
+ }, onMouseEnter: () => setHoveredHash(hash), onMouseLeave: () => setHoveredHash(null), style: {
790
+ width: "100%",
791
+ display: "flex",
792
+ alignItems: "center",
793
+ justifyContent: "space-between",
794
+ padding: "10px 14px",
795
+ backgroundColor: isSelected ? "#F4F7FE" : "transparent",
796
+ border: "none",
797
+ cursor: "pointer",
798
+ borderBottom: "1px solid #F0F0EF",
799
+ boxSizing: "border-box",
800
+ position: isDetailActive ? "relative" : "static",
801
+ zIndex: isDetailActive ? 50 : 1,
802
+ }, children: [_jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [_jsx(RadioDot, { selected: isSelected }), _jsxs("div", { style: {
803
+ position: "relative",
804
+ flexShrink: 0,
805
+ width: 40,
806
+ height: 40,
807
+ }, children: [_jsx(TokenLogo, { fontSize: 16, size: 40, token: t }), t.chainLogo && (_jsx("img", { alt: t.chainName, src: t.chainLogo, style: {
808
+ position: "absolute",
809
+ bottom: -8,
810
+ right: -8,
811
+ width: 22,
812
+ height: 22,
813
+ borderRadius: "999px",
814
+ border: "2px solid #FFFFFE",
815
+ zIndex: 2,
816
+ } }))] }), _jsxs("div", { style: {
817
+ display: "flex",
818
+ flexDirection: "column",
819
+ alignItems: "flex-start",
820
+ }, children: [_jsx("span", { style: {
821
+ fontFamily: '"Geist", system-ui, sans-serif',
822
+ fontWeight: 500,
823
+ fontSize: 15,
824
+ color: "#161615",
825
+ }, children: t.symbol }), _jsxs("div", { style: {
826
+ display: "flex",
827
+ alignItems: "center",
828
+ gap: 4,
829
+ }, children: [_jsx("span", { style: {
830
+ fontFamily: '"Geist", system-ui, sans-serif',
831
+ fontSize: 13,
832
+ color: "#848483",
833
+ }, children: isDetailActive
834
+ ? `${t.contractAddress.slice(0, 6)}...${t.contractAddress.slice(-4)}`
835
+ : `on ${t.chainName || "Unknown chain"}` }), isDetailActive && (_jsxs("div", { style: {
836
+ display: "flex",
837
+ alignItems: "center",
838
+ gap: "4px",
839
+ }, children: [copiedHash === hash ? (_jsx(Check, { style: {
840
+ width: 12,
841
+ height: 12,
842
+ color: "var(--foreground-brand)",
843
+ } })) : (_jsx(Copy, { onClick: (e) => {
844
+ e.stopPropagation();
845
+ navigator.clipboard.writeText(t.contractAddress);
846
+ setCopiedHash(hash);
847
+ setTimeout(() => setCopiedHash(null), 2000);
848
+ }, style: {
849
+ width: 12,
850
+ height: 12,
851
+ color: "#848483",
852
+ cursor: "pointer",
853
+ } })), _jsx("div", { className: "relative", onClick: (e) => {
854
+ e.stopPropagation();
855
+ if (tooltipState?.hash === hash) {
856
+ setTooltipState(null);
857
+ }
858
+ else {
859
+ const rect = e.currentTarget.getBoundingClientRect();
860
+ setTooltipState({
861
+ hash,
862
+ x: rect.left + rect.width / 2,
863
+ y: rect.top,
864
+ t,
865
+ });
866
+ }
867
+ }, children: _jsx(Info, { style: {
868
+ width: 12,
869
+ height: 12,
870
+ color: "#848483",
871
+ cursor: "pointer",
872
+ } }) })] }))] })] })] }), hasBalance && (_jsxs("div", { style: {
873
+ display: "flex",
874
+ flexDirection: "column",
875
+ alignItems: "flex-end",
876
+ }, children: [_jsx("span", { style: {
877
+ fontFamily: '"Geist", system-ui, sans-serif',
878
+ fontWeight: 500,
879
+ fontSize: 14,
880
+ color: "#161615",
881
+ }, children: formatTokenBalance(t.balance, {
882
+ symbol: t.symbol,
883
+ decimals: t.decimals,
884
+ }) ?? `${t.balance} ${t.symbol}` }), _jsx("span", { style: {
885
+ fontFamily: '"Geist", system-ui, sans-serif',
886
+ fontSize: 13,
887
+ color: "#848483",
888
+ }, children: t.balanceInFiat })] }))] }, hash));
889
+ }) })) }), showChainSelector &&
890
+ (() => {
891
+ const chainModal = (_jsxs("div", { style: {
892
+ bottom: 0,
893
+ display: "flex",
894
+ flexDirection: "column",
895
+ justifyContent: "flex-end",
896
+ left: 0,
897
+ pointerEvents: "none",
898
+ position: "absolute",
899
+ right: 0,
900
+ top: 0,
901
+ zIndex: 50,
902
+ }, children: [_jsx("div", { onClick: closeChainSelector, style: {
903
+ backgroundColor: "rgba(0,0,0,0.22)",
904
+ bottom: 0,
905
+ left: 0,
906
+ pointerEvents: "auto",
907
+ position: "absolute",
908
+ right: 0,
909
+ top: 0,
910
+ opacity: isChainSelectorClosing ? 0 : 1,
911
+ transition: `opacity ${CHAIN_SELECTOR_CLOSE_MS}ms ease`,
912
+ } }), _jsxs("div", { className: isChainSelectorClosing
913
+ ? undefined
914
+ : "animate-in slide-in-from-bottom-full duration-300", "data-nexus-widget-sheet": true, style: {
915
+ ...modalHeightTransitionStyle,
916
+ backgroundColor: "#FFFFFE",
917
+ borderRadius: "24px 24px 0 0",
918
+ boxShadow: "0 -4px 16px rgba(0,0,0,0.08)",
919
+ boxSizing: "border-box",
920
+ display: "flex",
921
+ flexDirection: "column",
922
+ height: "90%",
923
+ maxHeight: "90%",
924
+ overflow: "hidden",
925
+ padding: "12px",
926
+ pointerEvents: "auto",
927
+ position: "relative",
928
+ transform: isChainSelectorClosing
929
+ ? "translateY(100%)"
930
+ : "translateY(0)",
931
+ transition: `${modalHeightTransition}, transform ${CHAIN_SELECTOR_CLOSE_MS}ms ease, opacity ${CHAIN_SELECTOR_CLOSE_MS}ms ease`,
932
+ opacity: isChainSelectorClosing ? 0 : 1,
933
+ willChange: "height, max-height, transform, opacity",
934
+ width: "100%",
935
+ }, children: [_jsx("div", { style: {
936
+ display: "flex",
937
+ justifyContent: "center",
938
+ marginBottom: 8,
939
+ width: "100%",
940
+ }, children: _jsx("div", { style: {
941
+ backgroundColor: "#D8D8D6",
942
+ borderRadius: "999px",
943
+ height: 4,
944
+ width: 32,
945
+ } }) }), _jsxs("div", { style: {
946
+ alignItems: "center",
947
+ display: "flex",
948
+ gap: 10,
949
+ marginBottom: 10,
950
+ }, children: [_jsx("button", { onClick: closeChainSelector, style: {
951
+ alignItems: "center",
952
+ backgroundColor: "#FFFFFE",
953
+ border: "1px solid #E8E8E7",
954
+ borderRadius: 8,
955
+ cursor: "pointer",
956
+ display: "flex",
957
+ flexShrink: 0,
958
+ height: 30,
959
+ justifyContent: "center",
960
+ width: 30,
961
+ }, children: _jsx(ChevronDown, { style: {
962
+ width: 15,
963
+ height: 15,
964
+ transform: "rotate(90deg)",
965
+ } }) }), _jsx("span", { style: {
966
+ fontFamily: '"Geist", system-ui, sans-serif',
967
+ fontWeight: 600,
968
+ fontSize: 17,
969
+ color: "#161615",
970
+ }, children: "Select chain" })] }), _jsx("div", { style: { paddingBottom: 10 }, children: _jsxs("div", { style: {
971
+ alignItems: "center",
972
+ backgroundColor: "#FFFFFE",
973
+ border: `1px solid ${isChainSearchFocused ? "#A8C9FF" : "#E8E8E7"}`,
974
+ borderRadius: 11,
975
+ boxShadow: isChainSearchFocused
976
+ ? "0 0 0 1px rgba(0,107,244,0.16)"
977
+ : "none",
978
+ display: "flex",
979
+ gap: 8,
980
+ height: 38,
981
+ padding: "0 12px",
982
+ }, children: [_jsx(Search, { style: {
983
+ width: 18,
984
+ height: 18,
985
+ color: "#848483",
986
+ flexShrink: 0,
987
+ } }), _jsx("input", { onBlur: () => setIsChainSearchFocused(false), onChange: (e) => setChainQuery(e.target.value), onFocus: () => setIsChainSearchFocused(true), placeholder: "Search chains", style: {
988
+ backgroundColor: "transparent",
989
+ border: "none",
990
+ color: "#161615",
991
+ flex: 1,
992
+ fontFamily: '"Geist", system-ui, sans-serif',
993
+ fontSize: 13,
994
+ minWidth: 0,
995
+ outline: "none",
996
+ }, value: chainQuery })] }) }), _jsx("div", { style: {
997
+ flex: "1 1 auto",
998
+ marginBottom: 10,
999
+ minHeight: 0,
1000
+ overflowY: "auto",
1001
+ }, children: _jsxs("div", { style: {
1002
+ border: "1px solid #E8E8E7",
1003
+ borderRadius: 12,
1004
+ overflow: "hidden",
1005
+ backgroundColor: "#FFFFFE",
1006
+ }, children: [_jsxs("button", { onClick: () => {
1007
+ setSelectedChainFilter(null);
1008
+ closeChainSelector();
1009
+ }, style: {
1010
+ width: "100%",
1011
+ display: "flex",
1012
+ alignItems: "center",
1013
+ padding: "8px 14px",
1014
+ backgroundColor: "transparent",
1015
+ border: "none",
1016
+ borderBottom: "1px solid #F0F0EF",
1017
+ cursor: "pointer",
1018
+ boxSizing: "border-box",
1019
+ }, children: [_jsx(RadioDot, { selected: selectedChainFilter === null }), _jsxs("div", { style: {
1020
+ display: "flex",
1021
+ alignItems: "center",
1022
+ gap: 10,
1023
+ marginLeft: 10,
1024
+ }, children: [_jsx(Globe, { style: {
1025
+ width: 28,
1026
+ height: 28,
1027
+ color: "#161615",
1028
+ flexShrink: 0,
1029
+ } }), _jsx("span", { style: {
1030
+ fontFamily: '"Geist", system-ui, sans-serif',
1031
+ fontWeight: 500,
1032
+ fontSize: 14,
1033
+ color: "#161615",
1034
+ }, children: "All Chains" })] })] }), chainFilterIds
1035
+ .filter((id) => {
1036
+ const meta = chainMetaMap.get(id);
1037
+ return (meta?.name || "")
1038
+ .toLowerCase()
1039
+ .includes(chainQuery.toLowerCase());
1040
+ })
1041
+ .map((id) => {
1042
+ const meta = chainMetaMap.get(id);
1043
+ if (!meta)
1044
+ return null;
1045
+ return (_jsxs("button", { onClick: () => {
1046
+ setSelectedChainFilter(id);
1047
+ closeChainSelector();
1048
+ }, style: {
1049
+ width: "100%",
1050
+ display: "flex",
1051
+ alignItems: "center",
1052
+ padding: "8px 14px",
1053
+ backgroundColor: "transparent",
1054
+ border: "none",
1055
+ borderBottom: "1px solid #F0F0EF",
1056
+ cursor: "pointer",
1057
+ boxSizing: "border-box",
1058
+ }, children: [_jsx(RadioDot, { selected: selectedChainFilter === id }), _jsxs("div", { style: {
1059
+ display: "flex",
1060
+ alignItems: "center",
1061
+ gap: 10,
1062
+ marginLeft: 10,
1063
+ }, children: [_jsx("img", { src: meta.logo, style: {
1064
+ width: 28,
1065
+ height: 28,
1066
+ borderRadius: "999px",
1067
+ objectFit: "cover",
1068
+ } }), _jsx("span", { style: {
1069
+ fontFamily: '"Geist", system-ui, sans-serif',
1070
+ fontWeight: 500,
1071
+ fontSize: 14,
1072
+ color: "#161615",
1073
+ }, children: meta.name })] })] }, id));
1074
+ })] }) })] })] }));
1075
+ return portalRoot ? createPortal(chainModal, portalRoot) : chainModal;
1076
+ })(), tooltipState &&
1077
+ typeof window !== "undefined" &&
1078
+ (() => {
1079
+ const explorerUrl = tooltipState.t.chainId
1080
+ ? CHAIN_METADATA[tooltipState.t.chainId]?.blockExplorerUrls?.[0]
1081
+ : null;
1082
+ return createPortal(_jsxs("div", { className: "w-[280px] bg-white border border-[#E8E8E7] rounded-xl shadow-[0_8px_24px_rgba(0,0,0,0.12)] p-4 text-left", onClick: (e) => e.stopPropagation(), style: {
1083
+ position: "fixed",
1084
+ top: tooltipState.y - 12,
1085
+ left: tooltipState.x,
1086
+ transform: "translate(-50%, -100%)",
1087
+ zIndex: 2147483647,
1088
+ display: "flex",
1089
+ flexDirection: "column",
1090
+ pointerEvents: "auto",
1091
+ }, children: [_jsx("div", { style: {
1092
+ position: "absolute",
1093
+ bottom: "-6px",
1094
+ left: "50%",
1095
+ transform: "translateX(-50%) rotate(45deg)",
1096
+ width: "12px",
1097
+ height: "12px",
1098
+ backgroundColor: "#fff",
1099
+ borderRight: "1px solid #E8E8E7",
1100
+ borderBottom: "1px solid #E8E8E7",
1101
+ zIndex: 1,
1102
+ } }), _jsxs("div", { style: {
1103
+ display: "flex",
1104
+ alignItems: "center",
1105
+ gap: 8,
1106
+ marginBottom: 16,
1107
+ position: "relative",
1108
+ zIndex: 2,
1109
+ }, children: [_jsxs("div", { style: { position: "relative", width: 24, height: 24 }, children: [_jsx(TokenLogo, { fontSize: 10, size: 24, token: tooltipState.t }), tooltipState.t.chainLogo && (_jsx("img", { alt: tooltipState.t.chainName, src: tooltipState.t.chainLogo, style: {
1110
+ position: "absolute",
1111
+ bottom: -4,
1112
+ right: -4,
1113
+ width: 10,
1114
+ height: 10,
1115
+ borderRadius: "999px",
1116
+ border: "1px solid #FFFFFE",
1117
+ zIndex: 2,
1118
+ } }))] }), _jsx("div", { style: { display: "flex", flexDirection: "column" }, children: _jsx("span", { style: {
1119
+ fontFamily: '"Geist", system-ui, sans-serif',
1120
+ fontWeight: 600,
1121
+ fontSize: 14,
1122
+ color: "#161615",
1123
+ }, children: tooltipState.t.name }) })] }), _jsxs("div", { style: {
1124
+ display: "flex",
1125
+ justifyContent: "space-between",
1126
+ marginBottom: 8,
1127
+ position: "relative",
1128
+ zIndex: 2,
1129
+ }, children: [_jsx("span", { style: {
1130
+ fontFamily: '"Geist", system-ui, sans-serif',
1131
+ fontSize: 12,
1132
+ color: "#848483",
1133
+ }, children: "Symbol:" }), _jsx("span", { style: {
1134
+ fontFamily: '"Geist", system-ui, sans-serif',
1135
+ fontSize: 12,
1136
+ color: "#161615",
1137
+ fontWeight: 500,
1138
+ }, children: tooltipState.t.symbol })] }), _jsxs("div", { style: {
1139
+ display: "flex",
1140
+ justifyContent: "space-between",
1141
+ marginBottom: 8,
1142
+ position: "relative",
1143
+ zIndex: 2,
1144
+ }, children: [_jsx("span", { style: {
1145
+ fontFamily: '"Geist", system-ui, sans-serif',
1146
+ fontSize: 12,
1147
+ color: "#848483",
1148
+ }, children: "Decimals:" }), _jsx("span", { style: {
1149
+ fontFamily: '"Geist", system-ui, sans-serif',
1150
+ fontSize: 12,
1151
+ color: "#161615",
1152
+ fontWeight: 500,
1153
+ }, children: tooltipState.t.decimals })] }), _jsxs("div", { style: {
1154
+ display: "flex",
1155
+ flexDirection: "column",
1156
+ gap: 4,
1157
+ position: "relative",
1158
+ zIndex: 2,
1159
+ }, children: [_jsx("span", { style: {
1160
+ fontFamily: '"Geist", system-ui, sans-serif',
1161
+ fontSize: 12,
1162
+ color: "#848483",
1163
+ }, children: "Contract address:" }), explorerUrl ? (_jsx("a", { href: `${explorerUrl}/address/${tooltipState.t.contractAddress}`, rel: "noopener noreferrer", style: {
1164
+ fontFamily: '"Geist", system-ui, sans-serif',
1165
+ fontSize: 11,
1166
+ color: "var(--foreground-brand)",
1167
+ wordBreak: "break-all",
1168
+ textDecoration: "underline",
1169
+ outline: "none",
1170
+ cursor: "pointer",
1171
+ }, target: "_blank", children: tooltipState.t.contractAddress })) : (_jsx("span", { style: {
1172
+ fontFamily: '"Geist", system-ui, sans-serif',
1173
+ fontSize: 11,
1174
+ color: "#161615",
1175
+ wordBreak: "break-all",
1176
+ }, children: tooltipState.t.contractAddress }))] })] }), document.body);
1177
+ })()] }));
1178
+ }