@rhinestone/deposit-modal 0.8.1 → 0.9.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 (65) hide show
  1. package/README.md +193 -29
  2. package/dist/{chunk-2JPB3PLB.mjs → DepositModalReown-6KUAO5ZC.mjs} +56 -4
  3. package/dist/{chunk-Q3W53BKR.cjs → DepositModalReown-XC5RIU7Z.cjs} +62 -10
  4. package/dist/{caip-C_ZYFIGa.d.cts → caip-523Zrc3r.d.cts} +1 -3
  5. package/dist/{caip-C_ZYFIGa.d.ts → caip-523Zrc3r.d.ts} +1 -3
  6. package/dist/{chunk-D3Y7ZBED.cjs → chunk-22RV33LK.cjs} +2170 -1460
  7. package/dist/chunk-4WB2FHGC.mjs +865 -0
  8. package/dist/chunk-A223BLVC.mjs +801 -0
  9. package/dist/{chunk-HQWZCOMS.cjs → chunk-DIX7M3SP.cjs} +232 -601
  10. package/dist/{chunk-T43VUEJH.mjs → chunk-E4G5BOZP.mjs} +167 -536
  11. package/dist/chunk-H577RJN5.cjs +801 -0
  12. package/dist/{chunk-DABKCJVL.mjs → chunk-ILT2PITN.mjs} +60 -3
  13. package/dist/{chunk-7KHEIMWD.mjs → chunk-KKHLNFSA.mjs} +2436 -1726
  14. package/dist/chunk-LGVJWVSS.mjs +81 -0
  15. package/dist/chunk-NEXUCY4B.cjs +11 -0
  16. package/dist/chunk-NMIMRASD.cjs +1785 -0
  17. package/dist/chunk-OSI2CFGL.mjs +11 -0
  18. package/dist/chunk-PAGMU4RQ.cjs +865 -0
  19. package/dist/chunk-T7LGQFP3.cjs +81 -0
  20. package/dist/chunk-VN4BLZYC.cjs +2273 -0
  21. package/dist/chunk-VOGZAZB2.mjs +2273 -0
  22. package/dist/chunk-WYXBY2MQ.mjs +1785 -0
  23. package/dist/{chunk-4NN7UTWQ.cjs → chunk-YEBXUUH3.cjs} +63 -6
  24. package/dist/claim.cjs +10 -0
  25. package/dist/claim.d.cts +8 -0
  26. package/dist/claim.d.ts +8 -0
  27. package/dist/claim.mjs +10 -0
  28. package/dist/constants.cjs +3 -3
  29. package/dist/constants.d.cts +4 -2
  30. package/dist/constants.d.ts +4 -2
  31. package/dist/constants.mjs +5 -5
  32. package/dist/deposit.cjs +8 -6
  33. package/dist/deposit.d.cts +2 -2
  34. package/dist/deposit.d.ts +2 -2
  35. package/dist/deposit.mjs +7 -5
  36. package/dist/index.cjs +22 -12
  37. package/dist/index.d.cts +15 -2
  38. package/dist/index.d.ts +15 -2
  39. package/dist/index.mjs +21 -11
  40. package/dist/polymarket.cjs +5 -6
  41. package/dist/polymarket.mjs +2 -3
  42. package/dist/server.cjs +96 -0
  43. package/dist/server.d.cts +65 -0
  44. package/dist/server.d.ts +65 -0
  45. package/dist/server.mjs +96 -0
  46. package/dist/styles.css +263 -12
  47. package/dist/types-C0SGf-21.d.cts +606 -0
  48. package/dist/types-C0SGf-21.d.ts +606 -0
  49. package/dist/withdraw.cjs +6 -5
  50. package/dist/withdraw.d.cts +3 -6
  51. package/dist/withdraw.d.ts +3 -6
  52. package/dist/withdraw.mjs +5 -4
  53. package/package.json +53 -6
  54. package/dist/DepositModalReown-4BSHOQ5Y.mjs +0 -63
  55. package/dist/DepositModalReown-UND3DEJH.cjs +0 -63
  56. package/dist/WithdrawModalReown-FVXSSGY5.mjs +0 -46
  57. package/dist/WithdrawModalReown-TVZWJKQI.cjs +0 -46
  58. package/dist/chunk-7OKA6GNA.cjs +0 -4537
  59. package/dist/chunk-NASHLEVQ.mjs +0 -4537
  60. package/dist/chunk-O5OKA27M.mjs +0 -800
  61. package/dist/chunk-U7SVYWVD.cjs +0 -800
  62. package/dist/chunk-WK5AFRSP.mjs +0 -295
  63. package/dist/chunk-XRWQMIBY.cjs +0 -295
  64. package/dist/types--LQWg_4W.d.cts +0 -468
  65. package/dist/types--LQWg_4W.d.ts +0 -468
@@ -0,0 +1,865 @@
1
+ import {
2
+ ListRow
3
+ } from "./chunk-LGVJWVSS.mjs";
4
+ import {
5
+ AlertTriangleIcon,
6
+ BodyHeader,
7
+ Button,
8
+ Callout,
9
+ CdnIcon,
10
+ CheckIcon,
11
+ ChevronLeftIcon,
12
+ CloseIcon,
13
+ CoinsIcon,
14
+ ExternalLinkIcon,
15
+ HandCoinsIcon,
16
+ Modal,
17
+ PoweredBy,
18
+ RpcUrlsProvider,
19
+ TokenIcon,
20
+ applyTheme,
21
+ checkBackendUrl,
22
+ createDepositService,
23
+ debugError,
24
+ debugLog,
25
+ formatTokenAmount,
26
+ formatUserError,
27
+ useLatestRef,
28
+ useStableRpcUrls
29
+ } from "./chunk-VOGZAZB2.mjs";
30
+ import {
31
+ SERVICE_HEADERS
32
+ } from "./chunk-OSI2CFGL.mjs";
33
+ import {
34
+ getChainIcon,
35
+ getChainName,
36
+ getExplorerTxUrl,
37
+ getTokenDecimalsByAddress,
38
+ getTokenSymbol,
39
+ tokenIconUrl
40
+ } from "./chunk-A223BLVC.mjs";
41
+
42
+ // src/ClaimModal.tsx
43
+ import {
44
+ useCallback,
45
+ useEffect,
46
+ useMemo,
47
+ useReducer,
48
+ useRef,
49
+ useState
50
+ } from "react";
51
+ import { formatUnits, isAddress } from "viem";
52
+
53
+ // src/components/ui/Input.tsx
54
+ import { forwardRef } from "react";
55
+ import { jsx, jsxs } from "react/jsx-runtime";
56
+ var Input = forwardRef(
57
+ ({ label, error, variant, className = "", ...props }, ref) => {
58
+ const inputVariant = error ? "error" : variant || "default";
59
+ return /* @__PURE__ */ jsxs("div", { className: "rs-input-wrapper", children: [
60
+ label && /* @__PURE__ */ jsx("label", { className: "rs-input-label", children: label }),
61
+ /* @__PURE__ */ jsx(
62
+ "input",
63
+ {
64
+ ref,
65
+ className: `rs-input rs-input--${inputVariant} ${className}`,
66
+ ...props
67
+ }
68
+ ),
69
+ error && /* @__PURE__ */ jsxs("p", { className: "rs-input-error", children: [
70
+ /* @__PURE__ */ jsx(AlertTriangleIcon, { className: "rs-input-error-icon" }),
71
+ error
72
+ ] })
73
+ ] });
74
+ }
75
+ );
76
+ Input.displayName = "Input";
77
+
78
+ // src/core/recovery.ts
79
+ var RECOVER_DOMAIN = {
80
+ name: "Rhinestone Deposit Recovery",
81
+ version: "1"
82
+ };
83
+ var RECOVER_TYPES = {
84
+ RecoverDeposit: [
85
+ { name: "depositId", type: "uint256" },
86
+ { name: "destination", type: "address" }
87
+ ]
88
+ };
89
+ var RecoveryError = class extends Error {
90
+ constructor(message, status, code, retryable = false) {
91
+ super(message);
92
+ this.name = "RecoveryError";
93
+ this.status = status;
94
+ this.code = code;
95
+ this.retryable = retryable;
96
+ }
97
+ };
98
+ function formatRecoveryError(status, code, serverMessage) {
99
+ switch (code) {
100
+ case "DEPOSIT_NOT_RECOVERABLE":
101
+ return {
102
+ message: "This deposit can't be recovered \u2014 it may already have been claimed.",
103
+ retryable: false
104
+ };
105
+ case "RECOVERY_UNSUPPORTED":
106
+ return {
107
+ message: "This deposit can't be recovered automatically. Contact support to get your funds back.",
108
+ retryable: false
109
+ };
110
+ case "SIGNATURE_INVALID":
111
+ return {
112
+ message: "That signature wasn't accepted for this wallet. Contact support to recover this deposit.",
113
+ retryable: false
114
+ };
115
+ case "VERIFICATION_UNAVAILABLE":
116
+ return {
117
+ message: "Couldn't verify your signature just now. Try again shortly.",
118
+ retryable: true
119
+ };
120
+ case "REFUND_RECONCILIATION_REQUIRED":
121
+ return {
122
+ message: "Your recovery is being reviewed. Contact support with this deposit if it doesn't arrive.",
123
+ retryable: false
124
+ };
125
+ case "REFUND_FAILED":
126
+ return {
127
+ message: serverMessage ?? "The recovery couldn't be completed. Please try again or contact support.",
128
+ retryable: true
129
+ };
130
+ default:
131
+ if (status === 0) {
132
+ return {
133
+ message: "Couldn't reach the recovery service. Check your connection and try again.",
134
+ retryable: true
135
+ };
136
+ }
137
+ return {
138
+ message: serverMessage ?? "The recovery couldn't be completed. Please try again or contact support.",
139
+ retryable: true
140
+ };
141
+ }
142
+ }
143
+ async function submitSignedRecovery(params) {
144
+ const { backendUrl, depositId, signer, destination, signRecovery, debug } = params;
145
+ const scope = "service:recovery";
146
+ const typedData = {
147
+ domain: RECOVER_DOMAIN,
148
+ types: RECOVER_TYPES,
149
+ primaryType: "RecoverDeposit",
150
+ message: { depositId: BigInt(depositId), destination }
151
+ };
152
+ let signature;
153
+ try {
154
+ signature = await signRecovery({ typedData, signer, depositId, destination });
155
+ } catch (error) {
156
+ debugError(debug, scope, "signRecovery:failed", error);
157
+ throw new RecoveryError(
158
+ "Signature request was declined. Approve it in your wallet to recover this deposit.",
159
+ 0,
160
+ void 0,
161
+ true
162
+ );
163
+ }
164
+ if (!signature) {
165
+ throw new RecoveryError(
166
+ "Signature request was declined. Approve it in your wallet to recover this deposit.",
167
+ 0,
168
+ void 0,
169
+ true
170
+ );
171
+ }
172
+ const url = `${backendUrl.replace(/\/$/, "")}/deposits/recover`;
173
+ debugLog(debug, scope, "submitSignedRecovery:request", {
174
+ url,
175
+ depositId,
176
+ signer
177
+ });
178
+ let response;
179
+ try {
180
+ response = await fetch(url, {
181
+ method: "POST",
182
+ // The shared const, so recovery carries `x-deposit-modal-version` like
183
+ // every other backend call. Without it this path would be invisible in the
184
+ // per-client version telemetry the processor records.
185
+ headers: SERVICE_HEADERS,
186
+ // `depositId` as a string: it is a uint256 upstream and JSON numbers lose
187
+ // precision well before that range.
188
+ body: JSON.stringify({ depositId, destination, signature })
189
+ });
190
+ } catch (error) {
191
+ debugError(debug, scope, "submitSignedRecovery:network", error);
192
+ const { message, retryable } = formatRecoveryError(0);
193
+ throw new RecoveryError(message, 0, void 0, retryable);
194
+ }
195
+ if (!response.ok) {
196
+ const body = await response.json().catch(() => ({}));
197
+ debugError(debug, scope, "submitSignedRecovery:failed", body, {
198
+ status: response.status
199
+ });
200
+ const { message, retryable } = formatRecoveryError(
201
+ response.status,
202
+ body.code,
203
+ body.error
204
+ );
205
+ throw new RecoveryError(message, response.status, body.code, retryable);
206
+ }
207
+ const result = await response.json();
208
+ debugLog(debug, scope, "submitSignedRecovery:success", {
209
+ transactionHash: result.transactionHash
210
+ });
211
+ return result;
212
+ }
213
+
214
+ // src/ClaimModal.tsx
215
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
216
+ function evmChainIdFromCaip2(chain) {
217
+ if (!chain?.startsWith("eip155:")) return null;
218
+ const id = Number.parseInt(chain.slice("eip155:".length), 10);
219
+ return Number.isFinite(id) ? id : null;
220
+ }
221
+ function isEligible(row) {
222
+ return row.status === "failed" || row.status === "rejected";
223
+ }
224
+ function describeDeposit(row) {
225
+ const chainId = evmChainIdFromCaip2(row.chain) ?? 0;
226
+ const decimals = getTokenDecimalsByAddress(row.token, chainId);
227
+ const symbol = getTokenSymbol(row.token, chainId);
228
+ const raw = BigInt(row.sourceAmount ?? row.amount ?? "0");
229
+ const human = Number(formatUnits(raw, decimals));
230
+ const amount = formatTokenAmount(human, symbol) ?? formatUnits(raw, decimals);
231
+ return {
232
+ chainId,
233
+ symbol,
234
+ amount,
235
+ chainName: getChainName(chainId),
236
+ chainIcon: getChainIcon(chainId)
237
+ };
238
+ }
239
+ function shortenHash(hash) {
240
+ return `${hash.slice(0, 10)}\u2026${hash.slice(-8)}`;
241
+ }
242
+ function shortenAddress(addr) {
243
+ return addr.length > 12 ? `${addr.slice(0, 6)}\u2026${addr.slice(-4)}` : addr;
244
+ }
245
+ function humanizeStatus(status) {
246
+ if (!status) return void 0;
247
+ const spaced = status.replace(/[_-]+/g, " ").trim();
248
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
249
+ }
250
+ var INITIAL_STATE = {
251
+ step: "lookup",
252
+ deposits: [],
253
+ selected: null,
254
+ result: null,
255
+ error: null,
256
+ retryable: true
257
+ };
258
+ function claimReducer(state, action) {
259
+ switch (action.type) {
260
+ case "lookup/results":
261
+ return {
262
+ ...state,
263
+ deposits: action.deposits,
264
+ selected: action.autoSelect,
265
+ step: action.autoSelect ? "review" : "select",
266
+ error: null
267
+ };
268
+ case "select":
269
+ return { ...state, selected: action.deposit, step: "review", error: null };
270
+ case "back":
271
+ if (state.step === "failed") return { ...state, step: "review", error: null };
272
+ if (state.step === "review")
273
+ return {
274
+ ...state,
275
+ step: state.deposits.length > 1 ? "select" : "lookup",
276
+ error: null
277
+ };
278
+ if (state.step === "select") return { ...INITIAL_STATE };
279
+ return state;
280
+ case "submit":
281
+ return { ...state, step: "processing", error: null };
282
+ case "complete":
283
+ return {
284
+ ...state,
285
+ step: "complete",
286
+ result: {
287
+ txHash: action.txHash,
288
+ amount: action.amount,
289
+ amountDisplay: action.amountDisplay,
290
+ symbol: action.symbol,
291
+ destination: action.destination,
292
+ chain: action.chain
293
+ }
294
+ };
295
+ case "failed":
296
+ return {
297
+ ...state,
298
+ step: "failed",
299
+ error: action.error,
300
+ retryable: action.retryable ?? true
301
+ };
302
+ case "reset":
303
+ return { ...INITIAL_STATE };
304
+ default:
305
+ return state;
306
+ }
307
+ }
308
+ function ClaimModal({
309
+ signRecovery,
310
+ isOpen,
311
+ onClose,
312
+ inline,
313
+ closeOnOverlayClick,
314
+ backendUrl,
315
+ rpcUrls: rpcUrlsProp,
316
+ defaultTxHash,
317
+ defaultRefundDestination,
318
+ theme,
319
+ uiConfig,
320
+ className,
321
+ onReady,
322
+ onLifecycle,
323
+ onEvent,
324
+ onError,
325
+ debug
326
+ }) {
327
+ checkBackendUrl("ClaimModal", backendUrl);
328
+ const modalRef = useRef(null);
329
+ const onReadyRef = useLatestRef(onReady);
330
+ const onLifecycleRef = useLatestRef(onLifecycle);
331
+ const onEventRef = useLatestRef(onEvent);
332
+ const onErrorRef = useLatestRef(onError);
333
+ const rpcUrls = useStableRpcUrls(rpcUrlsProp);
334
+ const service = useMemo(
335
+ () => createDepositService(backendUrl, { debug, debugScope: "service:claim" }),
336
+ [backendUrl, debug]
337
+ );
338
+ const [state, dispatch] = useReducer(claimReducer, INITIAL_STATE);
339
+ const operationEpochRef = useRef(0);
340
+ const isOpenRef = useRef(isOpen);
341
+ isOpenRef.current = isOpen;
342
+ useEffect(() => {
343
+ if (isOpen && modalRef.current) applyTheme(modalRef.current, theme);
344
+ }, [isOpen, theme]);
345
+ useEffect(() => {
346
+ if (isOpen) {
347
+ onReadyRef.current?.();
348
+ onEventRef.current?.({ type: "claim_modal_open" });
349
+ }
350
+ }, [isOpen, onReadyRef, onEventRef]);
351
+ useEffect(() => {
352
+ if (!isOpen) {
353
+ operationEpochRef.current += 1;
354
+ dispatch({ type: "reset" });
355
+ }
356
+ }, [isOpen]);
357
+ const handleClose = useCallback(() => {
358
+ if (state.step !== "processing") {
359
+ operationEpochRef.current += 1;
360
+ onClose();
361
+ }
362
+ }, [state.step, onClose]);
363
+ const canGoBack = state.step === "select" || state.step === "review" || state.step === "failed";
364
+ const showBackButton = uiConfig?.showBackButton ?? true;
365
+ return /* @__PURE__ */ jsx2(RpcUrlsProvider, { value: rpcUrls, children: /* @__PURE__ */ jsx2(
366
+ Modal,
367
+ {
368
+ isOpen,
369
+ onClose: handleClose,
370
+ className,
371
+ inline,
372
+ closeOnOverlayClick,
373
+ children: /* @__PURE__ */ jsxs2("div", { ref: modalRef, className: "rs-modal", children: [
374
+ /* @__PURE__ */ jsxs2("div", { className: "rs-modal-header--redesigned", children: [
375
+ /* @__PURE__ */ jsx2("div", { className: "rs-modal-header-nav-left", children: showBackButton && canGoBack && /* @__PURE__ */ jsx2(
376
+ "button",
377
+ {
378
+ type: "button",
379
+ className: "rs-modal-header-back",
380
+ "aria-label": "Go back",
381
+ onClick: () => dispatch({ type: "back" }),
382
+ children: /* @__PURE__ */ jsx2(ChevronLeftIcon, {})
383
+ }
384
+ ) }),
385
+ /* @__PURE__ */ jsx2("div", { className: "rs-modal-header-nav-right", children: /* @__PURE__ */ jsx2(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ onClick: handleClose,
390
+ disabled: state.step === "processing",
391
+ className: "rs-modal-close",
392
+ "aria-label": "Close",
393
+ children: /* @__PURE__ */ jsx2(CloseIcon, {})
394
+ }
395
+ ) })
396
+ ] }),
397
+ /* @__PURE__ */ jsx2("div", { className: "rs-modal-body", children: /* @__PURE__ */ jsx2(
398
+ ClaimBody,
399
+ {
400
+ state,
401
+ dispatch,
402
+ service,
403
+ signRecovery,
404
+ backendUrl,
405
+ debug,
406
+ defaultTxHash,
407
+ defaultRefundDestination,
408
+ onClose: handleClose,
409
+ operationEpochRef,
410
+ isOpenRef,
411
+ onLifecycleRef,
412
+ onEventRef,
413
+ onErrorRef
414
+ }
415
+ ) })
416
+ ] })
417
+ }
418
+ ) });
419
+ }
420
+ function ClaimBody({
421
+ state,
422
+ dispatch,
423
+ service,
424
+ signRecovery,
425
+ backendUrl,
426
+ debug,
427
+ defaultTxHash,
428
+ defaultRefundDestination,
429
+ onClose,
430
+ operationEpochRef,
431
+ isOpenRef,
432
+ onLifecycleRef,
433
+ onEventRef,
434
+ onErrorRef
435
+ }) {
436
+ switch (state.step) {
437
+ case "lookup":
438
+ return /* @__PURE__ */ jsx2(
439
+ LookupStep,
440
+ {
441
+ service,
442
+ defaultTxHash,
443
+ operationEpochRef,
444
+ isOpenRef,
445
+ onResults: (deposits) => {
446
+ const eligible = deposits.filter(isEligible);
447
+ const autoSelect = deposits.length === 1 && eligible.length === 1 ? deposits[0] : null;
448
+ dispatch({ type: "lookup/results", deposits, autoSelect });
449
+ },
450
+ onLifecycleRef,
451
+ onEventRef,
452
+ onErrorRef
453
+ }
454
+ );
455
+ case "select":
456
+ return /* @__PURE__ */ jsx2(
457
+ SelectStep,
458
+ {
459
+ deposits: state.deposits,
460
+ onSelect: (deposit) => dispatch({ type: "select", deposit })
461
+ }
462
+ );
463
+ case "review":
464
+ case "processing":
465
+ return /* @__PURE__ */ jsx2(
466
+ ReviewStep,
467
+ {
468
+ deposit: state.selected,
469
+ submitting: state.step === "processing",
470
+ defaultRefundDestination,
471
+ onSubmit: (destination) => submitRecoveryRequest(destination)
472
+ }
473
+ );
474
+ case "complete":
475
+ return /* @__PURE__ */ jsx2(ResultStep, { result: state.result, onClose, onReset: () => dispatch({ type: "reset" }) });
476
+ case "failed":
477
+ return /* @__PURE__ */ jsx2(
478
+ ResultStep,
479
+ {
480
+ error: state.error,
481
+ onClose,
482
+ onRetry: state.retryable ? () => dispatch({ type: "back" }) : void 0
483
+ }
484
+ );
485
+ default:
486
+ return null;
487
+ }
488
+ async function submitRecoveryRequest(destination) {
489
+ const deposit = state.selected;
490
+ if (!deposit) return;
491
+ const sourceChainId = evmChainIdFromCaip2(deposit.chain);
492
+ if (sourceChainId == null || !deposit.id || !deposit.recipient || !deposit.depositAddress || !deposit.token || !deposit.chain) {
493
+ dispatch({
494
+ type: "failed",
495
+ error: "Deposit is missing data required to recover it."
496
+ });
497
+ return;
498
+ }
499
+ const signer = deposit.recipient;
500
+ const account = deposit.depositAddress;
501
+ const token = deposit.token;
502
+ const operationEpoch = operationEpochRef.current;
503
+ const isCurrentOperation = () => isOpenRef.current && operationEpochRef.current === operationEpoch;
504
+ dispatch({ type: "submit" });
505
+ onEventRef.current?.({
506
+ type: "claim_modal_refund_click",
507
+ chain: `eip155:${sourceChainId}`
508
+ });
509
+ onLifecycleRef.current?.({
510
+ type: "refund_requested",
511
+ account,
512
+ destination,
513
+ chain: sourceChainId
514
+ });
515
+ try {
516
+ const result = await submitSignedRecovery({
517
+ backendUrl,
518
+ depositId: deposit.id,
519
+ signer,
520
+ destination,
521
+ signRecovery,
522
+ debug
523
+ });
524
+ if (!isCurrentOperation()) return;
525
+ const paidTo = destination;
526
+ const decimals = getTokenDecimalsByAddress(token, sourceChainId);
527
+ const symbol = getTokenSymbol(token, sourceChainId);
528
+ const refundedRaw = BigInt(
529
+ result.amount ?? deposit.sourceAmount ?? deposit.amount ?? "0"
530
+ );
531
+ const amountDisplay = formatTokenAmount(Number(formatUnits(refundedRaw, decimals)), symbol) ?? formatUnits(refundedRaw, decimals);
532
+ dispatch({
533
+ type: "complete",
534
+ txHash: result.transactionHash,
535
+ amount: result.amount,
536
+ amountDisplay,
537
+ symbol,
538
+ destination: paidTo,
539
+ chain: sourceChainId
540
+ });
541
+ onEventRef.current?.({
542
+ type: "claim_modal_complete",
543
+ chain: `eip155:${sourceChainId}`
544
+ });
545
+ onLifecycleRef.current?.({
546
+ type: "complete",
547
+ txHash: result.transactionHash,
548
+ account,
549
+ destination: paidTo,
550
+ chain: sourceChainId,
551
+ token,
552
+ amount: result.amount
553
+ });
554
+ } catch (err) {
555
+ if (!isCurrentOperation()) return;
556
+ const recoveryError = err instanceof RecoveryError ? err : void 0;
557
+ const status = recoveryError?.status;
558
+ const message = recoveryError?.message ?? formatUserError(
559
+ err instanceof Error ? err.message : "Recovery failed"
560
+ );
561
+ dispatch({
562
+ type: "failed",
563
+ error: message,
564
+ retryable: recoveryError?.retryable ?? true
565
+ });
566
+ onEventRef.current?.({
567
+ type: "claim_modal_failed",
568
+ chain: `eip155:${sourceChainId}`,
569
+ status
570
+ });
571
+ onLifecycleRef.current?.({ type: "failed", status, error: message });
572
+ onErrorRef.current?.({ message, code: "CLAIM_FAILED" });
573
+ }
574
+ }
575
+ }
576
+ function LookupStep({
577
+ service,
578
+ defaultTxHash,
579
+ operationEpochRef,
580
+ isOpenRef,
581
+ onResults,
582
+ onLifecycleRef,
583
+ onEventRef,
584
+ onErrorRef
585
+ }) {
586
+ const [txHash, setTxHash] = useState(defaultTxHash ?? "");
587
+ const [loading, setLoading] = useState(false);
588
+ const [error, setError] = useState(null);
589
+ const trimmed = txHash.trim();
590
+ const validTxHash = /^0x[a-fA-F0-9]{64}$/.test(trimmed);
591
+ const handleLookup = useCallback(async () => {
592
+ const operationEpoch = operationEpochRef.current;
593
+ const isCurrentOperation = () => isOpenRef.current && operationEpochRef.current === operationEpoch;
594
+ setError(null);
595
+ setLoading(true);
596
+ try {
597
+ const deposits = await service.lookupRecoverableDeposits({
598
+ txHash: trimmed
599
+ });
600
+ if (!isCurrentOperation()) return;
601
+ onLifecycleRef.current?.({
602
+ type: "lookup",
603
+ txHash: trimmed,
604
+ matches: deposits.length
605
+ });
606
+ onEventRef.current?.({
607
+ type: "claim_modal_lookup",
608
+ matches: deposits.length
609
+ });
610
+ if (deposits.length === 0) {
611
+ setError("No deposits found for this transaction.");
612
+ return;
613
+ }
614
+ if (!deposits.some(isEligible)) {
615
+ setError(
616
+ "This transaction has no failed or rejected deposits available to recover."
617
+ );
618
+ return;
619
+ }
620
+ onResults(deposits);
621
+ } catch (err) {
622
+ if (!isCurrentOperation()) return;
623
+ const message = formatUserError(
624
+ err instanceof Error ? err.message : "Lookup failed"
625
+ );
626
+ setError(message);
627
+ onErrorRef.current?.({ message, code: "CLAIM_LOOKUP_FAILED" });
628
+ } finally {
629
+ if (isCurrentOperation()) setLoading(false);
630
+ }
631
+ }, [
632
+ trimmed,
633
+ service,
634
+ operationEpochRef,
635
+ isOpenRef,
636
+ onResults,
637
+ onLifecycleRef,
638
+ onEventRef,
639
+ onErrorRef
640
+ ]);
641
+ return /* @__PURE__ */ jsxs2("div", { className: "rs-screen", children: [
642
+ /* @__PURE__ */ jsxs2("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
643
+ /* @__PURE__ */ jsx2(
644
+ BodyHeader,
645
+ {
646
+ icon: /* @__PURE__ */ jsx2(HandCoinsIcon, {}),
647
+ title: "Claim a deposit",
648
+ subtitle: "Get funds from a failed deposit sent back to you on the source chain."
649
+ }
650
+ ),
651
+ /* @__PURE__ */ jsx2(
652
+ Input,
653
+ {
654
+ label: "Transaction hash",
655
+ placeholder: "0x\u2026",
656
+ value: txHash,
657
+ onChange: (e) => setTxHash(e.target.value),
658
+ error: trimmed.length > 0 && !validTxHash ? "Enter a valid transaction hash" : void 0
659
+ }
660
+ ),
661
+ error && /* @__PURE__ */ jsx2(Callout, { variant: "error", children: error }),
662
+ /* @__PURE__ */ jsx2(
663
+ Button,
664
+ {
665
+ onClick: handleLookup,
666
+ loading,
667
+ loadingText: "Searching\u2026",
668
+ disabled: !validTxHash,
669
+ fullWidth: true,
670
+ children: "Find deposit"
671
+ }
672
+ )
673
+ ] }),
674
+ /* @__PURE__ */ jsx2(PoweredBy, {})
675
+ ] });
676
+ }
677
+ function SelectStep({
678
+ deposits,
679
+ onSelect
680
+ }) {
681
+ return /* @__PURE__ */ jsxs2("div", { className: "rs-screen", children: [
682
+ /* @__PURE__ */ jsxs2("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
683
+ /* @__PURE__ */ jsx2(
684
+ BodyHeader,
685
+ {
686
+ icon: /* @__PURE__ */ jsx2(CoinsIcon, {}),
687
+ title: "Select a deposit",
688
+ subtitle: "This transaction produced multiple deposits. Choose the one to claim."
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx2("div", { className: "rs-list", children: deposits.map((row) => {
692
+ const { symbol, amount, chainName } = describeDeposit(row);
693
+ const eligible = isEligible(row);
694
+ return /* @__PURE__ */ jsx2(
695
+ ListRow,
696
+ {
697
+ leadingMedia: tokenIconUrl(symbol),
698
+ title: `${amount} ${symbol}`,
699
+ subtitle: chainName,
700
+ meta: eligible ? void 0 : humanizeStatus(row.status),
701
+ disabled: !eligible,
702
+ onClick: () => eligible && onSelect(row)
703
+ },
704
+ row.id ?? `${row.depositAddress}:${row.token}`
705
+ );
706
+ }) })
707
+ ] }),
708
+ /* @__PURE__ */ jsx2(PoweredBy, {})
709
+ ] });
710
+ }
711
+ function ReviewStep({
712
+ deposit,
713
+ submitting,
714
+ defaultRefundDestination,
715
+ onSubmit
716
+ }) {
717
+ const { chainId, symbol, amount, chainName, chainIcon } = describeDeposit(deposit);
718
+ const [destination, setDestination] = useState(defaultRefundDestination ?? "");
719
+ const destinationValid = isAddress(destination);
720
+ return /* @__PURE__ */ jsxs2("div", { className: "rs-screen", children: [
721
+ /* @__PURE__ */ jsxs2("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
722
+ /* @__PURE__ */ jsx2(
723
+ BodyHeader,
724
+ {
725
+ icon: /* @__PURE__ */ jsx2(HandCoinsIcon, {}),
726
+ title: "Review refund",
727
+ subtitle: "This returns the funds on the source chain. It does not retry the original bridge."
728
+ }
729
+ ),
730
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-details", children: [
731
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-detail-row", children: [
732
+ /* @__PURE__ */ jsx2("span", { children: "Amount" }),
733
+ /* @__PURE__ */ jsxs2("span", { className: "rs-review-detail-value", children: [
734
+ /* @__PURE__ */ jsxs2("span", { children: [
735
+ amount,
736
+ " ",
737
+ symbol
738
+ ] }),
739
+ /* @__PURE__ */ jsx2("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx2(TokenIcon, { symbol }) })
740
+ ] })
741
+ ] }),
742
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-detail-row", children: [
743
+ /* @__PURE__ */ jsx2("span", { children: "Source chain" }),
744
+ /* @__PURE__ */ jsxs2("span", { className: "rs-review-detail-value", children: [
745
+ /* @__PURE__ */ jsx2("span", { children: chainName }),
746
+ chainIcon && /* @__PURE__ */ jsx2("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx2(CdnIcon, { src: chainIcon }) })
747
+ ] })
748
+ ] }),
749
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-detail-row", children: [
750
+ /* @__PURE__ */ jsx2("span", { children: "Deposit transaction" }),
751
+ (() => {
752
+ const url = deposit.txHash ? getExplorerTxUrl(chainId, deposit.txHash) : null;
753
+ const short = deposit.txHash ? shortenHash(deposit.txHash) : "\u2014";
754
+ return url ? /* @__PURE__ */ jsxs2(
755
+ "a",
756
+ {
757
+ className: "rs-review-detail-value rs-link",
758
+ href: url,
759
+ target: "_blank",
760
+ rel: "noreferrer",
761
+ children: [
762
+ short,
763
+ /* @__PURE__ */ jsx2(ExternalLinkIcon, {})
764
+ ]
765
+ }
766
+ ) : /* @__PURE__ */ jsx2("span", { className: "rs-review-detail-value", children: short });
767
+ })()
768
+ ] })
769
+ ] }),
770
+ /* @__PURE__ */ jsx2(
771
+ Input,
772
+ {
773
+ label: "Send to",
774
+ placeholder: "0x\u2026",
775
+ value: destination,
776
+ onChange: (e) => setDestination(e.target.value),
777
+ error: destination.length > 0 && !destinationValid ? "Enter a valid address" : void 0
778
+ }
779
+ ),
780
+ /* @__PURE__ */ jsx2(
781
+ Button,
782
+ {
783
+ onClick: () => destinationValid && onSubmit(destination),
784
+ loading: submitting,
785
+ loadingText: "Refunding\u2026",
786
+ disabled: !destinationValid,
787
+ fullWidth: true,
788
+ children: "Claim refund"
789
+ }
790
+ )
791
+ ] }),
792
+ /* @__PURE__ */ jsx2(PoweredBy, {})
793
+ ] });
794
+ }
795
+ function ResultStep({
796
+ result,
797
+ error,
798
+ onClose,
799
+ onReset,
800
+ onRetry
801
+ }) {
802
+ const success = !!result;
803
+ return /* @__PURE__ */ jsxs2("div", { className: "rs-screen", children: [
804
+ /* @__PURE__ */ jsxs2("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
805
+ /* @__PURE__ */ jsx2(
806
+ BodyHeader,
807
+ {
808
+ icon: success ? /* @__PURE__ */ jsx2(CheckIcon, {}) : /* @__PURE__ */ jsx2(AlertTriangleIcon, {}),
809
+ variant: success ? "success" : "error",
810
+ title: success ? "Refund submitted" : "Refund failed",
811
+ subtitle: success ? "Your funds are being returned on the source chain." : void 0
812
+ }
813
+ ),
814
+ success ? /* @__PURE__ */ jsxs2("div", { className: "rs-review-details", children: [
815
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-detail-row", children: [
816
+ /* @__PURE__ */ jsx2("span", { children: "Amount" }),
817
+ /* @__PURE__ */ jsxs2("span", { className: "rs-review-detail-value", children: [
818
+ /* @__PURE__ */ jsxs2("span", { children: [
819
+ result.amountDisplay,
820
+ " ",
821
+ result.symbol
822
+ ] }),
823
+ /* @__PURE__ */ jsx2("span", { className: "rs-review-detail-icon", children: /* @__PURE__ */ jsx2(TokenIcon, { symbol: result.symbol }) })
824
+ ] })
825
+ ] }),
826
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-detail-row", children: [
827
+ /* @__PURE__ */ jsx2("span", { children: "Sent to" }),
828
+ /* @__PURE__ */ jsx2("span", { className: "rs-review-detail-value", children: shortenAddress(result.destination) })
829
+ ] }),
830
+ /* @__PURE__ */ jsxs2("div", { className: "rs-review-detail-row", children: [
831
+ /* @__PURE__ */ jsx2("span", { children: "Transaction" }),
832
+ (() => {
833
+ const url = getExplorerTxUrl(result.chain, result.txHash);
834
+ const short = shortenHash(result.txHash);
835
+ return url ? /* @__PURE__ */ jsxs2(
836
+ "a",
837
+ {
838
+ className: "rs-review-detail-value rs-link",
839
+ href: url,
840
+ target: "_blank",
841
+ rel: "noreferrer",
842
+ children: [
843
+ short,
844
+ /* @__PURE__ */ jsx2(ExternalLinkIcon, {})
845
+ ]
846
+ }
847
+ ) : /* @__PURE__ */ jsx2("span", { className: "rs-review-detail-value", children: short });
848
+ })()
849
+ ] })
850
+ ] }) : /* @__PURE__ */ jsx2(Callout, { variant: "error", children: error }),
851
+ /* @__PURE__ */ jsx2("div", { className: "rs-screen-button-row", children: success ? /* @__PURE__ */ jsxs2(Fragment, { children: [
852
+ onReset && /* @__PURE__ */ jsx2(Button, { variant: "outline", onClick: onReset, fullWidth: true, children: "Claim another deposit" }),
853
+ /* @__PURE__ */ jsx2(Button, { onClick: onClose, fullWidth: true, children: "Done" })
854
+ ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
855
+ /* @__PURE__ */ jsx2(Button, { variant: "outline", onClick: onClose, fullWidth: true, children: "Cancel" }),
856
+ onRetry && /* @__PURE__ */ jsx2(Button, { onClick: onRetry, fullWidth: true, children: "Try again" })
857
+ ] }) })
858
+ ] }),
859
+ /* @__PURE__ */ jsx2(PoweredBy, {})
860
+ ] });
861
+ }
862
+
863
+ export {
864
+ ClaimModal
865
+ };