@rhinestone/deposit-modal 0.15.0 → 0.17.0

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 (52) hide show
  1. package/README.md +22 -26
  2. package/dist/{DepositModalReown-R37SSHUF.cjs → DepositModalReown-6CXXGQ7X.cjs} +16 -17
  3. package/dist/{DepositModalReown-KOJ7VEY7.mjs → DepositModalReown-ELDSTIME.mjs} +3 -4
  4. package/dist/WithdrawModal-QB2U756L.cjs +11 -0
  5. package/dist/{WithdrawModal-QVXHMH43.mjs → WithdrawModal-QRMDR5T4.mjs} +3 -4
  6. package/dist/{chunk-DTVZUBHI.cjs → chunk-2W4BJ5I3.cjs} +71 -73
  7. package/dist/{chunk-IKUYZA7A.mjs → chunk-H66WM5AO.mjs} +33 -32
  8. package/dist/{chunk-5P2PVLS4.cjs → chunk-JCWJ6HW7.cjs} +1 -1
  9. package/dist/{chunk-3OOY67VJ.cjs → chunk-QGNJFDEO.cjs} +5143 -1882
  10. package/dist/{chunk-5VONFVQN.mjs → chunk-QMGEK623.mjs} +5146 -1885
  11. package/dist/{chunk-CXBMIZUF.mjs → chunk-XCBDJPBD.mjs} +1 -1
  12. package/dist/{chunk-VPDCGD2D.cjs → chunk-XJMDCBHS.cjs} +443 -442
  13. package/dist/{chunk-7UQ6OUEE.mjs → chunk-ZHX4QROC.mjs} +13 -15
  14. package/dist/constants.cjs +2 -2
  15. package/dist/constants.d.cts +1 -1
  16. package/dist/constants.d.ts +1 -1
  17. package/dist/constants.mjs +1 -1
  18. package/dist/deposit.cjs +4 -5
  19. package/dist/deposit.d.cts +2 -2
  20. package/dist/deposit.d.ts +2 -2
  21. package/dist/deposit.mjs +3 -4
  22. package/dist/embed.cjs +18 -59
  23. package/dist/embed.d.cts +2 -8
  24. package/dist/embed.d.ts +2 -8
  25. package/dist/embed.mjs +9 -50
  26. package/dist/index.cjs +5 -10
  27. package/dist/index.d.cts +1 -3
  28. package/dist/index.d.ts +1 -3
  29. package/dist/index.mjs +4 -9
  30. package/dist/server.d.cts +2 -2
  31. package/dist/server.d.ts +2 -2
  32. package/dist/styles.css +37 -0
  33. package/dist/{types-CQ0NHqCy.d.ts → types-B9SbZQIG.d.ts} +15 -113
  34. package/dist/{types-CxiLX5sv.d.cts → types-SwGGf0eH.d.cts} +15 -113
  35. package/dist/withdraw.cjs +4 -5
  36. package/dist/withdraw.d.cts +31 -3
  37. package/dist/withdraw.d.ts +31 -3
  38. package/dist/withdraw.mjs +3 -4
  39. package/package.json +6 -37
  40. package/dist/ClaimModal-GFDBVCN5.cjs +0 -11
  41. package/dist/ClaimModal-VQS3AKEL.mjs +0 -11
  42. package/dist/WithdrawModal-RY2NMFLE.cjs +0 -12
  43. package/dist/chunk-6HUHVXCY.cjs +0 -704
  44. package/dist/chunk-QJ7RCCU7.mjs +0 -704
  45. package/dist/chunk-UCBUCX2W.mjs +0 -3169
  46. package/dist/chunk-X4D52IZK.cjs +0 -3169
  47. package/dist/claim.cjs +0 -9
  48. package/dist/claim.d.cts +0 -17
  49. package/dist/claim.d.ts +0 -17
  50. package/dist/claim.mjs +0 -9
  51. package/dist/modal-ui-state-OD1RmKkK.d.cts +0 -30
  52. package/dist/modal-ui-state-OD1RmKkK.d.ts +0 -30
@@ -1,3169 +0,0 @@
1
- import {
2
- AlertTriangleIcon,
3
- AnalyticsSession,
4
- ArrowUpRightIcon,
5
- BodyHeader,
6
- Button,
7
- Callout,
8
- CdnIcon,
9
- CheckIcon,
10
- ChevronDownIcon,
11
- ChevronLeftIcon,
12
- ChevronRightIcon,
13
- ClaimAnalyticsControllerProvider,
14
- CloseIcon,
15
- CoinsIcon,
16
- ExternalLinkIcon,
17
- HistoryIcon,
18
- InfoIcon,
19
- ListRow,
20
- PoweredBy,
21
- RECOVERY_LOOKUP_MISS_COPY,
22
- RecoveryError,
23
- RecoveryLookupScreen,
24
- RecoveryOutcomeScreen,
25
- RecoveryReviewScreen,
26
- RegistrationError,
27
- Spinner,
28
- TokenIcon,
29
- WalletIcon,
30
- claimFailureReasonForError,
31
- classifyRecoveryLookup,
32
- debugError,
33
- debugLog,
34
- describeRecoveryDeposit,
35
- estimateReceiveAmount,
36
- formatFeeUsd,
37
- formatQuotedReceive,
38
- formatRawTokenAmount,
39
- formatRecoveryAmount,
40
- formatTokenAmount,
41
- formatUserError,
42
- isRecognizedToken,
43
- quoteFeeGroups,
44
- quoteFeeRow,
45
- randomUuid,
46
- recoveryFailureCause,
47
- resolveTokenDisplay,
48
- submitSignedRecovery,
49
- toRecoverableDeposit,
50
- tokenAmountToNumber,
51
- useClaimAnalyticsController,
52
- useExplorerTxUrl,
53
- useLatestRef
54
- } from "./chunk-5VONFVQN.mjs";
55
- import {
56
- HYPERCORE_CHAIN_ID,
57
- NATIVE_TOKEN_ADDRESS,
58
- SOLANA_MAINNET_CAIP2,
59
- SOLANA_TOKENS,
60
- cdnIconUrl,
61
- displayChainIdForCaip2,
62
- getChainIcon,
63
- getChainName,
64
- getTokenDecimalsByAddress,
65
- getTokenSymbol,
66
- targetChainToCaip2,
67
- targetChainToChainId,
68
- toEvmCaip2,
69
- tokenIconUrl
70
- } from "./chunk-EAGMY3QE.mjs";
71
-
72
- // src/core/history-status.ts
73
- function normalizeStatus(raw, isSpam) {
74
- const lower = raw.toLowerCase();
75
- if (lower === "completed" || lower === "complete" || lower === "bridge-complete" || lower === "processed")
76
- return "completed";
77
- if (lower === "failed" || lower === "error" || lower === "bridge-failed" || lower === "rejected") {
78
- return isSpam ? "spam" : "failed";
79
- }
80
- if (lower === "refunded") return "refunded";
81
- if (lower === "processing" || lower === "submitted" || lower === "bridging")
82
- return "processing";
83
- return "pending";
84
- }
85
- var STATUS_LABEL = {
86
- pending: "Pending",
87
- processing: "Processing",
88
- completed: "Completed",
89
- failed: "Failed",
90
- refunded: "Refunded",
91
- spam: "Spam token"
92
- };
93
- function filterVisibleDeposits(deposits) {
94
- const sourceHashes = new Set(
95
- deposits.map((d) => d.sourceTxHash?.toLowerCase()).filter((h) => Boolean(h))
96
- );
97
- return deposits.filter(
98
- (d) => !(d.txHash && sourceHashes.has(d.txHash.toLowerCase()))
99
- );
100
- }
101
-
102
- // src/core/history-badge.ts
103
- function deriveHistoryBadge(deposits, { recipient, canRecover }) {
104
- const visible = filterVisibleDeposits(deposits);
105
- if (canRecover) {
106
- const needsYou = visible.filter(
107
- (row) => toRecoverableDeposit(row, { recipient }) !== null
108
- ).length;
109
- if (needsYou > 0) return { state: "needs-you", count: needsYou };
110
- }
111
- const inProgress = visible.filter((row) => {
112
- const status = normalizeStatus(row.status, row.isSpam);
113
- return status === "pending" || status === "processing";
114
- }).length;
115
- if (inProgress > 0) return { state: "progress", count: inProgress };
116
- return null;
117
- }
118
- function historyBadgeAriaLabel(badge) {
119
- if (!badge) return "Deposit history";
120
- const single = badge.count === 1;
121
- const noun = single ? "deposit" : "deposits";
122
- const state = badge.state === "needs-you" ? single ? "needs you" : "need you" : "in progress";
123
- return `Deposit history \u2014 ${badge.count} ${noun} ${state}`;
124
- }
125
-
126
- // src/components/history/HistoryEntryButton.tsx
127
- import { jsx, jsxs } from "react/jsx-runtime";
128
- function HistoryEntryButton({
129
- badge,
130
- onClick,
131
- disabled
132
- }) {
133
- return /* @__PURE__ */ jsxs(
134
- "button",
135
- {
136
- type: "button",
137
- className: "rs-modal-header-history",
138
- "aria-label": historyBadgeAriaLabel(badge),
139
- onClick,
140
- disabled,
141
- children: [
142
- /* @__PURE__ */ jsx(HistoryIcon, {}),
143
- badge && /* @__PURE__ */ jsx(
144
- "span",
145
- {
146
- className: `rs-history-dot rs-history-dot--${badge.state}`,
147
- "aria-hidden": "true"
148
- }
149
- )
150
- ]
151
- }
152
- );
153
- }
154
-
155
- // src/components/history/HistoryOverlay.tsx
156
- import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo2, useRef as useRef4, useState as useState4 } from "react";
157
-
158
- // src/components/history/DepositHistoryPanel.tsx
159
- import {
160
- useCallback,
161
- useEffect,
162
- useId,
163
- useMemo,
164
- useRef,
165
- useState
166
- } from "react";
167
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
168
- function shortenHash(hash) {
169
- if (hash.length <= 14) return hash;
170
- return `${hash.slice(0, 6)}\u2026${hash.slice(-4)}`;
171
- }
172
- function formatDate(iso) {
173
- try {
174
- const date = new Date(iso);
175
- if (Number.isNaN(date.getTime())) return iso;
176
- return date.toLocaleDateString("en-US", {
177
- day: "numeric",
178
- month: "short",
179
- year: "numeric"
180
- });
181
- } catch {
182
- return iso;
183
- }
184
- }
185
- function resolveChainId(value) {
186
- if (value === void 0 || value === null) return null;
187
- if (typeof value === "number") return value;
188
- const display = displayChainIdForCaip2(value);
189
- if (display !== null) return display;
190
- if (value === "solana" || value === "hypercore") return value;
191
- const num = Number(value);
192
- return Number.isFinite(num) ? num : null;
193
- }
194
- function resolveTokenSymbol(token, chainId) {
195
- if (!token) return "";
196
- if (chainId === "solana") {
197
- const normalized = token.toLowerCase();
198
- const matched = SOLANA_TOKENS.find(
199
- (entry) => entry.mint.toLowerCase() === normalized
200
- );
201
- if (matched) return matched.symbol;
202
- if (normalized === "native" || normalized === "11111111111111111111111111111111" || normalized === "so11111111111111111111111111111111111111112") {
203
- return "SOL";
204
- }
205
- }
206
- if (chainId !== null && chainId !== void 0 && chainId !== "solana" && // Both callers pass the SOURCE chain, and `/chains` reports
207
- // `deposit: false` for Tron and Stellar — so neither can appear here, and
208
- // neither has an EVM token table to look one up in if it did.
209
- chainId !== "tron" && chainId !== "stellar" && /^0x[a-fA-F0-9]{40}$/.test(token)) {
210
- const evmChainId = chainId === "hypercore" ? HYPERCORE_CHAIN_ID : chainId;
211
- const sym = getTokenSymbol(token, evmChainId);
212
- if (sym !== "Token") return sym;
213
- }
214
- return shortenHash(token);
215
- }
216
- function formatAmount(rawAmount, token, chainId) {
217
- if (!rawAmount) return "";
218
- let decimals = 18;
219
- if (token && chainId !== null && chainId !== void 0) {
220
- if (chainId === "solana") {
221
- const normalized = token.toLowerCase();
222
- const matched = SOLANA_TOKENS.find(
223
- (entry) => entry.mint.toLowerCase() === normalized
224
- );
225
- if (matched) {
226
- decimals = matched.decimals;
227
- } else if (normalized === "native" || normalized === "11111111111111111111111111111111" || normalized === "so11111111111111111111111111111111111111112") {
228
- decimals = 9;
229
- }
230
- } else if (
231
- // See `resolveTokenSymbol`: this reads the source chain, which Tron and
232
- // Stellar cannot be.
233
- chainId !== "tron" && chainId !== "stellar" && /^0x[a-fA-F0-9]{40}$/.test(token)
234
- ) {
235
- const evmChainId = chainId === "hypercore" ? HYPERCORE_CHAIN_ID : chainId;
236
- decimals = getTokenDecimalsByAddress(token, evmChainId);
237
- }
238
- }
239
- try {
240
- const raw = BigInt(rawAmount);
241
- const divisor = 10n ** BigInt(decimals);
242
- const whole = raw / divisor;
243
- const remainder = raw % divisor;
244
- const fracStr = remainder.toString().padStart(decimals, "0");
245
- let frac = fracStr.slice(0, 5).replace(/0+$/, "");
246
- if (frac.length < 2) frac = frac.padEnd(2, "0");
247
- return `${whole}.${frac}`;
248
- } catch {
249
- return rawAmount;
250
- }
251
- }
252
- function DepositHistoryPanel({
253
- deposits,
254
- isLoading,
255
- error,
256
- hasMore,
257
- isLoadingMore,
258
- onLoadMore,
259
- onClose,
260
- onCloseModal,
261
- backLabel,
262
- title = "History",
263
- recipient,
264
- onRecover,
265
- onFindByTxHash,
266
- onRowView,
267
- obscured
268
- }) {
269
- const rootRef = useRef(null);
270
- useEffect(() => {
271
- rootRef.current?.toggleAttribute("inert", Boolean(obscured));
272
- }, [obscured]);
273
- const handleKeyDown = useCallback(
274
- (e) => {
275
- if (obscured) return;
276
- if (e.key === "Escape") {
277
- e.stopPropagation();
278
- onClose();
279
- }
280
- },
281
- [onClose, obscured]
282
- );
283
- const visibleDeposits = useMemo(
284
- () => filterVisibleDeposits(deposits),
285
- [deposits]
286
- );
287
- return /* @__PURE__ */ jsxs2(
288
- "div",
289
- {
290
- ref: rootRef,
291
- className: "rs-history-panel",
292
- role: "dialog",
293
- "aria-label": "Deposit history",
294
- "aria-hidden": obscured || void 0,
295
- onKeyDown: handleKeyDown,
296
- children: [
297
- /* @__PURE__ */ jsxs2("div", { className: "rs-modal-header--redesigned", children: [
298
- /* @__PURE__ */ jsx2("div", { className: "rs-modal-header-nav-left", children: /* @__PURE__ */ jsxs2(
299
- "button",
300
- {
301
- type: "button",
302
- className: `rs-modal-header-back${backLabel ? " rs-modal-header-back--labelled" : ""}`,
303
- "aria-label": backLabel ? `Back to ${backLabel.toLowerCase()}` : "Back",
304
- onClick: onClose,
305
- children: [
306
- /* @__PURE__ */ jsx2(ChevronLeftIcon, {}),
307
- backLabel && /* @__PURE__ */ jsx2("span", { children: backLabel })
308
- ]
309
- }
310
- ) }),
311
- /* @__PURE__ */ jsx2("div", { className: "rs-modal-header-nav-right", children: onCloseModal && /* @__PURE__ */ jsx2(
312
- "button",
313
- {
314
- type: "button",
315
- className: "rs-modal-close",
316
- "aria-label": "Close",
317
- onClick: onCloseModal,
318
- children: /* @__PURE__ */ jsx2(CloseIcon, {})
319
- }
320
- ) })
321
- ] }),
322
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-screen", children: [
323
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-screen-body", children: [
324
- /* @__PURE__ */ jsx2(BodyHeader, { icon: /* @__PURE__ */ jsx2(HistoryIcon, {}), title }),
325
- onFindByTxHash && /* @__PURE__ */ jsxs2(
326
- "button",
327
- {
328
- type: "button",
329
- className: "rs-history-hint",
330
- onClick: onFindByTxHash,
331
- children: [
332
- /* @__PURE__ */ jsx2("span", { children: "Deposit missing?" }),
333
- /* @__PURE__ */ jsx2("strong", { children: "Find it by transaction hash" })
334
- ]
335
- }
336
- ),
337
- isLoading && deposits.length === 0 && /* @__PURE__ */ jsxs2("div", { className: "rs-history-empty", children: [
338
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(HistoryIcon, {}) }),
339
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-empty-text", children: [
340
- /* @__PURE__ */ jsx2(Spinner, { className: "rs-spinner--sm" }),
341
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-hint", children: "Loading history\u2026" })
342
- ] })
343
- ] }),
344
- error && !isLoading && /* @__PURE__ */ jsxs2("div", { className: "rs-history-empty", children: [
345
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(AlertTriangleIcon, {}) }),
346
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-empty-text", children: [
347
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-title", children: "Couldn't load history" }),
348
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-hint", children: error })
349
- ] })
350
- ] }),
351
- !isLoading && !error && visibleDeposits.length === 0 && /* @__PURE__ */ jsxs2("div", { className: "rs-history-empty", children: [
352
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-glyph", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(HistoryIcon, {}) }),
353
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-empty-text", children: [
354
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-title", children: "No deposits yet" }),
355
- /* @__PURE__ */ jsx2("span", { className: "rs-history-empty-hint", children: "Your deposit history will appear here" })
356
- ] })
357
- ] }),
358
- visibleDeposits.length > 0 && /* @__PURE__ */ jsxs2("div", { className: "rs-history-list", children: [
359
- visibleDeposits.map((deposit, i) => /* @__PURE__ */ jsx2(
360
- HistoryCard,
361
- {
362
- deposit,
363
- position: i,
364
- recipient,
365
- onRecover,
366
- onRowView
367
- },
368
- deposit.txHash || i
369
- )),
370
- hasMore && /* @__PURE__ */ jsx2(
371
- "button",
372
- {
373
- type: "button",
374
- className: "rs-history-load-more",
375
- onClick: onLoadMore,
376
- disabled: isLoadingMore,
377
- children: isLoadingMore ? "Loading\u2026" : "Load more"
378
- }
379
- )
380
- ] })
381
- ] }),
382
- /* @__PURE__ */ jsx2(PoweredBy, {})
383
- ] })
384
- ]
385
- }
386
- );
387
- }
388
- function HistoryCard({
389
- deposit,
390
- position,
391
- recipient,
392
- onRecover,
393
- onRowView
394
- }) {
395
- const explorerTxUrl = useExplorerTxUrl();
396
- const [expanded, setExpanded] = useState(false);
397
- const panelId = useId();
398
- const status = normalizeStatus(deposit.status, deposit.isSpam);
399
- const sourceChainId = resolveChainId(deposit.chain);
400
- const targetChainId = resolveChainId(deposit.targetChain);
401
- const sourceChainName = sourceChainId ? getChainName(sourceChainId) : null;
402
- const targetChainName = targetChainId ? getChainName(targetChainId) : null;
403
- const sourceChainIcon = sourceChainId ? getChainIcon(sourceChainId) : void 0;
404
- const targetChainIcon = targetChainId ? getChainIcon(targetChainId) : void 0;
405
- const sourceSymbol = resolveTokenSymbol(deposit.token, sourceChainId);
406
- const rawAmount = deposit.sourceAmount && deposit.sourceAmount !== "0" ? deposit.sourceAmount : deposit.amount;
407
- const formattedAmount = rawAmount ? formatAmount(rawAmount, deposit.token, sourceChainId) : null;
408
- const displayAmount = formattedAmount ? sourceSymbol && /^USD/.test(sourceSymbol) ? `$${Number(formattedAmount).toLocaleString("en-US", {
409
- minimumFractionDigits: 2,
410
- maximumFractionDigits: 2
411
- })}` : `${formattedAmount} ${sourceSymbol}` : "\u2014";
412
- const date = deposit.createdAt ? formatDate(deposit.createdAt) : null;
413
- const txTypeLabel = `Deposit${sourceSymbol ? ` ${sourceSymbol}` : ""}`;
414
- const srcTxHash = sourceChainId === "hypercore" ? deposit.txHash : deposit.sourceTxHash;
415
- const srcTxUrl = srcTxHash && sourceChainId ? explorerTxUrl(sourceChainId, srcTxHash) : null;
416
- const eligible = toRecoverableDeposit(deposit, { recipient });
417
- const recoverable = onRecover ? eligible : null;
418
- const toggleExpanded = () => {
419
- const next = !expanded;
420
- setExpanded(next);
421
- const depositId = deposit.id ?? deposit.txHash;
422
- if (!next || !depositId) return;
423
- onRowView?.({
424
- depositId,
425
- status,
426
- recoverable: eligible !== null,
427
- position
428
- });
429
- };
430
- return /* @__PURE__ */ jsxs2(
431
- "div",
432
- {
433
- className: `rs-history-card ${expanded ? "rs-history-card--expanded" : ""}`,
434
- children: [
435
- /* @__PURE__ */ jsx2(
436
- "button",
437
- {
438
- type: "button",
439
- className: "rs-history-card-trigger",
440
- onClick: toggleExpanded,
441
- "aria-expanded": expanded,
442
- "aria-controls": panelId,
443
- children: /* @__PURE__ */ jsxs2("div", { className: "rs-history-card-row", children: [
444
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-card-icons", children: [
445
- /* @__PURE__ */ jsx2("span", { className: "rs-history-card-token", children: /* @__PURE__ */ jsx2(
446
- TokenIcon,
447
- {
448
- symbol: sourceSymbol,
449
- fallback: sourceSymbol.slice(0, 2)
450
- }
451
- ) }),
452
- sourceChainIcon && /* @__PURE__ */ jsx2("span", { className: "rs-history-card-chain", children: /* @__PURE__ */ jsx2(CdnIcon, { src: sourceChainIcon }) })
453
- ] }),
454
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-card-text", children: [
455
- /* @__PURE__ */ jsx2("span", { className: "rs-history-card-title", children: txTypeLabel }),
456
- date && /* @__PURE__ */ jsx2("span", { className: "rs-history-card-date", children: date })
457
- ] }),
458
- /* @__PURE__ */ jsxs2("div", { className: "rs-history-card-side", children: [
459
- /* @__PURE__ */ jsx2("span", { className: "rs-history-card-amount", children: displayAmount }),
460
- /* @__PURE__ */ jsx2("span", { className: `rs-history-badge rs-history-badge--${status}`, children: STATUS_LABEL[status] })
461
- ] })
462
- ] })
463
- }
464
- ),
465
- /* @__PURE__ */ jsx2("div", { className: "rs-history-card-panel", id: panelId, children: /* @__PURE__ */ jsx2("div", { className: "rs-history-card-panel-inner", children: /* @__PURE__ */ jsxs2("div", { className: "rs-history-card-details", children: [
466
- (status === "failed" || recoverable) && /* @__PURE__ */ jsx2(Callout, { variant: "error", children: recoveryFailureCause(sourceChainName) }),
467
- sourceChainName && /* @__PURE__ */ jsxs2("div", { className: "rs-amount-detail-row", children: [
468
- /* @__PURE__ */ jsx2("span", { children: "Source chain" }),
469
- /* @__PURE__ */ jsxs2("span", { className: "rs-amount-detail-value", children: [
470
- /* @__PURE__ */ jsx2("span", { children: sourceChainName }),
471
- sourceChainIcon && /* @__PURE__ */ jsx2("span", { className: "rs-amount-detail-icon", children: /* @__PURE__ */ jsx2(CdnIcon, { src: sourceChainIcon }) })
472
- ] })
473
- ] }),
474
- targetChainName && /* @__PURE__ */ jsxs2("div", { className: "rs-amount-detail-row", children: [
475
- /* @__PURE__ */ jsx2("span", { children: "Destination chain" }),
476
- /* @__PURE__ */ jsxs2("span", { className: "rs-amount-detail-value", children: [
477
- /* @__PURE__ */ jsx2("span", { children: targetChainName }),
478
- targetChainIcon && /* @__PURE__ */ jsx2("span", { className: "rs-amount-detail-icon", children: /* @__PURE__ */ jsx2(CdnIcon, { src: targetChainIcon }) })
479
- ] })
480
- ] }),
481
- srcTxUrl && srcTxHash && /* @__PURE__ */ jsxs2("div", { className: "rs-amount-detail-row", children: [
482
- /* @__PURE__ */ jsx2("span", { children: "Transaction details" }),
483
- /* @__PURE__ */ jsxs2(
484
- "a",
485
- {
486
- href: srcTxUrl,
487
- target: "_blank",
488
- rel: "noopener noreferrer",
489
- className: "rs-history-card-link",
490
- children: [
491
- /* @__PURE__ */ jsx2("span", { children: shortenHash(srcTxHash) }),
492
- /* @__PURE__ */ jsx2(ArrowUpRightIcon, {})
493
- ]
494
- }
495
- )
496
- ] }),
497
- recoverable && /* @__PURE__ */ jsxs2(
498
- Button,
499
- {
500
- onClick: () => onRecover?.(recoverable),
501
- fullWidth: true,
502
- className: "rs-history-card-recover",
503
- children: [
504
- /* @__PURE__ */ jsxs2("span", { children: [
505
- "Recover ",
506
- displayAmount
507
- ] }),
508
- /* @__PURE__ */ jsx2(ChevronRightIcon, {})
509
- ]
510
- }
511
- )
512
- ] }) }) })
513
- ]
514
- }
515
- );
516
- }
517
- DepositHistoryPanel.displayName = "DepositHistoryPanel";
518
-
519
- // src/components/history/HistoryFallbackFlow.tsx
520
- import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
521
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
522
- function HistoryFallbackFlow({
523
- service,
524
- recipient,
525
- onBack,
526
- onCloseModal,
527
- onFound
528
- }) {
529
- const analytics = useClaimAnalyticsController();
530
- const [loading, setLoading] = useState2(false);
531
- const [error, setError] = useState2(null);
532
- const [matches, setMatches] = useState2(null);
533
- useEffect2(() => {
534
- analytics.openStep("lookup");
535
- }, [analytics]);
536
- const epochRef = useRef2(0);
537
- useEffect2(() => {
538
- setMatches(null);
539
- setError(null);
540
- setLoading(false);
541
- return () => {
542
- epochRef.current += 1;
543
- };
544
- }, [recipient]);
545
- const reportSelection = useCallback2(
546
- (match, autoSelected) => {
547
- analytics.selectDeposit({
548
- depositId: match.depositId,
549
- transactionHash: match.sourceTxHash ?? null,
550
- autoSelected
551
- });
552
- },
553
- [analytics]
554
- );
555
- const lookup = useCallback2(
556
- async (txHash) => {
557
- const epoch = epochRef.current;
558
- const isCurrentLookup = () => epochRef.current === epoch;
559
- setError(null);
560
- setLoading(true);
561
- analytics.rearmStep("lookup");
562
- try {
563
- const rows = await service.lookupRecoverableDeposits({ txHash });
564
- if (!isCurrentLookup()) return;
565
- const outcome = classifyRecoveryLookup(newestFirst(rows), {
566
- recipient
567
- });
568
- const { eligible, miss } = outcome;
569
- analytics.lookupResult(outcome.rows.length, eligible.length);
570
- if (miss) {
571
- analytics.friction(
572
- miss === "not_found" ? "lookup_no_deposits_found" : "lookup_no_eligible_deposits",
573
- "lookup"
574
- );
575
- setError(RECOVERY_LOOKUP_MISS_COPY[miss]);
576
- return;
577
- }
578
- analytics.completeStep("lookup");
579
- if (eligible.length === 1) {
580
- reportSelection(eligible[0], true);
581
- onFound(eligible[0]);
582
- return;
583
- }
584
- analytics.openStep("select");
585
- setMatches(eligible);
586
- } catch (err) {
587
- if (!isCurrentLookup()) return;
588
- analytics.failure("lookup_failed", true, "lookup");
589
- setError(
590
- formatUserError(err instanceof Error ? err.message : "Lookup failed")
591
- );
592
- } finally {
593
- if (isCurrentLookup()) setLoading(false);
594
- }
595
- },
596
- [analytics, service, recipient, onFound, reportSelection]
597
- );
598
- const selectMatch = useCallback2(
599
- (match) => {
600
- analytics.completeStep("select");
601
- reportSelection(match, false);
602
- onFound(match);
603
- },
604
- [analytics, onFound, reportSelection]
605
- );
606
- const handleKeyDown = useCallback2(
607
- (e) => {
608
- if (e.key === "Escape") {
609
- e.stopPropagation();
610
- onBack("escape");
611
- }
612
- },
613
- [onBack]
614
- );
615
- return /* @__PURE__ */ jsxs3(
616
- "div",
617
- {
618
- className: "rs-history-lookup",
619
- role: "dialog",
620
- "aria-label": "Find a deposit",
621
- onKeyDown: handleKeyDown,
622
- children: [
623
- /* @__PURE__ */ jsxs3("div", { className: "rs-modal-header--redesigned", children: [
624
- /* @__PURE__ */ jsx3("div", { className: "rs-modal-header-nav-left", children: /* @__PURE__ */ jsxs3(
625
- "button",
626
- {
627
- type: "button",
628
- className: "rs-modal-header-back rs-modal-header-back--labelled",
629
- "aria-label": "Back to history",
630
- onClick: () => onBack(),
631
- children: [
632
- /* @__PURE__ */ jsx3(ChevronLeftIcon, {}),
633
- /* @__PURE__ */ jsx3("span", { children: "History" })
634
- ]
635
- }
636
- ) }),
637
- /* @__PURE__ */ jsx3("div", { className: "rs-modal-header-nav-right", children: /* @__PURE__ */ jsx3(
638
- "button",
639
- {
640
- type: "button",
641
- className: "rs-modal-close",
642
- "aria-label": "Close",
643
- onClick: onCloseModal,
644
- children: /* @__PURE__ */ jsx3(CloseIcon, {})
645
- }
646
- ) })
647
- ] }),
648
- matches ? /* @__PURE__ */ jsxs3("div", { className: "rs-screen", children: [
649
- /* @__PURE__ */ jsxs3("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
650
- /* @__PURE__ */ jsx3(
651
- BodyHeader,
652
- {
653
- icon: /* @__PURE__ */ jsx3(CoinsIcon, {}),
654
- title: "Select a deposit",
655
- subtitle: "This transaction produced multiple deposits. Choose the one to recover."
656
- }
657
- ),
658
- /* @__PURE__ */ jsx3("div", { className: "rs-list", children: matches.map((match) => {
659
- const { symbol, amount, chainName } = describeRecoveryDeposit({
660
- chainId: match.sourceChainId,
661
- token: match.token,
662
- amountRaw: match.amountRaw
663
- });
664
- return /* @__PURE__ */ jsx3(
665
- ListRow,
666
- {
667
- leadingMedia: tokenIconUrl(symbol),
668
- title: `${amount} ${symbol}`,
669
- subtitle: chainName,
670
- onClick: () => selectMatch(match)
671
- },
672
- match.depositId
673
- );
674
- }) })
675
- ] }),
676
- /* @__PURE__ */ jsx3(PoweredBy, {})
677
- ] }) : /* @__PURE__ */ jsx3(
678
- RecoveryLookupScreen,
679
- {
680
- title: "Find a deposit",
681
- subtitle: "Some deposits don't appear in your history. Look one up by the transaction that sent the funds.",
682
- hint: "The transaction hash from the wallet or exchange you sent from.",
683
- loading,
684
- error,
685
- onSubmit: lookup,
686
- onInvalid: () => analytics.friction("lookup_transaction_hash_invalid", "lookup")
687
- }
688
- )
689
- ]
690
- }
691
- );
692
- }
693
- function newestFirst(rows) {
694
- return [...rows].sort(
695
- (a, b) => (b.createdAt ? Date.parse(b.createdAt) : 0) - (a.createdAt ? Date.parse(a.createdAt) : 0)
696
- );
697
- }
698
- HistoryFallbackFlow.displayName = "HistoryFallbackFlow";
699
-
700
- // src/components/history/HistoryRecoveryFlow.tsx
701
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
702
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
703
- function HistoryRecoveryFlow({
704
- deposit,
705
- entrySource,
706
- backendUrl,
707
- signRecovery,
708
- debug,
709
- onBack,
710
- onCloseModal,
711
- onSubmittingChange,
712
- onRecovered
713
- }) {
714
- const analytics = useClaimAnalyticsController();
715
- const [phase, setPhase] = useState3("review");
716
- const [outcome, setOutcome] = useState3(null);
717
- const [error, setError] = useState3(null);
718
- const [retryable, setRetryable] = useState3(false);
719
- const lastFailureRef = useRef3(null);
720
- useEffect3(() => {
721
- if (entrySource === "history_row") {
722
- analytics.selectDeposit({
723
- depositId: deposit.depositId,
724
- transactionHash: deposit.sourceTxHash ?? null,
725
- autoSelected: true
726
- });
727
- }
728
- analytics.openStep("review");
729
- }, [analytics, deposit, entrySource]);
730
- const { symbol, decimals } = describeRecoveryDeposit({
731
- chainId: deposit.sourceChainId,
732
- token: deposit.token,
733
- amountRaw: deposit.amountRaw
734
- });
735
- const submit = useCallback3(
736
- async (destination) => {
737
- setPhase("submitting");
738
- onSubmittingChange(true);
739
- analytics.completeStep("review");
740
- analytics.openStep("submit");
741
- try {
742
- const result = await submitSignedRecovery({
743
- backendUrl,
744
- // The target chain: the processor verifies where the recipient wallet
745
- // lives, not where the refund pays out.
746
- chainId: deposit.targetChainId,
747
- depositId: deposit.depositId,
748
- signer: deposit.signer,
749
- destination,
750
- signRecovery,
751
- debug
752
- });
753
- setOutcome({
754
- // No destination to reconcile: it is inside the signed struct, so the
755
- // processor cannot have paid anywhere else.
756
- destination,
757
- txHash: result.transactionHash,
758
- symbol,
759
- amountDisplay: formatRecoveryAmount(
760
- result.amount ?? deposit.amountRaw,
761
- decimals,
762
- symbol
763
- )
764
- });
765
- setPhase("success");
766
- analytics.handoff(
767
- {
768
- depositId: deposit.depositId,
769
- transactionHash: deposit.sourceTxHash ?? null,
770
- refundTransactionHash: result.transactionHash
771
- },
772
- "submit"
773
- );
774
- analytics.completeStep("submit");
775
- analytics.outcome("completed");
776
- onRecovered();
777
- } catch (err) {
778
- const recoveryError = err instanceof RecoveryError ? err : void 0;
779
- setError(
780
- recoveryError?.message ?? formatUserError(
781
- err instanceof Error ? err.message : "Recovery failed"
782
- )
783
- );
784
- const canRetry = recoveryError?.retryable ?? true;
785
- setRetryable(canRetry);
786
- setPhase("failed");
787
- const reason = claimFailureReasonForError(err);
788
- lastFailureRef.current = canRetry ? reason : null;
789
- analytics.failure(reason, canRetry, "submit");
790
- analytics.outcome("failed");
791
- } finally {
792
- onSubmittingChange(false);
793
- }
794
- },
795
- [
796
- analytics,
797
- backendUrl,
798
- debug,
799
- decimals,
800
- deposit,
801
- onRecovered,
802
- onSubmittingChange,
803
- signRecovery,
804
- symbol
805
- ]
806
- );
807
- const retry = useCallback3(() => {
808
- const reason = lastFailureRef.current;
809
- if (reason) {
810
- lastFailureRef.current = null;
811
- analytics.retry(reason, "submit");
812
- }
813
- setError(null);
814
- setPhase("review");
815
- }, [analytics]);
816
- const submitting = phase === "submitting";
817
- return /* @__PURE__ */ jsxs4(
818
- "div",
819
- {
820
- className: "rs-history-recovery",
821
- role: "dialog",
822
- "aria-label": "Recover deposit",
823
- children: [
824
- /* @__PURE__ */ jsxs4("div", { className: "rs-modal-header--redesigned", children: [
825
- /* @__PURE__ */ jsx4("div", { className: "rs-modal-header-nav-left", children: !submitting && /* @__PURE__ */ jsxs4(
826
- "button",
827
- {
828
- type: "button",
829
- className: "rs-modal-header-back rs-modal-header-back--labelled",
830
- "aria-label": "Back to history",
831
- onClick: onBack,
832
- children: [
833
- /* @__PURE__ */ jsx4(ChevronLeftIcon, {}),
834
- /* @__PURE__ */ jsx4("span", { children: "History" })
835
- ]
836
- }
837
- ) }),
838
- /* @__PURE__ */ jsx4("div", { className: "rs-modal-header-nav-right", children: /* @__PURE__ */ jsx4(
839
- "button",
840
- {
841
- type: "button",
842
- className: "rs-modal-close",
843
- "aria-label": "Close",
844
- onClick: onCloseModal,
845
- disabled: submitting,
846
- children: /* @__PURE__ */ jsx4(CloseIcon, {})
847
- }
848
- ) })
849
- ] }),
850
- (phase === "review" || submitting) && /* @__PURE__ */ jsx4(
851
- RecoveryReviewScreen,
852
- {
853
- chainId: deposit.sourceChainId,
854
- token: deposit.token,
855
- amountRaw: deposit.amountRaw,
856
- txHash: deposit.sourceTxHash,
857
- defaultDestination: deposit.destinationPrefill,
858
- destinationHint: "Prefilled with the account this deposit was for. Editable.",
859
- submitting,
860
- onSubmit: submit,
861
- onDestinationInvalid: () => analytics.friction("refund_destination_invalid", "review")
862
- }
863
- ),
864
- phase === "success" && outcome && /* @__PURE__ */ jsx4(
865
- RecoveryOutcomeScreen,
866
- {
867
- result: { ...outcome, chainId: deposit.sourceChainId },
868
- children: /* @__PURE__ */ jsx4(Button, { variant: "outline", onClick: onBack, fullWidth: true, children: "Back to history" })
869
- }
870
- ),
871
- phase === "failed" && /* @__PURE__ */ jsxs4(RecoveryOutcomeScreen, { error, children: [
872
- /* @__PURE__ */ jsx4(Button, { variant: "outline", onClick: onBack, fullWidth: true, children: "Back to history" }),
873
- retryable && /* @__PURE__ */ jsx4(Button, { onClick: retry, fullWidth: true, children: "Try again" })
874
- ] })
875
- ]
876
- }
877
- );
878
- }
879
- HistoryRecoveryFlow.displayName = "HistoryRecoveryFlow";
880
-
881
- // src/components/history/HistoryOverlay.tsx
882
- import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
883
- function HistoryOverlay({
884
- controller,
885
- backLabel,
886
- title,
887
- recipient,
888
- backendUrl,
889
- service,
890
- signRecovery,
891
- debug,
892
- onCloseModal,
893
- analytics,
894
- inline,
895
- closeOnOverlayClick,
896
- enableAnalyticsIngest,
897
- analyticsIngestUrl,
898
- onEvent
899
- }) {
900
- const recovering = signRecovery ? controller.recovering : null;
901
- const [fallbackOpen, setFallbackOpen] = useState4(false);
902
- const [attempt, setAttempt] = useState4(null);
903
- const claimControllerRef = useRef4(null);
904
- const beginAttempt = useCallback4(
905
- (entrySource) => {
906
- const id = randomUuid();
907
- const started = {
908
- id,
909
- entrySource,
910
- correlation: {
911
- entry_source: entrySource,
912
- ...analytics.sourceSessionId ? { source_session_id: analytics.sourceSessionId } : {},
913
- ...analytics.visitId ? { history_visit_id: analytics.visitId } : {},
914
- claim_attempt_id: id
915
- }
916
- };
917
- setAttempt(started);
918
- return started;
919
- },
920
- [analytics]
921
- );
922
- const endAttempt = useCallback4(
923
- (source, reason) => {
924
- const claim = claimControllerRef.current;
925
- claim?.abandon(reason);
926
- claim?.close(source);
927
- claimControllerRef.current = null;
928
- setAttempt(null);
929
- },
930
- []
931
- );
932
- const openFallback = useCallback4(() => {
933
- const started = beginAttempt("history_fallback");
934
- analytics.fallbackOpened(started.id);
935
- setFallbackOpen(true);
936
- }, [analytics, beginAttempt]);
937
- const closeFallback = useCallback4(
938
- (via) => {
939
- endAttempt(via === "escape" ? "escape" : "history_back", "back");
940
- setFallbackOpen(false);
941
- },
942
- [endAttempt]
943
- );
944
- const canLookup = Boolean(signRecovery) && Boolean(recipient);
945
- useEffect4(() => {
946
- if (canLookup) return;
947
- setFallbackOpen(false);
948
- if (fallbackOpen && !recovering) endAttempt("host_controlled", "back");
949
- }, [canLookup, fallbackOpen, recovering, endAttempt]);
950
- const startRecoveryFromRow = useCallback4(
951
- (deposit) => {
952
- const started = beginAttempt("history_row");
953
- analytics.recoveryStarted(started.id, "history_row", deposit.depositId);
954
- controller.startRecovery(deposit);
955
- },
956
- [analytics, beginAttempt, controller]
957
- );
958
- const startRecoveryFromLookup = useCallback4(
959
- (deposit) => {
960
- if (attempt) {
961
- analytics.recoveryStarted(
962
- attempt.id,
963
- attempt.entrySource,
964
- deposit.depositId
965
- );
966
- }
967
- setFallbackOpen(false);
968
- controller.startRecovery(deposit);
969
- },
970
- [analytics, attempt, controller]
971
- );
972
- const cancelRecovery = useCallback4(() => {
973
- endAttempt("history_back", "back");
974
- controller.cancelRecovery();
975
- }, [controller, endAttempt]);
976
- const closeModal = useCallback4(() => {
977
- endAttempt("header_button", "modal_close");
978
- onCloseModal();
979
- }, [endAttempt, onCloseModal]);
980
- useEffect4(() => {
981
- if (!attempt) return;
982
- return () => {
983
- const claim = claimControllerRef.current;
984
- claim?.abandon("modal_close");
985
- claim?.close("host_controlled");
986
- };
987
- }, [attempt]);
988
- const claimSessionProperties = useMemo2(
989
- () => ({
990
- presentation: inline ? "inline" : "overlay",
991
- overlay_close_enabled: !inline && Boolean(closeOnOverlayClick),
992
- prefills: { transaction_hash: false, refund_destination: true }
993
- }),
994
- [inline, closeOnOverlayClick]
995
- );
996
- const layers = /* @__PURE__ */ jsxs5(Fragment, { children: [
997
- fallbackOpen && recipient && !recovering && /* @__PURE__ */ jsx5(
998
- HistoryFallbackFlow,
999
- {
1000
- service,
1001
- recipient,
1002
- onBack: closeFallback,
1003
- onCloseModal: closeModal,
1004
- onFound: startRecoveryFromLookup
1005
- }
1006
- ),
1007
- recovering && signRecovery && /* @__PURE__ */ jsx5(
1008
- HistoryRecoveryFlow,
1009
- {
1010
- deposit: recovering,
1011
- entrySource: attempt?.entrySource ?? "history_row",
1012
- backendUrl,
1013
- signRecovery,
1014
- debug,
1015
- onBack: cancelRecovery,
1016
- onCloseModal: closeModal,
1017
- onSubmittingChange: controller.setRecoverySubmitting,
1018
- onRecovered: controller.markStale
1019
- }
1020
- )
1021
- ] });
1022
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
1023
- /* @__PURE__ */ jsx5(
1024
- DepositHistoryPanel,
1025
- {
1026
- deposits: controller.deposits,
1027
- isLoading: controller.isLoading,
1028
- error: controller.error,
1029
- hasMore: controller.hasMore,
1030
- isLoadingMore: controller.isLoadingMore,
1031
- onLoadMore: controller.loadMore,
1032
- onClose: controller.closePanel,
1033
- onCloseModal: closeModal,
1034
- backLabel,
1035
- title,
1036
- recipient,
1037
- onRecover: signRecovery ? startRecoveryFromRow : void 0,
1038
- onFindByTxHash: canLookup ? openFallback : void 0,
1039
- onRowView: analytics.rowViewed,
1040
- obscured: Boolean(recovering) || fallbackOpen
1041
- }
1042
- ),
1043
- attempt ? /* @__PURE__ */ jsx5(
1044
- AnalyticsSession,
1045
- {
1046
- modal: "claim",
1047
- sessionProperties: claimSessionProperties,
1048
- onEvent,
1049
- backendUrl,
1050
- enableIngest: enableAnalyticsIngest,
1051
- ingestUrl: analyticsIngestUrl,
1052
- children: /* @__PURE__ */ jsx5(
1053
- ClaimAnalyticsControllerProvider,
1054
- {
1055
- controllerRef: claimControllerRef,
1056
- correlation: attempt.correlation,
1057
- children: layers
1058
- }
1059
- )
1060
- },
1061
- attempt.id
1062
- ) : layers
1063
- ] });
1064
- }
1065
-
1066
- // src/components/history/useHistoryPanel.ts
1067
- import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef5, useState as useState5 } from "react";
1068
- function sortByCreatedAtDesc(items) {
1069
- return [...items].sort((a, b) => {
1070
- const at = a.createdAt ? Date.parse(a.createdAt) : 0;
1071
- const bt = b.createdAt ? Date.parse(b.createdAt) : 0;
1072
- return bt - at;
1073
- });
1074
- }
1075
- function useHistoryPanel({
1076
- isOpen,
1077
- recipient,
1078
- service,
1079
- canRecover,
1080
- analytics
1081
- }) {
1082
- const [isPanelOpen, setIsPanelOpen] = useState5(false);
1083
- const [deposits, setDeposits] = useState5([]);
1084
- const [hasMore, setHasMore] = useState5(false);
1085
- const [isLoading, setIsLoading] = useState5(false);
1086
- const [isLoadingMore, setIsLoadingMore] = useState5(false);
1087
- const [error, setError] = useState5(null);
1088
- const cursorRef = useRef5(null);
1089
- const staleRef = useRef5(false);
1090
- const loadedRef = useRef5(false);
1091
- const recipientRef = useRef5(recipient);
1092
- const requestRef = useRef5(0);
1093
- const [recovering, setRecovering] = useState5(null);
1094
- const [recoverySubmitting, setRecoverySubmitting] = useState5(false);
1095
- const fetchHistory = useCallback5(
1096
- async (mode = "initial") => {
1097
- if (!recipient) return;
1098
- const isInitial = mode === "initial";
1099
- const requestId = isInitial ? ++requestRef.current : requestRef.current;
1100
- const isCurrent = () => recipientRef.current === recipient && requestId === requestRef.current;
1101
- if (isInitial) {
1102
- setIsLoading(true);
1103
- } else {
1104
- setIsLoadingMore(true);
1105
- }
1106
- setError(null);
1107
- try {
1108
- const cursor = isInitial ? null : cursorRef.current;
1109
- if (!isInitial && !cursor) {
1110
- setHasMore(false);
1111
- return;
1112
- }
1113
- const result = await service.fetchDepositHistory({
1114
- recipient,
1115
- limit: 20,
1116
- cursor: cursor ?? void 0
1117
- });
1118
- if (!isCurrent()) return;
1119
- const incoming = result.deposits;
1120
- const nextCursor = result.nextCursor ?? null;
1121
- if (isInitial) {
1122
- setDeposits(sortByCreatedAtDesc(incoming));
1123
- } else {
1124
- setDeposits((prev) => sortByCreatedAtDesc([...prev, ...incoming]));
1125
- }
1126
- cursorRef.current = nextCursor;
1127
- setHasMore(Boolean(nextCursor));
1128
- staleRef.current = false;
1129
- loadedRef.current = true;
1130
- } catch (err) {
1131
- if (!isCurrent()) return;
1132
- setError(err instanceof Error ? err.message : "Failed to load history");
1133
- } finally {
1134
- if (isCurrent()) {
1135
- if (isInitial) {
1136
- setIsLoading(false);
1137
- } else {
1138
- setIsLoadingMore(false);
1139
- }
1140
- }
1141
- }
1142
- },
1143
- [recipient, service]
1144
- );
1145
- const badge = useMemo3(
1146
- () => deriveHistoryBadge(deposits, { recipient, canRecover }),
1147
- [deposits, recipient, canRecover]
1148
- );
1149
- const openPanel = useCallback5(() => {
1150
- setIsPanelOpen(true);
1151
- analytics?.visitOpened(badge);
1152
- if (!loadedRef.current || staleRef.current) {
1153
- fetchHistory("initial");
1154
- }
1155
- }, [analytics, badge, fetchHistory]);
1156
- const closePanel = useCallback5(() => {
1157
- if (recovering) return;
1158
- setIsPanelOpen(false);
1159
- analytics?.visitEnded();
1160
- }, [analytics, recovering]);
1161
- const loadMore = useCallback5(() => {
1162
- analytics?.loadMore(filterVisibleDeposits(deposits).length);
1163
- fetchHistory("more");
1164
- }, [analytics, deposits, fetchHistory]);
1165
- const markStale = useCallback5(() => {
1166
- staleRef.current = true;
1167
- if (isOpen) {
1168
- fetchHistory("initial");
1169
- }
1170
- }, [isOpen, fetchHistory]);
1171
- useEffect5(() => {
1172
- if (recipientRef.current === recipient) return;
1173
- recipientRef.current = recipient;
1174
- setDeposits([]);
1175
- setHasMore(false);
1176
- setError(null);
1177
- cursorRef.current = null;
1178
- staleRef.current = false;
1179
- loadedRef.current = false;
1180
- }, [recipient]);
1181
- useEffect5(() => {
1182
- if (!isOpen || !recipient) return;
1183
- if (!loadedRef.current || staleRef.current) {
1184
- fetchHistory("initial");
1185
- }
1186
- }, [isOpen, recipient, fetchHistory]);
1187
- useEffect5(() => {
1188
- if (isOpen) return;
1189
- setIsPanelOpen(false);
1190
- analytics?.visitEnded();
1191
- setDeposits([]);
1192
- setHasMore(false);
1193
- setError(null);
1194
- cursorRef.current = null;
1195
- staleRef.current = false;
1196
- loadedRef.current = false;
1197
- requestRef.current += 1;
1198
- setRecovering(null);
1199
- setRecoverySubmitting(false);
1200
- }, [isOpen, analytics]);
1201
- const invalidate = useCallback5(() => {
1202
- staleRef.current = true;
1203
- }, []);
1204
- const cancelRecovery = useCallback5(() => setRecovering(null), []);
1205
- return useMemo3(
1206
- () => ({
1207
- deposits,
1208
- isLoading,
1209
- isLoadingMore,
1210
- error,
1211
- hasMore,
1212
- badge,
1213
- isPanelOpen,
1214
- openPanel,
1215
- closePanel,
1216
- loadMore,
1217
- markStale,
1218
- invalidate,
1219
- recovering,
1220
- startRecovery: setRecovering,
1221
- cancelRecovery,
1222
- recoverySubmitting,
1223
- setRecoverySubmitting
1224
- }),
1225
- [
1226
- deposits,
1227
- isLoading,
1228
- isLoadingMore,
1229
- error,
1230
- hasMore,
1231
- badge,
1232
- isPanelOpen,
1233
- openPanel,
1234
- closePanel,
1235
- loadMore,
1236
- markStale,
1237
- invalidate,
1238
- recovering,
1239
- cancelRecovery,
1240
- recoverySubmitting
1241
- ]
1242
- );
1243
- }
1244
-
1245
- // src/core/removed-props.ts
1246
- var MANAGED_ACCOUNT_REMOVALS = {
1247
- signerAddress: "removed \u2014 accounts are service-managed, so there is no session signer. See MIGRATION.md.",
1248
- sessionChainIds: "removed \u2014 accounts are service-managed, so there is no smart session. See MIGRATION.md.",
1249
- dappAddress: "removed \u2014 the modal reads the address off `walletClient.account` instead, which cannot disagree with the wallet your app has connected.",
1250
- dappWalletClient: "renamed to `walletClient`.",
1251
- dappPublicClient: "renamed to `publicClient`.",
1252
- allowedRoutes: "removed \u2014 which chains and tokens you accept is the project's `depositWhitelist`, enforced by the processor. Offering a subset means a separate API key.",
1253
- rhinestoneApiKey: "removed \u2014 it was never read. The API key belongs on your backend proxy, which attaches it to the upstream request; the modal runs in the browser."
1254
- };
1255
- var REMOVED_PROPS = {
1256
- DepositModal: {
1257
- ...MANAGED_ACCOUNT_REMOVALS,
1258
- dappImports: "renamed to `assetMigrations`.",
1259
- initialDappImport: "renamed to `initialAssetMigration`.",
1260
- fiatOnrampMethods: "replaced by `fiatMethods`, a boolean map keyed by Swapped payment group (`creditcard`, `bank-transfer`, `apple-pay`). Omit it to offer all of them.",
1261
- enableSolana: "removed \u2014 Solana sources follow the project's `depositWhitelist`."
1262
- },
1263
- WithdrawModal: {
1264
- ...MANAGED_ACCOUNT_REMOVALS,
1265
- safeAddress: "renamed to `accountAddress`, and it need not be a Safe.",
1266
- onSignTransaction: "replaced by `onSendTransaction`, which performs one transfer and returns its hash. The modal no longer submits the transaction itself.",
1267
- reownAppId: "removed \u2014 WithdrawModal never connects a wallet; your app performs the transfer via `onSendTransaction`.",
1268
- onRequestConnect: "removed \u2014 WithdrawModal never connects a wallet."
1269
- }
1270
- };
1271
- var REMOVED_UI_CONFIG_KEYS = {
1272
- checkLiquidity: "removed \u2014 the modal no longer surfaces a liquidity cap. Route capacity is enforced when the deposit is processed.",
1273
- showHistoryButton: "removed \u2014 deposit history is always offered, from the modal's home screen. There is no way to hide it."
1274
- };
1275
- var warned = /* @__PURE__ */ new Set();
1276
- function warnOnce(component, key, replacement) {
1277
- const id = `${component}.${key}`;
1278
- if (warned.has(id)) return;
1279
- warned.add(id);
1280
- console.error(
1281
- `[rhinestone] <${component}> was passed \`${key}\`, which no longer exists: ${replacement}`
1282
- );
1283
- }
1284
- function warnRemovedProps(component, props) {
1285
- const table = REMOVED_PROPS[component];
1286
- if (!table) return;
1287
- for (const key of Object.keys(table)) {
1288
- if (Object.prototype.hasOwnProperty.call(props, key)) {
1289
- warnOnce(component, key, table[key]);
1290
- }
1291
- }
1292
- const uiConfig = props.uiConfig;
1293
- if (uiConfig && typeof uiConfig === "object") {
1294
- for (const key of Object.keys(REMOVED_UI_CONFIG_KEYS)) {
1295
- if (Object.prototype.hasOwnProperty.call(uiConfig, key)) {
1296
- warnOnce(
1297
- component,
1298
- `uiConfig.${key}`,
1299
- REMOVED_UI_CONFIG_KEYS[key]
1300
- );
1301
- }
1302
- }
1303
- }
1304
- }
1305
-
1306
- // src/core/history-entry.ts
1307
- var PROVIDER_SCREENS = /* @__PURE__ */ new Set(["fiat-onramp", "exchange-connect"]);
1308
- function isHistoryEntryVisible(input) {
1309
- if (input.submitting) return false;
1310
- if (input.screen === "processing") return input.settled;
1311
- if (PROVIDER_SCREENS.has(input.screen)) return false;
1312
- return true;
1313
- }
1314
-
1315
- // src/core/history-analytics.ts
1316
- function sinkFor(source) {
1317
- if (source.modal === "deposit") {
1318
- const at2 = () => source.controllerRef.current;
1319
- return {
1320
- opened: (detail) => at2()?.historyOpened({ ...detail, step: at2()?.step ?? null }),
1321
- rowViewed: (detail) => at2()?.historyRowViewed(detail),
1322
- loadMore: (detail) => at2()?.historyLoadMore(detail),
1323
- fallbackOpened: (detail) => at2()?.historyFallbackOpened(detail),
1324
- recoveryStarted: (detail) => at2()?.historyRecoveryStarted(detail)
1325
- };
1326
- }
1327
- const at = () => source.controllerRef.current;
1328
- return {
1329
- opened: (detail) => at()?.historyOpened({ ...detail, step: at()?.step ?? null }),
1330
- rowViewed: (detail) => at()?.historyRowViewed(detail),
1331
- loadMore: (detail) => at()?.historyLoadMore(detail),
1332
- fallbackOpened: (detail) => at()?.historyFallbackOpened(detail),
1333
- recoveryStarted: (detail) => at()?.historyRecoveryStarted(detail)
1334
- };
1335
- }
1336
- function badgeState(badge) {
1337
- if (!badge) return "none";
1338
- return badge.state === "needs-you" ? "needs_you" : "progress";
1339
- }
1340
- function createHistoryAnalytics(source) {
1341
- const sink = sinkFor(source);
1342
- let visitId = null;
1343
- let loadMorePresses = 0;
1344
- let viewedRows = /* @__PURE__ */ new Set();
1345
- return {
1346
- get sourceSessionId() {
1347
- return source.sessionIdRef.current;
1348
- },
1349
- get visitId() {
1350
- return visitId;
1351
- },
1352
- visitOpened(badge) {
1353
- visitId = randomUuid();
1354
- loadMorePresses = 0;
1355
- viewedRows = /* @__PURE__ */ new Set();
1356
- sink.opened({
1357
- history_visit_id: visitId,
1358
- badge: badgeState(badge),
1359
- badge_count: badge?.count ?? 0
1360
- });
1361
- },
1362
- visitEnded() {
1363
- visitId = null;
1364
- },
1365
- rowViewed({ depositId, status, recoverable, position }) {
1366
- if (!visitId || viewedRows.has(depositId)) return;
1367
- viewedRows.add(depositId);
1368
- sink.rowViewed({
1369
- history_visit_id: visitId,
1370
- deposit_id: depositId,
1371
- status,
1372
- recoverable,
1373
- position
1374
- });
1375
- },
1376
- loadMore(loadedRows) {
1377
- if (!visitId) return;
1378
- loadMorePresses += 1;
1379
- sink.loadMore({
1380
- history_visit_id: visitId,
1381
- page: loadMorePresses,
1382
- loaded_rows: loadedRows
1383
- });
1384
- },
1385
- fallbackOpened(claimAttemptId) {
1386
- if (!visitId) return;
1387
- sink.fallbackOpened({
1388
- history_visit_id: visitId,
1389
- claim_attempt_id: claimAttemptId
1390
- });
1391
- },
1392
- recoveryStarted(claimAttemptId, entrySource, depositId) {
1393
- if (!visitId) return;
1394
- sink.recoveryStarted({
1395
- history_visit_id: visitId,
1396
- claim_attempt_id: claimAttemptId,
1397
- entry_source: entrySource,
1398
- deposit_id: depositId
1399
- });
1400
- }
1401
- };
1402
- }
1403
-
1404
- // src/core/analytics-target.ts
1405
- var HEX_TOKEN = /^0x(?:[0-9a-f]{32}|[0-9a-f]{40})$/i;
1406
- var BASE58_TOKEN = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
1407
- function analyticsTargetProperties(input) {
1408
- const properties = {};
1409
- const chain = resolveChain(input.targetChain);
1410
- const token = resolveToken(input.targetToken);
1411
- if (chain) properties.target_chain = chain;
1412
- if (token) properties.target_token = token;
1413
- return properties;
1414
- }
1415
- function resolveChain(targetChain) {
1416
- if (targetChain === void 0) return void 0;
1417
- if (typeof targetChain === "number" && !(targetChain > 0 && Number.isInteger(targetChain))) {
1418
- return void 0;
1419
- }
1420
- return targetChainToCaip2(targetChain);
1421
- }
1422
- function resolveToken(targetToken) {
1423
- const raw = targetToken?.trim();
1424
- if (!raw) return void 0;
1425
- if (HEX_TOKEN.test(raw)) return raw.toLowerCase();
1426
- if (BASE58_TOKEN.test(raw)) return raw;
1427
- return raw.toLowerCase() === "native" ? "native" : void 0;
1428
- }
1429
-
1430
- // src/core/submission-uncertainty.ts
1431
- var MARKER = "__rhinestoneSubmissionUncertain";
1432
- function markSubmissionUncertain(error) {
1433
- Object.defineProperty(error, MARKER, {
1434
- value: true,
1435
- enumerable: false
1436
- });
1437
- return error;
1438
- }
1439
- function isSubmissionUncertain(error) {
1440
- let current = error;
1441
- for (let depth = 0; depth < 10; depth += 1) {
1442
- if (typeof current !== "object" || current === null) return false;
1443
- if (current[MARKER] === true) return true;
1444
- current = current.cause;
1445
- }
1446
- return false;
1447
- }
1448
-
1449
- // src/components/steps/ProcessingStep.tsx
1450
- import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef7, useState as useState9 } from "react";
1451
-
1452
- // src/components/ui/Tooltip.tsx
1453
- import {
1454
- useState as useState6,
1455
- useRef as useRef6,
1456
- useEffect as useEffect6,
1457
- useCallback as useCallback6
1458
- } from "react";
1459
- import { createPortal } from "react-dom";
1460
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1461
- function Tooltip({ content, children, className }) {
1462
- const [open, setOpen] = useState6(false);
1463
- const [position, setPosition] = useState6(null);
1464
- const triggerRef = useRef6(null);
1465
- const bubbleRef = useRef6(null);
1466
- const updatePosition = useCallback6(() => {
1467
- const trigger = triggerRef.current;
1468
- if (!trigger) return;
1469
- const rect = trigger.getBoundingClientRect();
1470
- setPosition({
1471
- top: rect.top,
1472
- left: rect.left + rect.width / 2
1473
- });
1474
- }, []);
1475
- useEffect6(() => {
1476
- if (!open) return;
1477
- updatePosition();
1478
- function handleOutside(event) {
1479
- const target = event.target;
1480
- if (!target) return;
1481
- if (triggerRef.current?.contains(target)) return;
1482
- if (bubbleRef.current?.contains(target)) return;
1483
- setOpen(false);
1484
- }
1485
- function handleKey(event) {
1486
- if (event.key === "Escape") setOpen(false);
1487
- }
1488
- document.addEventListener("mousedown", handleOutside);
1489
- document.addEventListener("touchstart", handleOutside, { passive: true });
1490
- document.addEventListener("keydown", handleKey);
1491
- window.addEventListener("scroll", updatePosition, true);
1492
- window.addEventListener("resize", updatePosition);
1493
- return () => {
1494
- document.removeEventListener("mousedown", handleOutside);
1495
- document.removeEventListener("touchstart", handleOutside);
1496
- document.removeEventListener("keydown", handleKey);
1497
- window.removeEventListener("scroll", updatePosition, true);
1498
- window.removeEventListener("resize", updatePosition);
1499
- };
1500
- }, [open, updatePosition]);
1501
- return /* @__PURE__ */ jsxs6(
1502
- "span",
1503
- {
1504
- ref: triggerRef,
1505
- className: `rs-tooltip ${className ?? ""}`,
1506
- onMouseEnter: () => setOpen(true),
1507
- onMouseLeave: () => setOpen(false),
1508
- children: [
1509
- /* @__PURE__ */ jsx6(
1510
- "span",
1511
- {
1512
- className: "rs-tooltip-trigger",
1513
- role: "button",
1514
- tabIndex: 0,
1515
- "aria-label": content,
1516
- onClick: (event) => {
1517
- event.stopPropagation();
1518
- event.preventDefault();
1519
- setOpen((value) => !value);
1520
- },
1521
- onKeyDown: (event) => {
1522
- if (event.key === "Enter" || event.key === " ") {
1523
- event.preventDefault();
1524
- setOpen((value) => !value);
1525
- }
1526
- },
1527
- children
1528
- }
1529
- ),
1530
- open && position && typeof document !== "undefined" && createPortal(
1531
- /* @__PURE__ */ jsx6(
1532
- "span",
1533
- {
1534
- ref: bubbleRef,
1535
- className: "rs-tooltip-bubble",
1536
- role: "tooltip",
1537
- style: { top: position.top, left: position.left },
1538
- children: content
1539
- }
1540
- ),
1541
- document.body
1542
- )
1543
- ]
1544
- }
1545
- );
1546
- }
1547
- Tooltip.displayName = "Tooltip";
1548
-
1549
- // src/components/ui/FeesAccordion.tsx
1550
- import { useState as useState7 } from "react";
1551
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1552
- function FeesAccordion({ total, rows, tooltip }) {
1553
- const [open, setOpen] = useState7(false);
1554
- const expandable = rows.length > 0;
1555
- return /* @__PURE__ */ jsxs7("div", { className: "rs-fees-accordion", children: [
1556
- /* @__PURE__ */ jsxs7("div", { className: "rs-fees-accordion-summary", children: [
1557
- /* @__PURE__ */ jsxs7("span", { className: "rs-fees-accordion-label", children: [
1558
- /* @__PURE__ */ jsx7("span", { children: "Fees" }),
1559
- tooltip && /* @__PURE__ */ jsx7(Tooltip, { content: tooltip, children: /* @__PURE__ */ jsx7("span", { className: "rs-review-detail-info", "aria-label": "Fees info", children: /* @__PURE__ */ jsx7(InfoIcon, {}) }) })
1560
- ] }),
1561
- /* @__PURE__ */ jsxs7(
1562
- "button",
1563
- {
1564
- type: "button",
1565
- className: "rs-fees-accordion-toggle",
1566
- "aria-label": "Fees",
1567
- "aria-expanded": expandable ? open : void 0,
1568
- disabled: !expandable,
1569
- onClick: () => expandable && setOpen((o) => !o),
1570
- children: [
1571
- /* @__PURE__ */ jsx7("span", { children: total }),
1572
- expandable && /* @__PURE__ */ jsx7(
1573
- "span",
1574
- {
1575
- className: `rs-fees-accordion-chevron${open ? " rs-fees-accordion-chevron--open" : ""}`,
1576
- "aria-hidden": "true",
1577
- children: /* @__PURE__ */ jsx7(ChevronDownIcon, {})
1578
- }
1579
- )
1580
- ]
1581
- }
1582
- )
1583
- ] }),
1584
- expandable && open && /* @__PURE__ */ jsx7("div", { className: "rs-fees-accordion-rows", children: rows.map((row, i) => /* @__PURE__ */ jsxs7("div", { className: "rs-fees-accordion-row", children: [
1585
- /* @__PURE__ */ jsx7("span", { children: row.label }),
1586
- /* @__PURE__ */ jsxs7("span", { className: "rs-review-detail-value", children: [
1587
- /* @__PURE__ */ jsx7(
1588
- "span",
1589
- {
1590
- className: row.strike ? "rs-review-detail-value--strike" : void 0,
1591
- children: row.value
1592
- }
1593
- ),
1594
- row.tooltip && /* @__PURE__ */ jsx7(Tooltip, { content: row.tooltip, children: /* @__PURE__ */ jsx7(
1595
- "span",
1596
- {
1597
- className: "rs-review-detail-info",
1598
- "aria-label": `${row.label} info`,
1599
- children: /* @__PURE__ */ jsx7(InfoIcon, {})
1600
- }
1601
- ) })
1602
- ] })
1603
- ] }, `${row.label}-${i}`)) })
1604
- ] });
1605
- }
1606
- FeesAccordion.displayName = "FeesAccordion";
1607
-
1608
- // src/components/steps/SwappedReceipt.tsx
1609
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1610
- function SwappedReceipt({
1611
- onrampMethod,
1612
- onrampMethodIcon,
1613
- amountPaid,
1614
- depositedAmount,
1615
- depositedSymbol,
1616
- feesTotal,
1617
- feeRows,
1618
- feesTooltip,
1619
- newLabel,
1620
- onNewDeposit,
1621
- onClose
1622
- }) {
1623
- return /* @__PURE__ */ jsxs8("div", { className: "rs-screen", children: [
1624
- /* @__PURE__ */ jsxs8("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
1625
- /* @__PURE__ */ jsx8(BodyHeader, { icon: /* @__PURE__ */ jsx8(CheckIcon, {}), title: "Deposit successful" }),
1626
- /* @__PURE__ */ jsxs8("div", { className: "rs-review-details", children: [
1627
- onrampMethod && /* @__PURE__ */ jsxs8("div", { className: "rs-review-detail-row", children: [
1628
- /* @__PURE__ */ jsx8("span", { children: "Onramp method" }),
1629
- /* @__PURE__ */ jsxs8("span", { className: "rs-review-detail-value", children: [
1630
- /* @__PURE__ */ jsx8("span", { children: onrampMethod }),
1631
- onrampMethodIcon && /* @__PURE__ */ jsx8("span", { className: "rs-review-detail-icon rs-review-detail-icon--square", children: /* @__PURE__ */ jsx8(CdnIcon, { src: onrampMethodIcon }) })
1632
- ] })
1633
- ] }),
1634
- amountPaid && /* @__PURE__ */ jsxs8("div", { className: "rs-review-detail-row", children: [
1635
- /* @__PURE__ */ jsx8("span", { children: "Amount paid" }),
1636
- /* @__PURE__ */ jsx8("span", { className: "rs-review-detail-value", children: amountPaid })
1637
- ] }),
1638
- /* @__PURE__ */ jsxs8("div", { className: "rs-review-detail-row", children: [
1639
- /* @__PURE__ */ jsx8("span", { children: "Deposited amount" }),
1640
- /* @__PURE__ */ jsxs8("span", { className: "rs-review-detail-value", children: [
1641
- /* @__PURE__ */ jsx8("span", { children: depositedAmount }),
1642
- depositedSymbol && /* @__PURE__ */ jsx8("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx8(TokenIcon, { symbol: depositedSymbol }) })
1643
- ] })
1644
- ] }),
1645
- feesTotal != null && /* @__PURE__ */ jsx8(
1646
- FeesAccordion,
1647
- {
1648
- total: feesTotal,
1649
- rows: feeRows ?? [],
1650
- tooltip: feesTooltip ?? void 0
1651
- }
1652
- )
1653
- ] }),
1654
- /* @__PURE__ */ jsxs8("div", { className: "rs-screen-button-row", children: [
1655
- onNewDeposit && /* @__PURE__ */ jsx8(Button, { variant: "outline", onClick: onNewDeposit, fullWidth: true, children: newLabel }),
1656
- onClose && /* @__PURE__ */ jsx8(Button, { onClick: onClose, fullWidth: true, children: "Done" })
1657
- ] })
1658
- ] }),
1659
- /* @__PURE__ */ jsx8(PoweredBy, {})
1660
- ] });
1661
- }
1662
- SwappedReceipt.displayName = "SwappedReceipt";
1663
-
1664
- // src/components/ui/ExchangeLogos.tsx
1665
- import { jsx as jsx9 } from "react/jsx-runtime";
1666
- var EXCHANGE_SLUGS = [
1667
- "binance",
1668
- "bitfinex",
1669
- "btcturk",
1670
- "bybit",
1671
- "coinbase",
1672
- "gateio",
1673
- "htx",
1674
- "kraken",
1675
- "kucoin"
1676
- ];
1677
- var EXCHANGE_LOGO_SOURCES = Object.fromEntries(
1678
- EXCHANGE_SLUGS.map((slug) => [slug, cdnIconUrl("exchanges", slug)])
1679
- );
1680
- var EXCHANGE_LOGO_ALIASES = {
1681
- gate: "gateio"
1682
- };
1683
- function normalize(value) {
1684
- return value.toLowerCase().replace(/[^a-z0-9]/g, "");
1685
- }
1686
- function getLogoSource(value) {
1687
- const key = normalize(value);
1688
- return EXCHANGE_LOGO_SOURCES[EXCHANGE_LOGO_ALIASES[key] ?? key] ?? null;
1689
- }
1690
- function getExchangeLogoSrc(name, connection) {
1691
- return getLogoSource(name) ?? getLogoSource(connection ?? "");
1692
- }
1693
- function getExchangeLogo(name, connection) {
1694
- const src = getExchangeLogoSrc(name, connection);
1695
- return src ? /* @__PURE__ */ jsx9(CdnIcon, { src }) : null;
1696
- }
1697
-
1698
- // src/core/webhook.ts
1699
- function isRecord(value) {
1700
- return typeof value === "object" && value !== null;
1701
- }
1702
- function asString(value) {
1703
- return typeof value === "string" ? value : void 0;
1704
- }
1705
- function asNumber(value) {
1706
- if (typeof value === "number" && Number.isFinite(value)) return value;
1707
- if (typeof value !== "string") return void 0;
1708
- const trimmed = value.trim();
1709
- if (!trimmed) return void 0;
1710
- const caipMatch = trimmed.match(/^eip155:(\d+)$/);
1711
- if (caipMatch?.[1]) {
1712
- const parsed2 = Number(caipMatch[1]);
1713
- return Number.isFinite(parsed2) ? parsed2 : void 0;
1714
- }
1715
- const parsed = Number(trimmed);
1716
- return Number.isFinite(parsed) ? parsed : void 0;
1717
- }
1718
- function asAmount(value) {
1719
- if (typeof value === "string") return value;
1720
- if (typeof value === "number" && Number.isFinite(value)) {
1721
- return value.toString();
1722
- }
1723
- return void 0;
1724
- }
1725
- function asAddress(value) {
1726
- if (typeof value !== "string") return void 0;
1727
- return /^0x[a-fA-F0-9]{40}$/.test(value) ? value : void 0;
1728
- }
1729
- function getEventTxHash(event) {
1730
- if (!event?.type) return void 0;
1731
- if (event.type === "deposit-received") {
1732
- return asString(event.data?.transactionHash);
1733
- }
1734
- if (event.type === "bridge-started" || event.type === "bridge-complete") {
1735
- const deposit = isRecord(event.data?.deposit) ? event.data.deposit : void 0;
1736
- const source = isRecord(event.data?.source) ? event.data.source : void 0;
1737
- return asString(deposit?.transactionHash) ?? asString(source?.transactionHash);
1738
- }
1739
- if (event.type === "bridge-failed" || event.type === "error") {
1740
- const deposit = isRecord(event.data?.deposit) ? event.data.deposit : void 0;
1741
- const source = isRecord(event.data?.source) ? event.data.source : void 0;
1742
- return asString(deposit?.transactionHash) ?? asString(source?.transactionHash);
1743
- }
1744
- return void 0;
1745
- }
1746
- function getEventSourceDetails(event) {
1747
- if (!event?.type || !isRecord(event.data)) return {};
1748
- if (event.type === "deposit-received") {
1749
- return {
1750
- chainId: asNumber(event.data.chain),
1751
- amount: asAmount(event.data.amount),
1752
- token: asAddress(event.data.token)
1753
- };
1754
- }
1755
- const source = isRecord(event.data.source) ? event.data.source : void 0;
1756
- const deposit = isRecord(event.data.deposit) ? event.data.deposit : void 0;
1757
- if (event.type === "bridge-started" || event.type === "bridge-complete" || event.type === "bridge-failed" || event.type === "error") {
1758
- return {
1759
- chainId: asNumber(source?.chain) ?? asNumber(deposit?.chain),
1760
- amount: asAmount(source?.amount) ?? asAmount(deposit?.amount),
1761
- token: asAddress(source?.asset) ?? asAddress(deposit?.asset) ?? asAddress(deposit?.token)
1762
- };
1763
- }
1764
- return {};
1765
- }
1766
- function asChain(value) {
1767
- if (typeof value === "string") {
1768
- const trimmed = value.trim();
1769
- if (trimmed.startsWith("solana")) return "solana";
1770
- if (trimmed.startsWith("hypercore")) return "hypercore";
1771
- }
1772
- return asNumber(value);
1773
- }
1774
- function getEventDestinationDetails(event) {
1775
- if (!event?.type || !isRecord(event.data)) return {};
1776
- if (event.type !== "bridge-started" && event.type !== "bridge-complete") {
1777
- return {};
1778
- }
1779
- const destination = isRecord(event.data.destination) ? event.data.destination : void 0;
1780
- if (!destination) return {};
1781
- const token = asString(destination.asset);
1782
- return {
1783
- chainId: asChain(destination.chain),
1784
- amount: asAmount(destination.amount),
1785
- token: token?.trim() || void 0
1786
- };
1787
- }
1788
- function isDepositEvent(event) {
1789
- return event?.type === "deposit-received" || event?.type === "bridge-started" || event?.type === "bridge-complete" || event?.type === "bridge-failed" || event?.type === "error";
1790
- }
1791
- function isFailedEvent(event) {
1792
- return event?.type === "bridge-failed" || event?.type === "error";
1793
- }
1794
- function isHexString(value) {
1795
- return value.startsWith("0x") || value.startsWith("0X");
1796
- }
1797
- function txRefsMatch(a, b) {
1798
- if (isHexString(a) && isHexString(b)) {
1799
- return a.toLowerCase() === b.toLowerCase();
1800
- }
1801
- return a === b;
1802
- }
1803
- function formatBridgeFailedMessage(event) {
1804
- const eventData = event?.data ?? {};
1805
- const code = typeof eventData.errorCode === "string" ? eventData.errorCode : void 0;
1806
- const backendMessage = typeof eventData.message === "string" ? eventData.message.trim() : "";
1807
- function toUserFacingFailure(raw) {
1808
- const lower = raw.toLowerCase();
1809
- if (lower.includes("insufficient funds")) {
1810
- return "Deposit was received, but processing could not continue due to insufficient funds. Please retry.";
1811
- }
1812
- if (lower.includes("no valid quote available")) {
1813
- return "No bridge route is currently available for this transfer. Please try again shortly.";
1814
- }
1815
- if (lower.includes("simulation failed")) {
1816
- return "Transfer processing failed during simulation. Please retry.";
1817
- }
1818
- if (raw.length > 220) {
1819
- return "Transfer processing failed. Please retry.";
1820
- }
1821
- return raw;
1822
- }
1823
- if (backendMessage.length > 0) {
1824
- return { message: toUserFacingFailure(backendMessage), code };
1825
- }
1826
- if (code) {
1827
- return { message: `Bridge failed (${code})`, code };
1828
- }
1829
- return { message: "Bridge failed" };
1830
- }
1831
- function failureMessageForEvent(event) {
1832
- if (event?.type === "error") {
1833
- const message = isRecord(event.data) ? asString(event.data.message) : void 0;
1834
- return message ?? "Unknown error";
1835
- }
1836
- return formatBridgeFailedMessage(event).message;
1837
- }
1838
-
1839
- // src/core/useTokenPrices.ts
1840
- import { useEffect as useEffect7, useState as useState8 } from "react";
1841
- var PRICE_SERVICE_NATIVE = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
1842
- var WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112";
1843
- var SOL_SYSTEM_PROGRAM = "11111111111111111111111111111111";
1844
- function isSolanaNativeAddress(address) {
1845
- if (!address) return true;
1846
- const lower = address.toLowerCase();
1847
- return lower === "native" || lower === SOL_SYSTEM_PROGRAM;
1848
- }
1849
- function isNativeAddress(address) {
1850
- if (!address) return true;
1851
- return address.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase();
1852
- }
1853
- function caip2For(token) {
1854
- if (token.chainId === "solana") {
1855
- const mint = isSolanaNativeAddress(token.address) ? WRAPPED_SOL_MINT : token.address;
1856
- return mint ? `${SOLANA_MAINNET_CAIP2}:${mint}` : void 0;
1857
- }
1858
- if (typeof token.chainId !== "number") return void 0;
1859
- const address = isNativeAddress(token.address) ? PRICE_SERVICE_NATIVE : token.address;
1860
- return `${toEvmCaip2(token.chainId)}:${address.toLowerCase()}`;
1861
- }
1862
- async function fetchTokenPriceUsd(service, token) {
1863
- const caip2 = caip2For(token);
1864
- if (!caip2) return null;
1865
- const prices = await service.fetchPricesByAddress([caip2]);
1866
- const price = prices[caip2];
1867
- return typeof price === "number" && price > 0 ? price : null;
1868
- }
1869
- function useTokenPrices(service, tokens) {
1870
- const [prices, setPrices] = useState8({});
1871
- const defined = tokens.filter((t) => Boolean(t?.symbol));
1872
- const entries = [
1873
- ...new Map(
1874
- defined.flatMap((token) => {
1875
- const caip2 = caip2For(token);
1876
- return caip2 ? [{ symbol: token.symbol.toUpperCase(), caip2 }] : [];
1877
- }).map((entry) => [entry.caip2, entry])
1878
- ).values()
1879
- ].sort((a, b) => a.caip2.localeCompare(b.caip2));
1880
- const key = JSON.stringify(entries);
1881
- useEffect7(() => {
1882
- if (key === "[]") return;
1883
- let cancelled = false;
1884
- const parsed = JSON.parse(key);
1885
- const caip2Ids = [...new Set(parsed.map((e) => e.caip2))];
1886
- service.fetchPricesByAddress(caip2Ids).then((byAddress) => {
1887
- if (cancelled) return;
1888
- const merged = {};
1889
- for (const entry of parsed) {
1890
- const price = byAddress[entry.caip2];
1891
- if (typeof price === "number") merged[entry.symbol] = price;
1892
- }
1893
- setPrices(merged);
1894
- }).catch(() => {
1895
- });
1896
- return () => {
1897
- cancelled = true;
1898
- };
1899
- }, [service, key]);
1900
- return prices;
1901
- }
1902
-
1903
- // src/components/steps/ProcessingStep.tsx
1904
- import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1905
- function SuccessBadge() {
1906
- return /* @__PURE__ */ jsxs9(
1907
- "svg",
1908
- {
1909
- width: "40",
1910
- height: "40",
1911
- viewBox: "0 0 40 40",
1912
- fill: "none",
1913
- xmlns: "http://www.w3.org/2000/svg",
1914
- "aria-hidden": "true",
1915
- children: [
1916
- /* @__PURE__ */ jsx10(
1917
- "rect",
1918
- {
1919
- width: "40",
1920
- height: "40",
1921
- rx: "8",
1922
- fill: "var(--rs-icon-wrapper-bg)"
1923
- }
1924
- ),
1925
- /* @__PURE__ */ jsx10(
1926
- "path",
1927
- {
1928
- d: "M28 14L17 25L12 20",
1929
- stroke: "var(--rs-icon-wrapper-icon)",
1930
- strokeWidth: "2",
1931
- strokeLinecap: "round",
1932
- strokeLinejoin: "round"
1933
- }
1934
- )
1935
- ]
1936
- }
1937
- );
1938
- }
1939
- function FailedBadge() {
1940
- return /* @__PURE__ */ jsxs9(
1941
- "svg",
1942
- {
1943
- width: "40",
1944
- height: "40",
1945
- viewBox: "0 0 40 40",
1946
- fill: "none",
1947
- xmlns: "http://www.w3.org/2000/svg",
1948
- "aria-hidden": "true",
1949
- children: [
1950
- /* @__PURE__ */ jsx10("rect", { width: "40", height: "40", rx: "8", fill: "#FB2C36" }),
1951
- /* @__PURE__ */ jsx10(
1952
- "path",
1953
- {
1954
- d: "M26 14L14 26M14 14L26 26",
1955
- stroke: "white",
1956
- strokeWidth: "2",
1957
- strokeLinecap: "round",
1958
- strokeLinejoin: "round"
1959
- }
1960
- )
1961
- ]
1962
- }
1963
- );
1964
- }
1965
- var INITIAL_POLL_INTERVAL = 3e3;
1966
- var MAX_POLL_INTERVAL = 3e4;
1967
- var BACKOFF_MULTIPLIER = 1.5;
1968
- var SWAPPED_RECEIPT_STATUS_RETRY_INTERVAL_MS = 1e3;
1969
- var SWAPPED_FIAT_STATUS_MAX_ATTEMPTS = 12;
1970
- var ESCALATED_DELAY_MS = 10 * 60 * 1e3;
1971
- var SOFT_DELAY_MS = {
1972
- confirming: 90 * 1e3,
1973
- received: 90 * 1e3,
1974
- bridging: 4 * 60 * 1e3
1975
- };
1976
- var PHASE_TIMINGS_PREFIX = "rhinestone:phase-timings";
1977
- var PAYMENT_METHOD_LABELS = {
1978
- creditcard: "Card",
1979
- debitcard: "Card",
1980
- card: "Card",
1981
- "apple-pay": "Apple Pay",
1982
- applepay: "Apple Pay",
1983
- "google-pay": "Google Pay",
1984
- googlepay: "Google Pay",
1985
- "bank-transfer": "Bank Transfer",
1986
- banktransfer: "Bank Transfer",
1987
- sepa: "SEPA",
1988
- "open-banking": "Open Banking",
1989
- "krak-pay": "Kraken Pay",
1990
- krakpay: "Kraken Pay",
1991
- kraken: "Kraken",
1992
- coinbase: "Coinbase",
1993
- binance: "Binance"
1994
- };
1995
- function formatPaymentMethod(method) {
1996
- const key = method.trim().toLowerCase();
1997
- if (PAYMENT_METHOD_LABELS[key]) return PAYMENT_METHOD_LABELS[key];
1998
- return key.replace(/[-_]+/g, " ").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1999
- }
2000
- function buildQuoteFeeRows(breakdown, sponsoredTooltip) {
2001
- if (!breakdown) return { rows: [], chargeableUsd: 0 };
2002
- const rows = [];
2003
- let chargeableUsd = 0;
2004
- for (const group of quoteFeeGroups(breakdown)) {
2005
- if (group.chargedUsd > 0) {
2006
- chargeableUsd += group.chargedUsd;
2007
- rows.push({ label: group.label, value: formatFeeUsd(group.chargedUsd) });
2008
- } else if (group.sponsoredUsd > 0) {
2009
- rows.push({
2010
- label: group.label,
2011
- value: formatFeeUsd(group.sponsoredUsd),
2012
- strike: true,
2013
- tooltip: sponsoredTooltip
2014
- });
2015
- }
2016
- }
2017
- return { rows, chargeableUsd };
2018
- }
2019
- function toSwappedFiatContext(status) {
2020
- return {
2021
- orderCrypto: status.orderCrypto,
2022
- orderCryptoAmount: status.orderCryptoAmount,
2023
- paidAmountUsd: status.paidAmountUsd,
2024
- paidAmountEur: status.paidAmountEur,
2025
- onrampFeeUsd: status.onrampFeeUsd,
2026
- paymentMethod: status.paymentMethod
2027
- };
2028
- }
2029
- function hasFiatReceiptPricing(status) {
2030
- return status.paidAmountUsd != null && status.onrampFeeUsd != null;
2031
- }
2032
- function loadPhaseTimings(txHash) {
2033
- if (typeof window === "undefined") return null;
2034
- try {
2035
- const raw = window.localStorage.getItem(`${PHASE_TIMINGS_PREFIX}:${txHash}`);
2036
- if (!raw) return null;
2037
- const parsed = JSON.parse(raw);
2038
- if (typeof parsed.startedAt !== "number") return null;
2039
- return parsed;
2040
- } catch {
2041
- return null;
2042
- }
2043
- }
2044
- function savePhaseTimings(txHash, timings) {
2045
- if (typeof window === "undefined") return;
2046
- try {
2047
- window.localStorage.setItem(
2048
- `${PHASE_TIMINGS_PREFIX}:${txHash}`,
2049
- JSON.stringify(timings)
2050
- );
2051
- } catch {
2052
- }
2053
- }
2054
- function isEventForTx(event, txHash) {
2055
- const eventTxHash = getEventTxHash(event);
2056
- if (!eventTxHash) return false;
2057
- return txRefsMatch(eventTxHash, txHash);
2058
- }
2059
- function parseWebhookTimestamp(event) {
2060
- if (typeof event?.time !== "string") return void 0;
2061
- const timestamp = Date.parse(event.time);
2062
- return Number.isFinite(timestamp) ? timestamp : void 0;
2063
- }
2064
- function syncPhaseTimings(previous, event) {
2065
- if (!event?.type) return previous;
2066
- const timestamp = parseWebhookTimestamp(event) ?? Date.now();
2067
- const setReceived = (event.type === "deposit-received" || event.type === "bridge-started" || event.type === "bridge-complete" || event.type === "bridge-failed" || event.type === "error") && previous.receivedAt === void 0;
2068
- const setBridging = (event.type === "bridge-started" || event.type === "bridge-complete") && previous.bridgingAt === void 0;
2069
- const setCompleted = event.type === "bridge-complete" && previous.completedAt === void 0;
2070
- if (!setReceived && !setBridging && !setCompleted) return previous;
2071
- return {
2072
- ...previous,
2073
- ...setReceived && { receivedAt: timestamp },
2074
- ...setBridging && { bridgingAt: timestamp },
2075
- ...setCompleted && { completedAt: timestamp }
2076
- };
2077
- }
2078
- function formatTimer(seconds) {
2079
- const safe = Math.max(0, seconds);
2080
- const mins = Math.floor(safe / 60);
2081
- const secs = safe % 60;
2082
- return `${mins}:${String(secs).padStart(2, "0")}`;
2083
- }
2084
- function TickerChar({ value }) {
2085
- const [current, setCurrent] = useState9(value);
2086
- const [previous, setPrevious] = useState9(null);
2087
- const [animKey, setAnimKey] = useState9(0);
2088
- useEffect8(() => {
2089
- if (value === current) return;
2090
- setPrevious(current);
2091
- setCurrent(value);
2092
- setAnimKey((k) => k + 1);
2093
- const timeout = setTimeout(() => setPrevious(null), 360);
2094
- return () => clearTimeout(timeout);
2095
- }, [value, current]);
2096
- return /* @__PURE__ */ jsxs9("span", { className: "rs-ticker-slot", children: [
2097
- previous !== null && /* @__PURE__ */ jsx10("span", { className: "rs-ticker-out", children: previous }, `out-${animKey}`),
2098
- /* @__PURE__ */ jsx10("span", { className: "rs-ticker-in", children: current }, `in-${animKey}`)
2099
- ] });
2100
- }
2101
- function Ticker({ value }) {
2102
- return /* @__PURE__ */ jsx10("span", { className: "rs-ticker", "aria-label": value, children: value.split("").map((char, index) => (
2103
- // Position-stable key so each slot keeps its own animation state.
2104
- // eslint-disable-next-line react/no-array-index-key
2105
- /* @__PURE__ */ jsx10(TickerChar, { value: char }, index)
2106
- )) });
2107
- }
2108
- function getPhaseStartTime(phaseId, phaseTimings) {
2109
- if (phaseId === "confirming") return phaseTimings.startedAt;
2110
- if (phaseId === "received") {
2111
- return phaseTimings.receivedAt ?? phaseTimings.startedAt;
2112
- }
2113
- return phaseTimings.bridgingAt ?? phaseTimings.receivedAt ?? phaseTimings.startedAt;
2114
- }
2115
- function getFailedPhaseId(phaseTimings) {
2116
- if (phaseTimings.bridgingAt !== void 0) return "bridging";
2117
- if (phaseTimings.receivedAt !== void 0) return "received";
2118
- return "confirming";
2119
- }
2120
- function getCurrentPhaseId(state, phaseTimings) {
2121
- if (state.type === "failed") {
2122
- return getFailedPhaseId(phaseTimings);
2123
- }
2124
- if (state.type === "complete") {
2125
- return void 0;
2126
- }
2127
- if (state.lastEvent?.type === "bridge-started" || state.lastEvent?.type === "bridge-complete")
2128
- return "bridging";
2129
- if (state.lastEvent?.type === "deposit-received") return "received";
2130
- return "confirming";
2131
- }
2132
- function getCompletionDestinationTxHash(event) {
2133
- const eventData = event?.data;
2134
- const swapTxHash = eventData?.swap?.transactionHash;
2135
- const destinationTxHash = eventData?.destination?.transactionHash;
2136
- return swapTxHash ?? destinationTxHash;
2137
- }
2138
- function getTerminalProcessingOutcome(event) {
2139
- if (event?.type === "post-bridge-swap-complete") {
2140
- return {
2141
- type: "complete",
2142
- destinationTxHash: getCompletionDestinationTxHash(event)
2143
- };
2144
- }
2145
- if (event?.type === "post-bridge-swap-failed") {
2146
- return {
2147
- type: "failed",
2148
- message: formatBridgeFailedMessage(event).message
2149
- };
2150
- }
2151
- if (event?.type === "bridge-complete") {
2152
- return {
2153
- type: "complete",
2154
- destinationTxHash: getCompletionDestinationTxHash(event)
2155
- };
2156
- }
2157
- if (event?.type === "bridge-failed") {
2158
- return {
2159
- type: "failed",
2160
- message: formatBridgeFailedMessage(event).message
2161
- };
2162
- }
2163
- if (event?.type === "error") {
2164
- return {
2165
- type: "failed",
2166
- message: event.data?.message ?? "Unknown error"
2167
- };
2168
- }
2169
- return null;
2170
- }
2171
- function getInitialProcessingState(event, txHash) {
2172
- if (!event || !isEventForTx(event, txHash)) return null;
2173
- const terminal = getTerminalProcessingOutcome(event);
2174
- if (terminal?.type === "complete") {
2175
- return { type: "complete", lastEvent: event };
2176
- }
2177
- if (terminal?.type === "failed") {
2178
- return { type: "failed", message: terminal.message, lastEvent: event };
2179
- }
2180
- if (isDepositEvent(event)) {
2181
- return { type: "processing", lastEvent: event };
2182
- }
2183
- return null;
2184
- }
2185
- function getSettledSourceTxHash(event, fallbackTxHash) {
2186
- return event?.data?.source?.transactionHash ?? fallbackTxHash;
2187
- }
2188
- function ProcessingStep({
2189
- smartAccount,
2190
- solanaDepositAddress,
2191
- txHash,
2192
- sourceChain,
2193
- sourceToken,
2194
- targetChain,
2195
- amount,
2196
- sourceSymbol: providedSourceSymbol,
2197
- sourceDecimals: providedSourceDecimals,
2198
- amountUsd,
2199
- service,
2200
- directTransfer,
2201
- sourceTxHashPending,
2202
- initialEvent,
2203
- flowLabel = "deposit",
2204
- debug,
2205
- targetToken,
2206
- uiConfig,
2207
- quotedFeeAmount,
2208
- quotedFeeSymbol,
2209
- quote,
2210
- balanceAfterUsd,
2211
- isSwappedOrder,
2212
- swappedContext,
2213
- onClose,
2214
- onNewDeposit,
2215
- onRetry,
2216
- onDepositComplete,
2217
- onDepositFailed,
2218
- onSourceTransactionObserved,
2219
- onSettledChange,
2220
- onError
2221
- }) {
2222
- const startTimeRef = useRef7(Date.now());
2223
- const pollIntervalRef = useRef7(INITIAL_POLL_INTERVAL);
2224
- const pollTimeoutRef = useRef7(null);
2225
- const escalatedDelayRef = useRef7(false);
2226
- const processingContextRef = useLatestRef({
2227
- amount,
2228
- sourceChain,
2229
- sourceToken,
2230
- sourceDecimals: providedSourceDecimals,
2231
- amountUsd,
2232
- targetChain,
2233
- targetToken
2234
- });
2235
- const onDepositCompleteRef = useLatestRef(onDepositComplete);
2236
- const onDepositFailedRef = useLatestRef(onDepositFailed);
2237
- const onErrorRef = useLatestRef(onError);
2238
- const explorerTxUrl = useExplorerTxUrl();
2239
- const initialTerminalEventRef = useRef7(null);
2240
- const completedDirectTransferTxHashRef = useRef7(null);
2241
- const [state, setState] = useState9(() => {
2242
- if (directTransfer) return { type: "complete" };
2243
- const initial = getInitialProcessingState(
2244
- initialEvent,
2245
- txHash
2246
- );
2247
- if (initial) {
2248
- if (initial.type !== "processing") {
2249
- initialTerminalEventRef.current = initial.lastEvent ?? null;
2250
- }
2251
- return initial;
2252
- }
2253
- return { type: "processing" };
2254
- });
2255
- const [elapsedSeconds, setElapsedSeconds] = useState9(0);
2256
- const [phaseTimings, setPhaseTimings] = useState9(() => {
2257
- const saved = loadPhaseTimings(txHash);
2258
- if (saved) {
2259
- startTimeRef.current = saved.startedAt;
2260
- return saved;
2261
- }
2262
- return { startedAt: startTimeRef.current };
2263
- });
2264
- const [hasEscalatedDelay, setHasEscalatedDelay] = useState9(false);
2265
- const updatePhaseTimings = useCallback7(
2266
- (updater) => {
2267
- setPhaseTimings((previous) => {
2268
- const next = updater(previous);
2269
- savePhaseTimings(txHash, next);
2270
- return next;
2271
- });
2272
- },
2273
- [txHash]
2274
- );
2275
- useEffect8(() => {
2276
- if (!directTransfer || completedDirectTransferTxHashRef.current === txHash) {
2277
- return;
2278
- }
2279
- completedDirectTransferTxHashRef.current = txHash;
2280
- const completedAt = Date.now();
2281
- updatePhaseTimings(() => ({
2282
- startedAt: startTimeRef.current,
2283
- completedAt,
2284
- endedAt: completedAt
2285
- }));
2286
- debugLog(debug, "processing", "direct-transfer:complete", {
2287
- txHash,
2288
- flowLabel
2289
- });
2290
- const context = processingContextRef.current;
2291
- onDepositCompleteRef.current?.(txHash, void 0, {
2292
- amount: context.amount,
2293
- sourceChain: context.sourceChain,
2294
- sourceToken: context.sourceToken,
2295
- sourceDecimals: context.sourceDecimals,
2296
- amountUsd: context.amountUsd,
2297
- targetChain: context.targetChain,
2298
- targetToken: context.targetToken
2299
- });
2300
- }, [
2301
- debug,
2302
- directTransfer,
2303
- flowLabel,
2304
- onDepositCompleteRef,
2305
- processingContextRef,
2306
- txHash,
2307
- updatePhaseTimings
2308
- ]);
2309
- useEffect8(() => {
2310
- const event = initialTerminalEventRef.current;
2311
- if (!event) return;
2312
- initialTerminalEventRef.current = null;
2313
- const outcome = getTerminalProcessingOutcome(event);
2314
- if (!outcome) return;
2315
- if (outcome.type === "complete") {
2316
- const context = processingContextRef.current;
2317
- const sourceTxHash2 = getSettledSourceTxHash(event, txHash);
2318
- debugLog(debug, "processing", "initial-event:complete", {
2319
- txHash,
2320
- sourceTxHash: sourceTxHash2,
2321
- destinationTxHash: outcome.destinationTxHash,
2322
- event: event.type
2323
- });
2324
- onDepositCompleteRef.current?.(
2325
- sourceTxHash2,
2326
- outcome.destinationTxHash,
2327
- {
2328
- amount: context.amount,
2329
- sourceChain: context.sourceChain,
2330
- sourceToken: context.sourceToken,
2331
- sourceDecimals: context.sourceDecimals,
2332
- amountUsd: context.amountUsd,
2333
- targetChain: context.targetChain,
2334
- targetToken: context.targetToken
2335
- }
2336
- );
2337
- return;
2338
- }
2339
- debugLog(debug, "processing", "initial-event:failed", {
2340
- txHash,
2341
- message: outcome.message,
2342
- event: event.type
2343
- });
2344
- onDepositFailedRef.current?.(txHash, outcome.message);
2345
- }, [
2346
- debug,
2347
- onDepositCompleteRef,
2348
- onDepositFailedRef,
2349
- processingContextRef,
2350
- txHash
2351
- ]);
2352
- const onSettledChangeRef = useRef7(onSettledChange);
2353
- onSettledChangeRef.current = onSettledChange;
2354
- useEffect8(() => {
2355
- onSettledChangeRef.current?.(state.type !== "processing");
2356
- return () => onSettledChangeRef.current?.(false);
2357
- }, [state.type]);
2358
- useEffect8(() => {
2359
- if (directTransfer || state.type !== "processing") return;
2360
- const updateElapsed = () => {
2361
- setElapsedSeconds(
2362
- Math.floor((Date.now() - startTimeRef.current) / 1e3)
2363
- );
2364
- };
2365
- updateElapsed();
2366
- const intervalId = setInterval(updateElapsed, 1e3);
2367
- return () => clearInterval(intervalId);
2368
- }, [directTransfer, state.type]);
2369
- useEffect8(() => {
2370
- if (state.type === "processing") return;
2371
- const endedAt = state.type === "complete" ? phaseTimings.completedAt ?? Date.now() : Date.now();
2372
- setElapsedSeconds(Math.floor((endedAt - startTimeRef.current) / 1e3));
2373
- updatePhaseTimings(
2374
- (previous) => previous.endedAt !== void 0 ? previous : { ...previous, endedAt }
2375
- );
2376
- }, [phaseTimings.completedAt, state.type, updatePhaseTimings]);
2377
- useEffect8(() => {
2378
- if (!state.lastEvent) return;
2379
- updatePhaseTimings(
2380
- (previous) => syncPhaseTimings(previous, state.lastEvent)
2381
- );
2382
- }, [state.lastEvent?.time, state.lastEvent?.type, updatePhaseTimings]);
2383
- const [swappedFiatContext, setSwappedFiatContext] = useState9(null);
2384
- useEffect8(() => {
2385
- let cancelled = false;
2386
- let timeoutId;
2387
- const isFiatSwappedOrder = isSwappedOrder && swappedContext?.variant === "fiat";
2388
- const expectedOrderUuid = swappedContext?.expectedOrderUuid ?? null;
2389
- setSwappedFiatContext(null);
2390
- if (isFiatSwappedOrder && !expectedOrderUuid) {
2391
- return () => {
2392
- cancelled = true;
2393
- };
2394
- }
2395
- let attempts = 0;
2396
- const matchesCurrentDeposit = (res) => {
2397
- if (isFiatSwappedOrder) {
2398
- return res.orderId === expectedOrderUuid;
2399
- }
2400
- return res.transactionId != null && res.transactionId.toLowerCase() === txHash.toLowerCase();
2401
- };
2402
- const scheduleRetry = () => {
2403
- if (cancelled || !isFiatSwappedOrder || attempts >= SWAPPED_FIAT_STATUS_MAX_ATTEMPTS) {
2404
- return;
2405
- }
2406
- timeoutId = setTimeout(
2407
- fetchContext,
2408
- SWAPPED_RECEIPT_STATUS_RETRY_INTERVAL_MS
2409
- );
2410
- };
2411
- async function fetchContext() {
2412
- attempts += 1;
2413
- try {
2414
- const res = await service.fetchSwappedOrderStatus(smartAccount);
2415
- if (cancelled) return;
2416
- if (!res || !matchesCurrentDeposit(res)) {
2417
- scheduleRetry();
2418
- return;
2419
- }
2420
- setSwappedFiatContext(toSwappedFiatContext(res));
2421
- if (isFiatSwappedOrder && !hasFiatReceiptPricing(res)) {
2422
- scheduleRetry();
2423
- }
2424
- } catch {
2425
- scheduleRetry();
2426
- }
2427
- }
2428
- fetchContext();
2429
- return () => {
2430
- cancelled = true;
2431
- if (timeoutId !== void 0) clearTimeout(timeoutId);
2432
- };
2433
- }, [
2434
- service,
2435
- smartAccount,
2436
- txHash,
2437
- isSwappedOrder,
2438
- swappedContext?.variant,
2439
- swappedContext?.expectedOrderUuid
2440
- ]);
2441
- useEffect8(() => {
2442
- if (directTransfer) return;
2443
- if (state.type !== "processing") {
2444
- pollIntervalRef.current = INITIAL_POLL_INTERVAL;
2445
- return;
2446
- }
2447
- let isMounted = true;
2448
- async function pollStatus() {
2449
- try {
2450
- const account = sourceChain === "solana" && solanaDepositAddress ? solanaDepositAddress : smartAccount;
2451
- debugLog(debug, "processing", "poll:request", {
2452
- account,
2453
- txHash,
2454
- intervalMs: pollIntervalRef.current
2455
- });
2456
- const data = await service.fetchStatus(account, txHash);
2457
- const lastEvent2 = data.lastEvent;
2458
- const eventMatchesTx = isEventForTx(lastEvent2, txHash);
2459
- const eventForCurrentTx = eventMatchesTx ? lastEvent2 : void 0;
2460
- if (lastEvent2) {
2461
- const eventData = lastEvent2.data;
2462
- debugLog(debug, "processing", "poll:event", {
2463
- type: lastEvent2.type,
2464
- matchesTx: eventMatchesTx,
2465
- intentId: eventData?.intentId
2466
- });
2467
- }
2468
- if (!isMounted) return;
2469
- if (eventForCurrentTx?.type === "bridge-complete") {
2470
- setState({ type: "complete", lastEvent: eventForCurrentTx });
2471
- const sourceTxHash2 = getSettledSourceTxHash(
2472
- eventForCurrentTx,
2473
- txHash
2474
- );
2475
- const destinationTxHash2 = eventForCurrentTx.data?.destination?.transactionHash;
2476
- debugLog(debug, "processing", "state:complete", {
2477
- txHash,
2478
- sourceTxHash: sourceTxHash2,
2479
- destinationTxHash: destinationTxHash2,
2480
- event: eventForCurrentTx.type
2481
- });
2482
- const context = processingContextRef.current;
2483
- onDepositCompleteRef.current?.(sourceTxHash2, destinationTxHash2, {
2484
- amount: context.amount,
2485
- sourceChain: context.sourceChain,
2486
- sourceToken: context.sourceToken,
2487
- sourceDecimals: context.sourceDecimals,
2488
- amountUsd: context.amountUsd,
2489
- targetChain: context.targetChain,
2490
- targetToken: context.targetToken
2491
- });
2492
- return;
2493
- }
2494
- if (eventForCurrentTx?.type === "bridge-failed") {
2495
- const formatted = formatBridgeFailedMessage(eventForCurrentTx);
2496
- setState({
2497
- type: "failed",
2498
- message: formatted.message,
2499
- lastEvent: eventForCurrentTx
2500
- });
2501
- debugLog(debug, "processing", "state:failed", {
2502
- txHash,
2503
- message: formatted.message,
2504
- code: formatted.code
2505
- });
2506
- onDepositFailedRef.current?.(txHash, formatted.message);
2507
- return;
2508
- }
2509
- if (eventForCurrentTx?.type === "error") {
2510
- const errorMessage = eventForCurrentTx.data?.message ?? "Unknown error";
2511
- setState({
2512
- type: "failed",
2513
- message: errorMessage,
2514
- lastEvent: eventForCurrentTx
2515
- });
2516
- debugLog(debug, "processing", "state:error-event", {
2517
- txHash,
2518
- message: errorMessage
2519
- });
2520
- onDepositFailedRef.current?.(txHash, errorMessage);
2521
- return;
2522
- }
2523
- setState((previous) => ({
2524
- type: "processing",
2525
- lastEvent: eventForCurrentTx ?? previous.lastEvent
2526
- }));
2527
- scheduleNextPoll();
2528
- } catch (error) {
2529
- debugError(debug, "processing", "poll:failure", error, {
2530
- smartAccount,
2531
- txHash,
2532
- intervalMs: pollIntervalRef.current
2533
- });
2534
- scheduleNextPoll();
2535
- }
2536
- }
2537
- function scheduleNextPoll() {
2538
- if (!isMounted) return;
2539
- pollTimeoutRef.current = setTimeout(() => {
2540
- pollIntervalRef.current = Math.min(
2541
- pollIntervalRef.current * BACKOFF_MULTIPLIER,
2542
- MAX_POLL_INTERVAL
2543
- );
2544
- debugLog(debug, "processing", "poll:scheduled", {
2545
- nextIntervalMs: pollIntervalRef.current
2546
- });
2547
- pollStatus();
2548
- }, pollIntervalRef.current);
2549
- }
2550
- pollStatus();
2551
- return () => {
2552
- isMounted = false;
2553
- if (pollTimeoutRef.current) {
2554
- clearTimeout(pollTimeoutRef.current);
2555
- }
2556
- };
2557
- }, [
2558
- debug,
2559
- directTransfer,
2560
- onDepositCompleteRef,
2561
- onDepositFailedRef,
2562
- processingContextRef,
2563
- service,
2564
- smartAccount,
2565
- solanaDepositAddress,
2566
- sourceChain,
2567
- state.type,
2568
- txHash
2569
- ]);
2570
- useEffect8(() => {
2571
- if (directTransfer || state.type !== "processing") return;
2572
- const timeoutId = setTimeout(() => {
2573
- if (escalatedDelayRef.current) return;
2574
- escalatedDelayRef.current = true;
2575
- setHasEscalatedDelay(true);
2576
- const message = "Transfer is taking longer than expected. Your funds are safe and processing will continue automatically.";
2577
- debugLog(debug, "processing", "state:delay-escalated", {
2578
- txHash,
2579
- timeoutMs: ESCALATED_DELAY_MS
2580
- });
2581
- onErrorRef.current?.(message, "PROCESS_TIMEOUT");
2582
- }, ESCALATED_DELAY_MS);
2583
- return () => clearTimeout(timeoutId);
2584
- }, [debug, directTransfer, onErrorRef, state.type, txHash]);
2585
- const isComplete = state.type === "complete";
2586
- const isFailed = state.type === "failed";
2587
- const isProcessing = state.type === "processing";
2588
- const lastEvent = state.lastEvent;
2589
- const failureMessage = state.type === "failed" ? state.message : void 0;
2590
- const timelineNowMs = phaseTimings.endedAt ?? Date.now();
2591
- const flowNoun = flowLabel === "withdraw" ? "withdrawal" : "deposit";
2592
- const flowCapitalized = flowLabel === "withdraw" ? "Withdrawal" : "Deposit";
2593
- const destinationTxHash = lastEvent?.data?.destination?.transactionHash || null;
2594
- const sourceTxHash = getSettledSourceTxHash(lastEvent, txHash);
2595
- const sourceDetails = getEventSourceDetails(lastEvent);
2596
- const displaySourceChain = sourceDetails.chainId ?? sourceChain;
2597
- const displaySourceToken = sourceDetails.token ?? sourceToken;
2598
- const displayAmount = sourceDetails.amount ?? amount;
2599
- const targetChainId = targetChainToChainId(targetChain);
2600
- const settledSourceTxHash = lastEvent?.data?.source?.transactionHash;
2601
- const sourceExplorerHash = settledSourceTxHash ?? (sourceTxHashPending ? null : sourceTxHash);
2602
- useEffect8(() => {
2603
- if (sourceTxHashPending && settledSourceTxHash) {
2604
- onSourceTransactionObserved?.(settledSourceTxHash);
2605
- }
2606
- }, [onSourceTransactionObserved, settledSourceTxHash, sourceTxHashPending]);
2607
- const sourceExplorerUrl = sourceExplorerHash ? explorerTxUrl(displaySourceChain, sourceExplorerHash) : null;
2608
- const destExplorerUrl = destinationTxHash ? explorerTxUrl(targetChainId, destinationTxHash) : null;
2609
- const sourceDisplay = (() => {
2610
- const resolved = resolveTokenDisplay(displaySourceToken, displaySourceChain, {
2611
- symbol: providedSourceSymbol ?? (displaySourceChain === "solana" ? "SOL" : "Token"),
2612
- decimals: providedSourceDecimals
2613
- });
2614
- return {
2615
- symbol: resolved.symbol,
2616
- decimals: resolved.decimals ?? (displaySourceChain === "solana" ? 9 : 18)
2617
- };
2618
- })();
2619
- const sourceSymbol = sourceDisplay.symbol;
2620
- const formattedSentAmount = formatRawTokenAmount(displayAmount, sourceDisplay) ?? // Not raw base units (e.g. a decimal string) — render the number as-is.
2621
- formatTokenAmount(Number(displayAmount), sourceDisplay.symbol) ?? displayAmount;
2622
- const eventDestination = getEventDestinationDetails(lastEvent);
2623
- const targetDisplay = resolveTokenDisplay(
2624
- eventDestination.token ?? targetToken,
2625
- eventDestination.chainId ?? targetChainId,
2626
- // Dapp deposits default to USDC targets; mirror the source symbol last so
2627
- // unresolvable same-token routes still label sensibly.
2628
- { symbol: providedSourceSymbol ?? "USDC" }
2629
- );
2630
- const targetSymbol = targetDisplay.symbol;
2631
- const formattedDestinationAmount = eventDestination.amount !== void 0 ? formatRawTokenAmount(eventDestination.amount, targetDisplay) : void 0;
2632
- const amountUsdNumber = amountUsd !== void 0 && Number(amountUsd) > 0 ? Number(amountUsd) : void 0;
2633
- const prices = useTokenPrices(service, [
2634
- {
2635
- chainId: displaySourceChain,
2636
- address: displaySourceToken,
2637
- symbol: sourceDisplay.symbol
2638
- },
2639
- {
2640
- chainId: eventDestination.chainId ?? targetChainId,
2641
- address: eventDestination.token ?? targetToken,
2642
- symbol: targetDisplay.symbol
2643
- }
2644
- ]);
2645
- const estimatedReceiveAmount = (() => {
2646
- const estimate = estimateReceiveAmount({
2647
- sourceAmount: tokenAmountToNumber(displayAmount, sourceDisplay.decimals),
2648
- sourceSymbol: sourceDisplay.symbol,
2649
- targetSymbol: targetDisplay.symbol,
2650
- sourceAmountUsd: amountUsdNumber,
2651
- sourceTrusted: isRecognizedToken(displaySourceToken, displaySourceChain),
2652
- prices
2653
- });
2654
- return estimate !== void 0 ? formatTokenAmount(estimate, targetDisplay.symbol) : void 0;
2655
- })();
2656
- const quotedReceive = quote ? formatQuotedReceive(quote.output, targetSymbol) : void 0;
2657
- const receiveDisplay = formattedDestinationAmount ? (
2658
- // The delivered amount, once the bridge reports it — actual, not an
2659
- // estimate, so it wins over the quote and carries no "~".
2660
- `${formattedDestinationAmount} ${targetSymbol}`
2661
- ) : quotedReceive ?? (estimatedReceiveAmount !== void 0 ? `~${estimatedReceiveAmount} ${targetSymbol}` : amountUsdNumber !== void 0 ? `~$${amountUsdNumber.toFixed(2)}` : (
2662
- // Cross-token with no price and no USD context (rare QR case):
2663
- // an honest dash beats a wrong number.
2664
- "\u2014"
2665
- ));
2666
- const formattedBridgingCost = (() => {
2667
- if (sourceSymbol.toUpperCase() !== targetSymbol.toUpperCase()) {
2668
- return void 0;
2669
- }
2670
- const sentValue = tokenAmountToNumber(displayAmount, sourceDisplay.decimals);
2671
- const receivedValue = eventDestination.amount !== void 0 ? tokenAmountToNumber(eventDestination.amount, targetDisplay.decimals) : void 0;
2672
- if (sentValue === void 0 || receivedValue === void 0 || sentValue <= receivedValue) {
2673
- return void 0;
2674
- }
2675
- const formatted = formatTokenAmount(sentValue - receivedValue, sourceSymbol);
2676
- if (formatted === void 0 || Number(formatted.replace(/,/g, "")) === 0) {
2677
- return void 0;
2678
- }
2679
- return formatted;
2680
- })();
2681
- const currentPhaseId = getCurrentPhaseId(state, phaseTimings);
2682
- const activePhaseStartedAt = currentPhaseId ? getPhaseStartTime(currentPhaseId, phaseTimings) : void 0;
2683
- const activePhaseElapsedMs = isProcessing && activePhaseStartedAt !== void 0 ? timelineNowMs - activePhaseStartedAt : 0;
2684
- const delayPhaseId = isProcessing && currentPhaseId && activePhaseElapsedMs >= SOFT_DELAY_MS[currentPhaseId] ? currentPhaseId : void 0;
2685
- void delayPhaseId;
2686
- void hasEscalatedDelay;
2687
- const sourceChainIcon = getChainIcon(displaySourceChain);
2688
- const targetChainIcon = getChainIcon(targetChainId);
2689
- const sourceChainName = getChainName(displaySourceChain);
2690
- const targetChainName = getChainName(targetChainId);
2691
- const timerText = formatTimer(elapsedSeconds);
2692
- const feeSponsored = uiConfig?.feeSponsored ?? false;
2693
- const feeTooltip = uiConfig?.feeTooltip ?? (feeSponsored ? "Network fees are sponsored for this deposit." : "Network fees apply.");
2694
- const stateTitle = isComplete ? `${flowCapitalized} successful` : isFailed ? `${flowCapitalized} failed` : "Processing...";
2695
- const handleRetry = onRetry ?? onNewDeposit;
2696
- const headerContent = isComplete ? /* @__PURE__ */ jsxs9("div", { className: "rs-body-header", children: [
2697
- /* @__PURE__ */ jsx10(SuccessBadge, {}),
2698
- /* @__PURE__ */ jsx10("div", { className: "rs-body-header-text", children: /* @__PURE__ */ jsx10("h2", { className: "rs-body-header-title", children: stateTitle }) })
2699
- ] }) : isFailed ? /* @__PURE__ */ jsxs9("div", { className: "rs-body-header", children: [
2700
- /* @__PURE__ */ jsx10(FailedBadge, {}),
2701
- /* @__PURE__ */ jsx10("div", { className: "rs-body-header-text", children: /* @__PURE__ */ jsx10("h2", { className: "rs-body-header-title", children: stateTitle }) })
2702
- ] }) : /* @__PURE__ */ jsx10(BodyHeader, { icon: /* @__PURE__ */ jsx10(WalletIcon, {}), title: stateTitle });
2703
- if (isComplete && isSwappedOrder) {
2704
- const effectivePaymentMethod = swappedContext?.method ?? swappedFiatContext?.paymentMethod ?? null;
2705
- const onrampMethod = effectivePaymentMethod ? formatPaymentMethod(effectivePaymentMethod) : null;
2706
- const onrampMethodIcon = swappedContext?.variant === "connect" && effectivePaymentMethod ? getExchangeLogoSrc(
2707
- onrampMethod ?? effectivePaymentMethod,
2708
- effectivePaymentMethod
2709
- ) : null;
2710
- const connectPaidAmount = swappedContext?.variant === "connect" ? `${formattedSentAmount} ${sourceSymbol}` : null;
2711
- const amountPaid = connectPaidAmount ?? (swappedFiatContext?.paidAmountUsd != null ? `$${swappedFiatContext.paidAmountUsd.toFixed(2)}` : null);
2712
- const depositedAmount = receiveDisplay;
2713
- const onrampFeeUsd = swappedFiatContext?.onrampFeeUsd ?? null;
2714
- const sponsoredFeeTooltip = "This fee is sponsored for this deposit.";
2715
- const feesTooltip = feeSponsored ? "On-ramp fees are charged by the payment provider. Market and network fees are sponsored for this deposit." : "Fees include on-ramp, market, and network costs for this deposit.";
2716
- const { rows: quoteFeeRows, chargeableUsd } = buildQuoteFeeRows(
2717
- quote?.fees.breakdown,
2718
- sponsoredFeeTooltip
2719
- );
2720
- const feeRows = [];
2721
- if (onrampFeeUsd != null) {
2722
- feeRows.push({
2723
- label: "On-ramp fee",
2724
- value: formatFeeUsd(onrampFeeUsd)
2725
- });
2726
- }
2727
- const hasFallbackFee = quoteFeeRows.length === 0 && quotedFeeAmount !== void 0 && Number.isFinite(Number(quotedFeeAmount));
2728
- if (quoteFeeRows.length > 0) {
2729
- feeRows.push(...quoteFeeRows);
2730
- } else if (hasFallbackFee) {
2731
- feeRows.push({
2732
- label: "Network fee",
2733
- value: formatFeeUsd(Number(quotedFeeAmount)),
2734
- strike: feeSponsored,
2735
- tooltip: feeSponsored ? sponsoredFeeTooltip : void 0
2736
- });
2737
- }
2738
- const fallbackChargeableUsd = hasFallbackFee && !feeSponsored ? Number(quotedFeeAmount) : 0;
2739
- const feesTotal = feeRows.length > 0 ? formatFeeUsd(
2740
- (onrampFeeUsd ?? 0) + chargeableUsd + fallbackChargeableUsd
2741
- ) : null;
2742
- return /* @__PURE__ */ jsx10(
2743
- SwappedReceipt,
2744
- {
2745
- onrampMethod,
2746
- onrampMethodIcon,
2747
- amountPaid,
2748
- depositedAmount,
2749
- depositedSymbol: targetSymbol,
2750
- feesTotal,
2751
- feeRows,
2752
- feesTooltip,
2753
- newLabel: `New ${flowNoun}`,
2754
- onNewDeposit,
2755
- onClose: onClose ? () => onClose("success_done") : void 0
2756
- }
2757
- );
2758
- }
2759
- return /* @__PURE__ */ jsxs9("div", { className: "rs-screen", children: [
2760
- /* @__PURE__ */ jsxs9("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
2761
- headerContent,
2762
- /* @__PURE__ */ jsxs9("div", { className: "rs-review-details", children: [
2763
- /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2764
- /* @__PURE__ */ jsx10("span", { children: "Source chain" }),
2765
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2766
- /* @__PURE__ */ jsx10("span", { children: sourceChainName }),
2767
- sourceChainIcon && /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx10(CdnIcon, { src: sourceChainIcon }) }),
2768
- sourceExplorerUrl && /* @__PURE__ */ jsx10(
2769
- "a",
2770
- {
2771
- href: sourceExplorerUrl,
2772
- target: "_blank",
2773
- rel: "noopener noreferrer",
2774
- className: "rs-review-detail-link",
2775
- "aria-label": "View source transaction",
2776
- children: /* @__PURE__ */ jsx10(ExternalLinkIcon, {})
2777
- }
2778
- )
2779
- ] })
2780
- ] }),
2781
- /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2782
- /* @__PURE__ */ jsx10("span", { children: "Destination chain" }),
2783
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2784
- /* @__PURE__ */ jsx10("span", { children: targetChainName }),
2785
- targetChainIcon && /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx10(CdnIcon, { src: targetChainIcon }) }),
2786
- destExplorerUrl && /* @__PURE__ */ jsx10(
2787
- "a",
2788
- {
2789
- href: destExplorerUrl,
2790
- target: "_blank",
2791
- rel: "noopener noreferrer",
2792
- className: "rs-review-detail-link",
2793
- "aria-label": "View destination transaction",
2794
- children: /* @__PURE__ */ jsx10(ExternalLinkIcon, {})
2795
- }
2796
- )
2797
- ] })
2798
- ] }),
2799
- /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2800
- /* @__PURE__ */ jsx10("span", { children: isProcessing ? "Time elapsed" : "Total time" }),
2801
- /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-value", children: /* @__PURE__ */ jsx10(Ticker, { value: timerText }) })
2802
- ] }),
2803
- isSwappedOrder ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
2804
- swappedFiatContext?.paidAmountUsd != null && /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2805
- /* @__PURE__ */ jsx10("span", { children: "You pay" }),
2806
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2807
- "$",
2808
- swappedFiatContext.paidAmountUsd.toFixed(2),
2809
- swappedFiatContext.paymentMethod && /* @__PURE__ */ jsxs9("span", { style: { color: "#71717b", marginLeft: 6 }, children: [
2810
- "via",
2811
- " ",
2812
- formatPaymentMethod(swappedFiatContext.paymentMethod)
2813
- ] })
2814
- ] })
2815
- ] }),
2816
- swappedFiatContext?.onrampFeeUsd != null && /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2817
- /* @__PURE__ */ jsx10("span", { children: "On-ramp fee" }),
2818
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2819
- "$",
2820
- swappedFiatContext.onrampFeeUsd.toFixed(2)
2821
- ] })
2822
- ] }),
2823
- formattedBridgingCost && /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2824
- /* @__PURE__ */ jsx10("span", { children: "Bridging cost" }),
2825
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2826
- /* @__PURE__ */ jsxs9("span", { children: [
2827
- formattedBridgingCost,
2828
- " ",
2829
- sourceSymbol
2830
- ] }),
2831
- /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx10(TokenIcon, { symbol: sourceSymbol }) })
2832
- ] })
2833
- ] })
2834
- ] }) : /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2835
- /* @__PURE__ */ jsx10("span", { children: isProcessing ? "You send" : "You sent" }),
2836
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2837
- /* @__PURE__ */ jsxs9("span", { children: [
2838
- formattedSentAmount,
2839
- " ",
2840
- sourceSymbol
2841
- ] }),
2842
- /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx10(TokenIcon, { symbol: sourceSymbol }) })
2843
- ] })
2844
- ] }),
2845
- /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2846
- /* @__PURE__ */ jsx10("span", { children: isProcessing ? "Receive" : "Received" }),
2847
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2848
- /* @__PURE__ */ jsx10("span", { children: receiveDisplay }),
2849
- /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx10(TokenIcon, { symbol: targetSymbol }) })
2850
- ] })
2851
- ] }),
2852
- isFailed && balanceAfterUsd !== void 0 && /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2853
- /* @__PURE__ */ jsx10("span", { children: "Balance" }),
2854
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2855
- "$",
2856
- balanceAfterUsd.toFixed(2)
2857
- ] })
2858
- ] }),
2859
- isSwappedOrder ? quotedFeeAmount !== void 0 && /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2860
- /* @__PURE__ */ jsx10("span", { children: "Fees" }),
2861
- /* @__PURE__ */ jsxs9("span", { className: "rs-review-detail-value", children: [
2862
- /* @__PURE__ */ jsxs9(
2863
- "span",
2864
- {
2865
- style: feeSponsored ? { textDecoration: "line-through" } : void 0,
2866
- children: [
2867
- quotedFeeAmount,
2868
- " ",
2869
- quotedFeeSymbol ?? sourceSymbol
2870
- ]
2871
- }
2872
- ),
2873
- /* @__PURE__ */ jsx10(Tooltip, { content: feeTooltip, children: /* @__PURE__ */ jsx10(
2874
- "span",
2875
- {
2876
- className: "rs-review-detail-info",
2877
- "aria-label": "Fee info",
2878
- children: /* @__PURE__ */ jsx10(InfoIcon, {})
2879
- }
2880
- ) })
2881
- ] })
2882
- ] }) : (() => {
2883
- const { display, struck } = quoteFeeRow(quote?.fees);
2884
- if (display !== null) {
2885
- return /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2886
- /* @__PURE__ */ jsx10("span", { children: "Fees" }),
2887
- /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-value", children: /* @__PURE__ */ jsx10(
2888
- "span",
2889
- {
2890
- style: struck ? { textDecoration: "line-through" } : void 0,
2891
- children: display
2892
- }
2893
- ) })
2894
- ] });
2895
- }
2896
- const feeValue = quotedFeeAmount ?? formattedBridgingCost;
2897
- if (feeValue === void 0) return null;
2898
- return /* @__PURE__ */ jsxs9("div", { className: "rs-review-detail-row", children: [
2899
- /* @__PURE__ */ jsx10("span", { children: "Fees" }),
2900
- /* @__PURE__ */ jsx10("span", { className: "rs-review-detail-value", children: /* @__PURE__ */ jsxs9(
2901
- "span",
2902
- {
2903
- style: feeSponsored ? { textDecoration: "line-through" } : void 0,
2904
- children: [
2905
- feeValue,
2906
- " ",
2907
- quotedFeeSymbol ?? sourceSymbol
2908
- ]
2909
- }
2910
- ) })
2911
- ] });
2912
- })()
2913
- ] }),
2914
- isFailed && failureMessage && /* @__PURE__ */ jsx10(Callout, { variant: "error", children: failureMessage }),
2915
- isProcessing && /* @__PURE__ */ jsx10(
2916
- Button,
2917
- {
2918
- fullWidth: true,
2919
- disabled: true,
2920
- loading: true,
2921
- loadingText: "Submitting transaction\u2026",
2922
- children: "Submitting transaction\u2026"
2923
- }
2924
- ),
2925
- isComplete && /* @__PURE__ */ jsxs9("div", { className: "rs-screen-button-row", children: [
2926
- onNewDeposit && /* @__PURE__ */ jsxs9(Button, { variant: "outline", onClick: onNewDeposit, fullWidth: true, children: [
2927
- "New ",
2928
- flowNoun
2929
- ] }),
2930
- onClose && /* @__PURE__ */ jsx10(Button, { onClick: () => onClose("success_done"), fullWidth: true, children: "Done" })
2931
- ] }),
2932
- isFailed && /* @__PURE__ */ jsxs9("div", { className: "rs-screen-button-row", children: [
2933
- onClose && /* @__PURE__ */ jsx10(
2934
- Button,
2935
- {
2936
- variant: "outline",
2937
- onClick: () => onClose("failure_cancel"),
2938
- fullWidth: true,
2939
- children: "Cancel"
2940
- }
2941
- ),
2942
- handleRetry && /* @__PURE__ */ jsx10(Button, { onClick: handleRetry, fullWidth: true, children: "Try again" })
2943
- ] })
2944
- ] }),
2945
- /* @__PURE__ */ jsx10(PoweredBy, {})
2946
- ] });
2947
- }
2948
-
2949
- // src/core/account-setup.ts
2950
- import { concat, isAddress, keccak256, toHex } from "viem";
2951
- var SetupError = class extends Error {
2952
- constructor(message, category) {
2953
- super(message);
2954
- this.category = category;
2955
- this.name = "SetupError";
2956
- }
2957
- };
2958
- function stableStringify(value) {
2959
- if (value === null || typeof value !== "object") {
2960
- return JSON.stringify(value);
2961
- }
2962
- if (Array.isArray(value)) {
2963
- return `[${value.map(stableStringify).join(",")}]`;
2964
- }
2965
- const obj = value;
2966
- const keys = Object.keys(obj).sort();
2967
- const entries = keys.filter((k) => obj[k] !== void 0).map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`);
2968
- return `{${entries.join(",")}}`;
2969
- }
2970
- function normalizeKeyToken(value) {
2971
- if (value === void 0) return void 0;
2972
- return value.startsWith("0x") ? value.toLowerCase() : value;
2973
- }
2974
- var RECIPIENT_PATTERNS = {
2975
- solana: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/,
2976
- tron: /^T[1-9A-HJ-NP-Za-km-z]{33}$/,
2977
- stellar: /^G[A-Z2-7]{55}$/
2978
- };
2979
- var RECIPIENT_HINTS = {
2980
- solana: "A Solana destination needs a base58 recipient address.",
2981
- tron: "A Tron destination needs a base58 recipient address starting with T.",
2982
- stellar: "A Stellar destination needs a recipient account ID starting with G."
2983
- };
2984
- function recipientNamespace(targetChain) {
2985
- return targetChainToCaip2(targetChain).split(":")[0] ?? "";
2986
- }
2987
- function recipientMatchesTarget(recipient, targetChain) {
2988
- const pattern = RECIPIENT_PATTERNS[recipientNamespace(targetChain)];
2989
- return pattern ? pattern.test(recipient) : isAddress(recipient);
2990
- }
2991
- function deriveAccountSalt(input) {
2992
- return keccak256(
2993
- concat([
2994
- keccak256(toHex(normalizeKeyToken(input.recipient) ?? "")),
2995
- keccak256(toHex(targetChainToCaip2(input.targetChain))),
2996
- keccak256(toHex(normalizeKeyToken(input.targetToken) ?? ""))
2997
- ])
2998
- );
2999
- }
3000
- function computeRequestKey(input) {
3001
- return stableStringify({
3002
- targetChain: input.targetChain,
3003
- targetToken: normalizeKeyToken(input.targetToken),
3004
- recipient: normalizeKeyToken(input.recipient),
3005
- outputTokenRules: input.outputTokenRules ?? null,
3006
- rejectUnmapped: input.rejectUnmapped ?? null,
3007
- forceRegister: input.forceRegister ?? false
3008
- });
3009
- }
3010
- function computeCacheKey(input) {
3011
- return stableStringify({
3012
- targetChain: input.targetChain,
3013
- targetToken: normalizeKeyToken(input.targetToken),
3014
- recipient: normalizeKeyToken(input.recipient),
3015
- outputTokenRules: input.outputTokenRules ?? null,
3016
- rejectUnmapped: input.rejectUnmapped ?? null
3017
- });
3018
- }
3019
- function computeIsCacheable(input) {
3020
- return !input.forceRegister;
3021
- }
3022
- var SETUP_REQUEST_DEDUPE_TTL_MS = 3e4;
3023
- var setupRequestDedupe = /* @__PURE__ */ new Map();
3024
- function readDedupedAccountSetupResult(requestKey) {
3025
- const now = Date.now();
3026
- const existing = setupRequestDedupe.get(requestKey);
3027
- if (existing?.settled && existing.expiresAt > now && existing.result) {
3028
- return existing.result;
3029
- }
3030
- return null;
3031
- }
3032
- async function runAccountSetup(input, deps) {
3033
- const { service, debug } = deps;
3034
- const cacheKey = computeCacheKey(input);
3035
- if (!input.recipient) {
3036
- throw new SetupError(
3037
- "A recipient is required to open a deposit account.",
3038
- "client"
3039
- );
3040
- }
3041
- if (!recipientMatchesTarget(input.recipient, input.targetChain)) {
3042
- throw new SetupError(
3043
- RECIPIENT_HINTS[recipientNamespace(input.targetChain)] ?? "An EVM destination needs a 0x recipient address.",
3044
- "client"
3045
- );
3046
- }
3047
- try {
3048
- const salt = deriveAccountSalt(input);
3049
- debugLog(debug, "account-setup", "setup:start", {
3050
- salt,
3051
- recipient: input.recipient,
3052
- targetChain: input.targetChain,
3053
- forceRegister: input.forceRegister ?? false
3054
- });
3055
- const result = await service.registerManagedAccount({
3056
- salt,
3057
- target: {
3058
- chain: targetChainToCaip2(input.targetChain),
3059
- token: input.targetToken,
3060
- recipient: input.recipient,
3061
- ...input.outputTokenRules?.length && {
3062
- outputTokenRules: input.outputTokenRules
3063
- },
3064
- ...input.rejectUnmapped != null && {
3065
- rejectUnmapped: input.rejectUnmapped
3066
- }
3067
- }
3068
- });
3069
- return {
3070
- smartAccount: result.evmDepositAddress,
3071
- solanaDepositAddress: result.solanaDepositAddress,
3072
- cacheKey
3073
- };
3074
- } catch (error) {
3075
- debugError(debug, "account-setup", "setup:failed", error, {
3076
- recipient: input.recipient
3077
- });
3078
- if (error instanceof SetupError) throw error;
3079
- throw mapError(error);
3080
- }
3081
- }
3082
- function runAccountSetupDeduped(input, deps, requestKey) {
3083
- const now = Date.now();
3084
- const existing = setupRequestDedupe.get(requestKey);
3085
- if (existing && (!existing.settled || existing.expiresAt > now)) {
3086
- debugLog(deps.debug, "account-setup", "setup:dedupe-hit", {
3087
- recipient: input.recipient,
3088
- forceRegister: input.forceRegister ?? false
3089
- });
3090
- return existing.promise;
3091
- }
3092
- const entry = {
3093
- promise: runAccountSetup(input, deps),
3094
- settled: false,
3095
- expiresAt: Number.POSITIVE_INFINITY,
3096
- result: null
3097
- };
3098
- setupRequestDedupe.set(requestKey, entry);
3099
- entry.promise.then((result) => {
3100
- entry.settled = true;
3101
- entry.result = result;
3102
- entry.expiresAt = Date.now() + SETUP_REQUEST_DEDUPE_TTL_MS;
3103
- const cleanupTimer = setTimeout(() => {
3104
- if (setupRequestDedupe.get(requestKey) === entry) {
3105
- setupRequestDedupe.delete(requestKey);
3106
- }
3107
- }, SETUP_REQUEST_DEDUPE_TTL_MS);
3108
- if (typeof cleanupTimer === "object" && cleanupTimer && "unref" in cleanupTimer && typeof cleanupTimer.unref === "function") {
3109
- cleanupTimer.unref();
3110
- }
3111
- }).catch(() => {
3112
- if (setupRequestDedupe.get(requestKey) === entry) {
3113
- setupRequestDedupe.delete(requestKey);
3114
- }
3115
- });
3116
- return entry.promise;
3117
- }
3118
- function mapError(error) {
3119
- if (error instanceof TypeError) {
3120
- return new SetupError(
3121
- "Couldn't reach the deposit service. Please check your connection.",
3122
- "network"
3123
- );
3124
- }
3125
- if (error instanceof RegistrationError) {
3126
- if (error.status >= 500) {
3127
- return new SetupError(
3128
- "Deposit service is temporarily unavailable. Please try again shortly.",
3129
- "server"
3130
- );
3131
- }
3132
- return new SetupError(error.message, "client");
3133
- }
3134
- return new SetupError(
3135
- "Deposit service is temporarily unavailable. Please try again shortly.",
3136
- "unknown"
3137
- );
3138
- }
3139
-
3140
- export {
3141
- getExchangeLogoSrc,
3142
- getExchangeLogo,
3143
- Tooltip,
3144
- markSubmissionUncertain,
3145
- isSubmissionUncertain,
3146
- getEventTxHash,
3147
- getEventSourceDetails,
3148
- isDepositEvent,
3149
- isFailedEvent,
3150
- txRefsMatch,
3151
- failureMessageForEvent,
3152
- fetchTokenPriceUsd,
3153
- useTokenPrices,
3154
- ProcessingStep,
3155
- SetupError,
3156
- deriveAccountSalt,
3157
- computeRequestKey,
3158
- computeCacheKey,
3159
- computeIsCacheable,
3160
- readDedupedAccountSetupResult,
3161
- runAccountSetupDeduped,
3162
- HistoryEntryButton,
3163
- HistoryOverlay,
3164
- useHistoryPanel,
3165
- warnRemovedProps,
3166
- isHistoryEntryVisible,
3167
- createHistoryAnalytics,
3168
- analyticsTargetProperties
3169
- };