@rhinestone/deposit-modal 0.1.20 → 0.1.22

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.
@@ -0,0 +1,1524 @@
1
+ import {
2
+ Button,
3
+ ConnectStep,
4
+ Modal,
5
+ PoweredBy,
6
+ ProcessingStep,
7
+ accountFromPrivateKey,
8
+ applyTheme,
9
+ createDepositService,
10
+ createSessionOwnerKey,
11
+ createSmartAccount,
12
+ createViewOnlyAccount,
13
+ currencyFormatter,
14
+ formatUserError,
15
+ getAccountAddress,
16
+ getAccountInitData,
17
+ getAssetId,
18
+ getPublicClient,
19
+ getSessionDetails,
20
+ isNativeAsset,
21
+ loadSessionOwnerFromStorage,
22
+ saveSessionOwnerToStorage
23
+ } from "./chunk-JBT2ZV3Q.mjs";
24
+ import {
25
+ CHAIN_BY_ID,
26
+ DEFAULT_BACKEND_URL,
27
+ DEFAULT_SIGNER_ADDRESS,
28
+ NATIVE_TOKEN_ADDRESS,
29
+ SOURCE_CHAINS,
30
+ getChainIcon,
31
+ getChainId,
32
+ getChainName,
33
+ getSupportedTargetTokens,
34
+ getTokenDecimalsByAddress,
35
+ getTokenIcon,
36
+ getTokenSymbol
37
+ } from "./chunk-A6QLADED.mjs";
38
+
39
+ // src/WithdrawModal.tsx
40
+ import {
41
+ useCallback as useCallback3,
42
+ useEffect as useEffect3,
43
+ useMemo as useMemo3,
44
+ useRef as useRef2,
45
+ useState as useState3,
46
+ lazy,
47
+ Suspense
48
+ } from "react";
49
+
50
+ // src/WithdrawFlow.tsx
51
+ import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useState as useState2 } from "react";
52
+
53
+ // src/components/steps/WithdrawFormStep.tsx
54
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
55
+ import { erc20Abi, formatUnits, parseUnits } from "viem";
56
+ import { jsx, jsxs } from "react/jsx-runtime";
57
+ function useClickOutside(ref, onClose) {
58
+ useEffect(() => {
59
+ function handleClick(e) {
60
+ if (ref.current && !ref.current.contains(e.target)) {
61
+ onClose();
62
+ }
63
+ }
64
+ document.addEventListener("mousedown", handleClick);
65
+ return () => document.removeEventListener("mousedown", handleClick);
66
+ }, [ref, onClose]);
67
+ }
68
+ function WithdrawFormStep({
69
+ walletClient,
70
+ publicClient,
71
+ address,
72
+ safeAddress,
73
+ asset,
74
+ defaultRecipient,
75
+ defaultAmount,
76
+ targetChain,
77
+ targetToken,
78
+ targetChains,
79
+ targetTokenOptions,
80
+ onTargetChainChange,
81
+ onTargetTokenChange,
82
+ switchChain,
83
+ submitting = false,
84
+ onSubmit,
85
+ onBalanceUsdChange
86
+ }) {
87
+ const [recipient, setRecipient] = useState(
88
+ defaultRecipient ?? address ?? ""
89
+ );
90
+ const [amount, setAmount] = useState(defaultAmount ?? "");
91
+ const [balance, setBalance] = useState(null);
92
+ const [error, setError] = useState(null);
93
+ const [isSwitching, setIsSwitching] = useState(false);
94
+ const [showChainDropdown, setShowChainDropdown] = useState(false);
95
+ const [showTokenDropdown, setShowTokenDropdown] = useState(false);
96
+ const [isSubmitting, setIsSubmitting] = useState(false);
97
+ const hasAttemptedSwitch = useRef(false);
98
+ const chainDropdownRef = useRef(null);
99
+ const tokenDropdownRef = useRef(null);
100
+ const publicClientChainId = publicClient.chain?.id;
101
+ useClickOutside(chainDropdownRef, () => setShowChainDropdown(false));
102
+ useClickOutside(tokenDropdownRef, () => setShowTokenDropdown(false));
103
+ const isRecipientConnected = Boolean(
104
+ address && recipient && recipient.toLowerCase() === address.toLowerCase()
105
+ );
106
+ const chainMismatch = Boolean(
107
+ walletClient?.chain?.id && walletClient.chain.id !== asset.chainId
108
+ );
109
+ const targetSymbol = getTokenSymbol(targetToken, targetChain);
110
+ const targetChainName = getChainName(targetChain);
111
+ const isBusy = submitting || isSubmitting;
112
+ useEffect(() => {
113
+ if (chainMismatch && switchChain && !hasAttemptedSwitch.current) {
114
+ hasAttemptedSwitch.current = true;
115
+ setIsSwitching(true);
116
+ switchChain(asset.chainId).catch((err) => {
117
+ const raw = err instanceof Error ? err.message : "Failed to switch chain";
118
+ setError(formatUserError(raw));
119
+ }).finally(() => {
120
+ setIsSwitching(false);
121
+ });
122
+ }
123
+ }, [chainMismatch, switchChain, asset.chainId]);
124
+ useEffect(() => {
125
+ hasAttemptedSwitch.current = false;
126
+ }, [asset.chainId]);
127
+ useEffect(() => {
128
+ let active = true;
129
+ async function fetchBalance() {
130
+ if (!safeAddress || !publicClient) return;
131
+ if (publicClientChainId && publicClientChainId !== asset.chainId) {
132
+ return;
133
+ }
134
+ try {
135
+ const bal = isNativeAsset(asset) ? await publicClient.getBalance({ address: safeAddress }) : await publicClient.readContract({
136
+ address: asset.token,
137
+ abi: erc20Abi,
138
+ functionName: "balanceOf",
139
+ args: [safeAddress]
140
+ });
141
+ if (active) {
142
+ setBalance(bal);
143
+ }
144
+ } catch {
145
+ if (active) {
146
+ setBalance(null);
147
+ }
148
+ }
149
+ }
150
+ void fetchBalance();
151
+ const unwatch = publicClient.watchBlockNumber({
152
+ emitOnBegin: false,
153
+ onBlockNumber() {
154
+ void fetchBalance();
155
+ }
156
+ });
157
+ return () => {
158
+ active = false;
159
+ unwatch();
160
+ };
161
+ }, [safeAddress, publicClient, publicClientChainId, asset]);
162
+ const formattedBalance = useMemo(() => {
163
+ if (balance === null) return "...";
164
+ try {
165
+ const raw = formatUnits(balance, asset.decimals);
166
+ const numeric = Number(raw);
167
+ if (!Number.isFinite(numeric)) return raw;
168
+ return new Intl.NumberFormat("en-US", {
169
+ maximumFractionDigits: 2
170
+ }).format(numeric);
171
+ } catch {
172
+ return "...";
173
+ }
174
+ }, [balance, asset.decimals]);
175
+ useEffect(() => {
176
+ if (balance === null) return;
177
+ const sym = asset.symbol.toUpperCase();
178
+ if (sym !== "USDC" && sym !== "USDT") return;
179
+ try {
180
+ const raw = formatUnits(balance, asset.decimals);
181
+ const numeric = Number(raw);
182
+ if (!Number.isFinite(numeric)) return;
183
+ onBalanceUsdChange?.(numeric);
184
+ } catch {
185
+ return;
186
+ }
187
+ }, [balance, asset.decimals, asset.symbol, onBalanceUsdChange]);
188
+ const amountUsd = useMemo(() => {
189
+ if (!amount) return null;
190
+ const parsed = Number(amount);
191
+ if (!Number.isFinite(parsed) || parsed <= 0) return null;
192
+ const sym = asset.symbol.toUpperCase();
193
+ if (sym === "USDC" || sym === "USDT") {
194
+ return parsed;
195
+ }
196
+ return null;
197
+ }, [amount, asset.symbol]);
198
+ const handleMaxClick = useCallback(() => {
199
+ if (balance === null) return;
200
+ const maxAmount = formatUnits(balance, asset.decimals);
201
+ setAmount(maxAmount);
202
+ setError(null);
203
+ }, [balance, asset.decimals]);
204
+ const handleUseConnected = useCallback(() => {
205
+ if (!address) return;
206
+ setRecipient(address);
207
+ setError(null);
208
+ }, [address]);
209
+ const handleWithdraw = useCallback(async () => {
210
+ if (!recipient || !/^0x[a-fA-F0-9]{40}$/.test(recipient)) {
211
+ setError("Enter a valid recipient address");
212
+ return;
213
+ }
214
+ const parsed = Number(amount);
215
+ if (!amount || !Number.isFinite(parsed) || parsed <= 0) {
216
+ setError("Enter a valid amount");
217
+ return;
218
+ }
219
+ if (balance !== null) {
220
+ try {
221
+ const amountInUnits = parseUnits(amount, asset.decimals);
222
+ if (amountInUnits > balance) {
223
+ setError("Insufficient balance");
224
+ return;
225
+ }
226
+ } catch {
227
+ setError("Invalid amount");
228
+ return;
229
+ }
230
+ }
231
+ setError(null);
232
+ setIsSubmitting(true);
233
+ try {
234
+ await onSubmit(recipient, amount);
235
+ } catch (err) {
236
+ const raw = err instanceof Error ? err.message : "Withdrawal failed";
237
+ setError(formatUserError(raw));
238
+ } finally {
239
+ setIsSubmitting(false);
240
+ }
241
+ }, [recipient, amount, balance, asset.decimals, onSubmit]);
242
+ const handleSwitch = async () => {
243
+ if (!asset.chainId || !switchChain) return;
244
+ setIsSwitching(true);
245
+ try {
246
+ await switchChain(asset.chainId);
247
+ } catch (err) {
248
+ const raw = err instanceof Error ? err.message : "Failed to switch chain";
249
+ setError(formatUserError(raw));
250
+ } finally {
251
+ setIsSwitching(false);
252
+ }
253
+ };
254
+ return /* @__PURE__ */ jsxs("div", { className: "rs-step", children: [
255
+ /* @__PURE__ */ jsxs("div", { className: "rs-step-body rs-withdraw-form", children: [
256
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-section", children: [
257
+ /* @__PURE__ */ jsx("label", { className: "rs-withdraw-label", children: "Recipient" }),
258
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-input-row", children: [
259
+ /* @__PURE__ */ jsx(
260
+ "input",
261
+ {
262
+ type: "text",
263
+ className: "rs-withdraw-input rs-withdraw-input--address",
264
+ placeholder: "0x...",
265
+ value: recipient,
266
+ onChange: (e) => {
267
+ setRecipient(e.target.value.trim());
268
+ if (error) setError(null);
269
+ }
270
+ }
271
+ ),
272
+ address && /* @__PURE__ */ jsxs(
273
+ "button",
274
+ {
275
+ type: "button",
276
+ className: `rs-withdraw-use-connected ${isRecipientConnected ? "rs-withdraw-use-connected--active" : ""}`,
277
+ onClick: handleUseConnected,
278
+ title: isRecipientConnected ? "Using connected wallet" : "Use connected wallet",
279
+ children: [
280
+ /* @__PURE__ */ jsx(
281
+ "svg",
282
+ {
283
+ viewBox: "0 0 24 24",
284
+ fill: "none",
285
+ stroke: "currentColor",
286
+ strokeWidth: "2",
287
+ className: "rs-withdraw-use-connected-icon",
288
+ children: /* @__PURE__ */ jsx(
289
+ "path",
290
+ {
291
+ strokeLinecap: "round",
292
+ strokeLinejoin: "round",
293
+ d: "M21 12a2.25 2.25 0 00-2.25-2.25H15a3 3 0 11-6 0H5.25A2.25 2.25 0 003 12m18 0v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6m18 0V9M3 12V9m18 0a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 9m18 0V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v3"
294
+ }
295
+ )
296
+ }
297
+ ),
298
+ isRecipientConnected ? null : "Wallet"
299
+ ]
300
+ }
301
+ )
302
+ ] })
303
+ ] }),
304
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-section", children: [
305
+ /* @__PURE__ */ jsx("label", { className: "rs-withdraw-label", children: "Amount" }),
306
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-input-row", children: [
307
+ /* @__PURE__ */ jsx(
308
+ "input",
309
+ {
310
+ type: "text",
311
+ inputMode: "decimal",
312
+ className: "rs-withdraw-input rs-withdraw-input--amount",
313
+ placeholder: "0",
314
+ value: amount,
315
+ onChange: (e) => {
316
+ const value = e.target.value.replace(/[^0-9.]/g, "");
317
+ const parts = value.split(".");
318
+ if (parts.length > 2) return;
319
+ setAmount(value);
320
+ if (error) setError(null);
321
+ }
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-amount-right", children: [
325
+ /* @__PURE__ */ jsx("span", { className: "rs-withdraw-token-label", children: asset.symbol }),
326
+ /* @__PURE__ */ jsx(
327
+ "button",
328
+ {
329
+ type: "button",
330
+ className: "rs-withdraw-max-btn",
331
+ onClick: handleMaxClick,
332
+ disabled: balance === null,
333
+ children: "Max"
334
+ }
335
+ )
336
+ ] })
337
+ ] }),
338
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-amount-info", children: [
339
+ /* @__PURE__ */ jsx("span", { className: "rs-withdraw-usd", children: amountUsd !== null ? currencyFormatter.format(amountUsd) : "$0.00" }),
340
+ /* @__PURE__ */ jsxs("span", { className: "rs-withdraw-balance", children: [
341
+ formattedBalance,
342
+ " ",
343
+ asset.symbol
344
+ ] })
345
+ ] })
346
+ ] }),
347
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-receive-row", children: [
348
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-receive-col", children: [
349
+ /* @__PURE__ */ jsx("label", { className: "rs-withdraw-label", children: "Receive" }),
350
+ /* @__PURE__ */ jsxs(
351
+ "div",
352
+ {
353
+ className: "rs-withdraw-dropdown-container",
354
+ ref: tokenDropdownRef,
355
+ children: [
356
+ /* @__PURE__ */ jsxs(
357
+ "button",
358
+ {
359
+ type: "button",
360
+ className: "rs-withdraw-dropdown",
361
+ onClick: () => {
362
+ setShowTokenDropdown(!showTokenDropdown);
363
+ setShowChainDropdown(false);
364
+ },
365
+ children: [
366
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-dropdown-value", children: [
367
+ getTokenIcon(targetSymbol) && /* @__PURE__ */ jsx(
368
+ "img",
369
+ {
370
+ src: getTokenIcon(targetSymbol),
371
+ alt: targetSymbol,
372
+ className: "rs-withdraw-dropdown-icon"
373
+ }
374
+ ),
375
+ /* @__PURE__ */ jsx("span", { children: targetSymbol })
376
+ ] }),
377
+ /* @__PURE__ */ jsx(
378
+ "svg",
379
+ {
380
+ viewBox: "0 0 24 24",
381
+ fill: "none",
382
+ stroke: "currentColor",
383
+ strokeWidth: "2",
384
+ className: "rs-withdraw-dropdown-arrow",
385
+ style: {
386
+ transform: showTokenDropdown ? "rotate(180deg)" : void 0
387
+ },
388
+ children: /* @__PURE__ */ jsx(
389
+ "path",
390
+ {
391
+ strokeLinecap: "round",
392
+ strokeLinejoin: "round",
393
+ d: "M19 9l-7 7-7-7"
394
+ }
395
+ )
396
+ }
397
+ )
398
+ ]
399
+ }
400
+ ),
401
+ showTokenDropdown && /* @__PURE__ */ jsx("div", { className: "rs-withdraw-dropdown-menu", children: targetTokenOptions.map((option) => /* @__PURE__ */ jsxs(
402
+ "button",
403
+ {
404
+ type: "button",
405
+ className: "rs-withdraw-dropdown-item",
406
+ onClick: () => {
407
+ onTargetTokenChange(option.address);
408
+ setShowTokenDropdown(false);
409
+ },
410
+ children: [
411
+ getTokenIcon(option.symbol) && /* @__PURE__ */ jsx(
412
+ "img",
413
+ {
414
+ src: getTokenIcon(option.symbol),
415
+ alt: option.symbol,
416
+ className: "rs-withdraw-dropdown-icon"
417
+ }
418
+ ),
419
+ /* @__PURE__ */ jsx("span", { children: option.symbol })
420
+ ]
421
+ },
422
+ option.address
423
+ )) })
424
+ ]
425
+ }
426
+ )
427
+ ] }),
428
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-receive-col", children: [
429
+ /* @__PURE__ */ jsx("label", { className: "rs-withdraw-label", children: "Chain" }),
430
+ /* @__PURE__ */ jsxs(
431
+ "div",
432
+ {
433
+ className: "rs-withdraw-dropdown-container",
434
+ ref: chainDropdownRef,
435
+ children: [
436
+ /* @__PURE__ */ jsxs(
437
+ "button",
438
+ {
439
+ type: "button",
440
+ className: "rs-withdraw-dropdown",
441
+ onClick: () => {
442
+ setShowChainDropdown(!showChainDropdown);
443
+ setShowTokenDropdown(false);
444
+ },
445
+ children: [
446
+ /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-dropdown-value", children: [
447
+ getChainIcon(targetChain) && /* @__PURE__ */ jsx(
448
+ "img",
449
+ {
450
+ src: getChainIcon(targetChain),
451
+ alt: targetChainName,
452
+ className: "rs-withdraw-dropdown-icon"
453
+ }
454
+ ),
455
+ /* @__PURE__ */ jsx("span", { children: targetChainName })
456
+ ] }),
457
+ /* @__PURE__ */ jsx(
458
+ "svg",
459
+ {
460
+ viewBox: "0 0 24 24",
461
+ fill: "none",
462
+ stroke: "currentColor",
463
+ strokeWidth: "2",
464
+ className: "rs-withdraw-dropdown-arrow",
465
+ style: {
466
+ transform: showChainDropdown ? "rotate(180deg)" : void 0
467
+ },
468
+ children: /* @__PURE__ */ jsx(
469
+ "path",
470
+ {
471
+ strokeLinecap: "round",
472
+ strokeLinejoin: "round",
473
+ d: "M19 9l-7 7-7-7"
474
+ }
475
+ )
476
+ }
477
+ )
478
+ ]
479
+ }
480
+ ),
481
+ showChainDropdown && /* @__PURE__ */ jsx("div", { className: "rs-withdraw-dropdown-menu", children: targetChains.map((chain) => /* @__PURE__ */ jsxs(
482
+ "button",
483
+ {
484
+ type: "button",
485
+ className: "rs-withdraw-dropdown-item",
486
+ onClick: () => {
487
+ onTargetChainChange(chain.id);
488
+ setShowChainDropdown(false);
489
+ },
490
+ children: [
491
+ getChainIcon(chain.id) && /* @__PURE__ */ jsx(
492
+ "img",
493
+ {
494
+ src: getChainIcon(chain.id),
495
+ alt: chain.name,
496
+ className: "rs-withdraw-dropdown-icon"
497
+ }
498
+ ),
499
+ /* @__PURE__ */ jsx("span", { children: chain.name })
500
+ ]
501
+ },
502
+ chain.id
503
+ )) })
504
+ ]
505
+ }
506
+ )
507
+ ] })
508
+ ] }),
509
+ chainMismatch && /* @__PURE__ */ jsxs("div", { className: "rs-chain-switch", children: [
510
+ /* @__PURE__ */ jsxs("div", { className: "rs-chain-switch-text", children: [
511
+ "Switch to ",
512
+ getChainName(asset.chainId),
513
+ " to continue."
514
+ ] }),
515
+ switchChain && /* @__PURE__ */ jsx(
516
+ Button,
517
+ {
518
+ variant: "outline",
519
+ size: "small",
520
+ loading: isSwitching,
521
+ onClick: handleSwitch,
522
+ children: "Switch"
523
+ }
524
+ )
525
+ ] }),
526
+ error && /* @__PURE__ */ jsxs("div", { className: "rs-withdraw-error", children: [
527
+ /* @__PURE__ */ jsx(
528
+ "svg",
529
+ {
530
+ viewBox: "0 0 24 24",
531
+ fill: "none",
532
+ stroke: "currentColor",
533
+ strokeWidth: "2",
534
+ children: /* @__PURE__ */ jsx(
535
+ "path",
536
+ {
537
+ strokeLinecap: "round",
538
+ strokeLinejoin: "round",
539
+ d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
540
+ }
541
+ )
542
+ }
543
+ ),
544
+ /* @__PURE__ */ jsx("span", { children: error })
545
+ ] })
546
+ ] }),
547
+ /* @__PURE__ */ jsx("div", { className: "rs-step-footer", children: /* @__PURE__ */ jsx(
548
+ Button,
549
+ {
550
+ onClick: handleWithdraw,
551
+ fullWidth: true,
552
+ loading: isBusy,
553
+ disabled: !recipient || !amount || chainMismatch || isBusy,
554
+ children: isBusy ? "Preparing..." : "Withdraw"
555
+ }
556
+ ) }),
557
+ /* @__PURE__ */ jsx(PoweredBy, {})
558
+ ] });
559
+ }
560
+ WithdrawFormStep.displayName = "WithdrawFormStep";
561
+
562
+ // src/WithdrawFlow.tsx
563
+ import { walletClientToAccount } from "@rhinestone/sdk";
564
+
565
+ // src/core/safe.ts
566
+ import {
567
+ concat,
568
+ encodeFunctionData,
569
+ erc20Abi as erc20Abi2,
570
+ hashTypedData,
571
+ pad,
572
+ parseEventLogs,
573
+ toHex,
574
+ zeroAddress
575
+ } from "viem";
576
+ var SAFE_ABI = [
577
+ {
578
+ type: "function",
579
+ name: "isOwner",
580
+ stateMutability: "view",
581
+ inputs: [{ name: "owner", type: "address" }],
582
+ outputs: [{ name: "", type: "bool" }]
583
+ },
584
+ {
585
+ type: "function",
586
+ name: "nonce",
587
+ stateMutability: "view",
588
+ inputs: [],
589
+ outputs: [{ type: "uint256" }]
590
+ },
591
+ {
592
+ type: "function",
593
+ name: "execTransaction",
594
+ stateMutability: "payable",
595
+ inputs: [
596
+ { name: "to", type: "address" },
597
+ { name: "value", type: "uint256" },
598
+ { name: "data", type: "bytes" },
599
+ { name: "operation", type: "uint8" },
600
+ { name: "safeTxGas", type: "uint256" },
601
+ { name: "baseGas", type: "uint256" },
602
+ { name: "gasPrice", type: "uint256" },
603
+ { name: "gasToken", type: "address" },
604
+ { name: "refundReceiver", type: "address" },
605
+ { name: "signatures", type: "bytes" }
606
+ ],
607
+ outputs: [{ name: "success", type: "bool" }]
608
+ },
609
+ {
610
+ type: "event",
611
+ name: "ExecutionSuccess",
612
+ inputs: [
613
+ { name: "txHash", type: "bytes32", indexed: true },
614
+ { name: "payment", type: "uint256", indexed: false }
615
+ ],
616
+ anonymous: false
617
+ },
618
+ {
619
+ type: "event",
620
+ name: "ExecutionFailure",
621
+ inputs: [
622
+ { name: "txHash", type: "bytes32", indexed: true },
623
+ { name: "payment", type: "uint256", indexed: false }
624
+ ],
625
+ anonymous: false
626
+ }
627
+ ];
628
+ async function executeSafeEthTransfer(params) {
629
+ const {
630
+ walletClient,
631
+ publicClient,
632
+ safeAddress,
633
+ recipient,
634
+ amount,
635
+ chainId
636
+ } = params;
637
+ const account = walletClient.account;
638
+ const chain = walletClient.chain;
639
+ if (!account || !chain) {
640
+ throw new Error("Wallet not connected");
641
+ }
642
+ if (chain.id !== chainId) {
643
+ throw new Error(`Switch to ${getChainName(chainId)} to sign`);
644
+ }
645
+ const isOwner = await publicClient.readContract({
646
+ address: safeAddress,
647
+ abi: SAFE_ABI,
648
+ functionName: "isOwner",
649
+ args: [account.address]
650
+ });
651
+ if (!isOwner) {
652
+ throw new Error("Connected wallet is not a Safe owner");
653
+ }
654
+ const safeTx = {
655
+ to: recipient,
656
+ value: amount,
657
+ data: "0x",
658
+ operation: 0,
659
+ safeTxGas: 0n,
660
+ baseGas: 0n,
661
+ gasPrice: 0n,
662
+ gasToken: zeroAddress,
663
+ refundReceiver: zeroAddress
664
+ };
665
+ const signature = concat([
666
+ pad(account.address, { size: 32 }),
667
+ pad(toHex(0), { size: 32 }),
668
+ toHex(1, { size: 1 })
669
+ ]);
670
+ const txHash = await walletClient.writeContract({
671
+ account,
672
+ chain,
673
+ address: safeAddress,
674
+ abi: SAFE_ABI,
675
+ functionName: "execTransaction",
676
+ args: [
677
+ safeTx.to,
678
+ safeTx.value,
679
+ safeTx.data,
680
+ safeTx.operation,
681
+ safeTx.safeTxGas,
682
+ safeTx.baseGas,
683
+ safeTx.gasPrice,
684
+ safeTx.gasToken,
685
+ safeTx.refundReceiver,
686
+ signature
687
+ ]
688
+ });
689
+ const receipt = await publicClient.waitForTransactionReceipt({
690
+ hash: txHash
691
+ });
692
+ const safeLogs = receipt.logs.filter(
693
+ (log) => log.address.toLowerCase() === safeAddress.toLowerCase()
694
+ );
695
+ const parsed = parseEventLogs({
696
+ abi: SAFE_ABI,
697
+ logs: safeLogs,
698
+ strict: false
699
+ });
700
+ const failed = parsed.find((log) => log.eventName === "ExecutionFailure");
701
+ if (failed) {
702
+ throw new Error("Safe transaction failed");
703
+ }
704
+ const succeeded = parsed.find((log) => log.eventName === "ExecutionSuccess");
705
+ if (!succeeded) {
706
+ throw new Error("Safe transaction status unavailable");
707
+ }
708
+ return { txHash };
709
+ }
710
+ async function executeSafeErc20Transfer(params) {
711
+ const {
712
+ walletClient,
713
+ publicClient,
714
+ safeAddress,
715
+ tokenAddress,
716
+ recipient,
717
+ amount,
718
+ chainId
719
+ } = params;
720
+ const account = walletClient.account;
721
+ const chain = walletClient.chain;
722
+ if (!account || !chain) {
723
+ throw new Error("Wallet not connected");
724
+ }
725
+ if (chain.id !== chainId) {
726
+ throw new Error(`Switch to ${getChainName(chainId)} to sign`);
727
+ }
728
+ const isOwner = await publicClient.readContract({
729
+ address: safeAddress,
730
+ abi: SAFE_ABI,
731
+ functionName: "isOwner",
732
+ args: [account.address]
733
+ });
734
+ if (!isOwner) {
735
+ throw new Error("Connected wallet is not a Safe owner");
736
+ }
737
+ const data = encodeFunctionData({
738
+ abi: erc20Abi2,
739
+ functionName: "transfer",
740
+ args: [recipient, amount]
741
+ });
742
+ const safeTx = {
743
+ to: tokenAddress,
744
+ value: 0n,
745
+ data,
746
+ operation: 0,
747
+ safeTxGas: 0n,
748
+ baseGas: 0n,
749
+ gasPrice: 0n,
750
+ gasToken: zeroAddress,
751
+ refundReceiver: zeroAddress
752
+ };
753
+ const signature = concat([
754
+ pad(account.address, { size: 32 }),
755
+ pad(toHex(0), { size: 32 }),
756
+ toHex(1, { size: 1 })
757
+ ]);
758
+ const txHash = await walletClient.writeContract({
759
+ account,
760
+ chain,
761
+ address: safeAddress,
762
+ abi: SAFE_ABI,
763
+ functionName: "execTransaction",
764
+ args: [
765
+ safeTx.to,
766
+ safeTx.value,
767
+ safeTx.data,
768
+ safeTx.operation,
769
+ safeTx.safeTxGas,
770
+ safeTx.baseGas,
771
+ safeTx.gasPrice,
772
+ safeTx.gasToken,
773
+ safeTx.refundReceiver,
774
+ signature
775
+ ]
776
+ });
777
+ const receipt = await publicClient.waitForTransactionReceipt({
778
+ hash: txHash
779
+ });
780
+ const safeLogs = receipt.logs.filter(
781
+ (log) => log.address.toLowerCase() === safeAddress.toLowerCase()
782
+ );
783
+ const parsed = parseEventLogs({
784
+ abi: SAFE_ABI,
785
+ logs: safeLogs,
786
+ strict: false
787
+ });
788
+ const failed = parsed.find((log) => log.eventName === "ExecutionFailure");
789
+ if (failed) {
790
+ throw new Error("Safe transaction failed");
791
+ }
792
+ const succeeded = parsed.find((log) => log.eventName === "ExecutionSuccess");
793
+ if (!succeeded) {
794
+ throw new Error("Safe transaction status unavailable");
795
+ }
796
+ return { txHash };
797
+ }
798
+ var SAFE_TX_TYPES = {
799
+ SafeTx: [
800
+ { name: "to", type: "address" },
801
+ { name: "value", type: "uint256" },
802
+ { name: "data", type: "bytes" },
803
+ { name: "operation", type: "uint8" },
804
+ { name: "safeTxGas", type: "uint256" },
805
+ { name: "baseGas", type: "uint256" },
806
+ { name: "gasPrice", type: "uint256" },
807
+ { name: "gasToken", type: "address" },
808
+ { name: "refundReceiver", type: "address" },
809
+ { name: "nonce", type: "uint256" }
810
+ ]
811
+ };
812
+ async function buildSafeTransaction(params) {
813
+ const { publicClient, safeAddress, to, value, data, chainId } = params;
814
+ const nonce = await publicClient.readContract({
815
+ address: safeAddress,
816
+ abi: SAFE_ABI,
817
+ functionName: "nonce"
818
+ });
819
+ const message = {
820
+ to,
821
+ value,
822
+ data,
823
+ operation: 0,
824
+ safeTxGas: 0n,
825
+ baseGas: 0n,
826
+ gasPrice: 0n,
827
+ gasToken: zeroAddress,
828
+ refundReceiver: zeroAddress,
829
+ nonce
830
+ };
831
+ const safeTxHash = hashTypedData({
832
+ domain: { chainId, verifyingContract: safeAddress },
833
+ types: SAFE_TX_TYPES,
834
+ primaryType: "SafeTx",
835
+ message
836
+ });
837
+ return {
838
+ chainId,
839
+ safeAddress,
840
+ safeTxHash,
841
+ typedData: {
842
+ domain: { chainId, verifyingContract: safeAddress },
843
+ types: SAFE_TX_TYPES,
844
+ primaryType: "SafeTx",
845
+ message
846
+ }
847
+ };
848
+ }
849
+
850
+ // src/WithdrawFlow.tsx
851
+ import { encodeFunctionData as encodeFunctionData2, erc20Abi as erc20Abi3, parseUnits as parseUnits2 } from "viem";
852
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
853
+ function WithdrawFlow({
854
+ dappWalletClient,
855
+ dappPublicClient,
856
+ dappAddress,
857
+ safeAddress,
858
+ sourceChain,
859
+ sourceToken,
860
+ targetChain: targetChainProp,
861
+ targetToken: targetTokenProp,
862
+ recipient: defaultRecipient,
863
+ amount: defaultAmount,
864
+ service,
865
+ rhinestoneApiKey,
866
+ signerAddress = DEFAULT_SIGNER_ADDRESS,
867
+ sessionChainIds,
868
+ forceRegister = false,
869
+ waitForFinalTx = true,
870
+ reownWallet,
871
+ onConnect,
872
+ onRelayTransaction,
873
+ onRequestConnect,
874
+ connectButtonLabel,
875
+ onStepChange,
876
+ onTotalBalanceChange,
877
+ onClose,
878
+ onConnected,
879
+ onWithdrawSubmitted,
880
+ onWithdrawComplete,
881
+ onWithdrawFailed,
882
+ onError,
883
+ debug
884
+ }) {
885
+ const [step, setStep] = useState2({ type: "form" });
886
+ const [isSubmitting, setIsSubmitting] = useState2(false);
887
+ const [totalBalanceUsd, setTotalBalanceUsd] = useState2(0);
888
+ const [isConnectSelectionConfirmed, setIsConnectSelectionConfirmed] = useState2(false);
889
+ const [targetChain, setTargetChain] = useState2(targetChainProp);
890
+ const [targetToken, setTargetToken] = useState2(targetTokenProp);
891
+ useEffect2(() => {
892
+ setTargetChain(targetChainProp);
893
+ setTargetToken(targetTokenProp);
894
+ }, [targetChainProp, targetTokenProp]);
895
+ const targetChainObj = useMemo2(() => CHAIN_BY_ID[targetChain], [targetChain]);
896
+ const hasCustomSigner = Boolean(dappAddress && onRelayTransaction);
897
+ const dappSwitchChain = useMemo2(() => {
898
+ if (!dappWalletClient?.switchChain) return void 0;
899
+ return async (chainId) => {
900
+ await dappWalletClient.switchChain?.({ id: chainId });
901
+ };
902
+ }, [dappWalletClient]);
903
+ const walletOptions = useMemo2(() => {
904
+ const options = [];
905
+ const seen = /* @__PURE__ */ new Set();
906
+ if (dappWalletClient?.account && dappAddress) {
907
+ options.push({
908
+ address: dappWalletClient.account.address,
909
+ label: "Connected Wallet",
910
+ kind: "connected"
911
+ });
912
+ seen.add(dappWalletClient.account.address.toLowerCase());
913
+ }
914
+ if (reownWallet?.address && reownWallet.isConnected && !seen.has(reownWallet.address.toLowerCase())) {
915
+ options.push({
916
+ address: reownWallet.address,
917
+ label: "External Wallet",
918
+ kind: "external"
919
+ });
920
+ }
921
+ return options;
922
+ }, [
923
+ dappWalletClient,
924
+ dappAddress,
925
+ reownWallet?.address,
926
+ reownWallet?.isConnected
927
+ ]);
928
+ const canAutoLock = (dappWalletClient?.account && dappAddress || hasCustomSigner) && !reownWallet;
929
+ const [selectedConnectAddress, setSelectedConnectAddress] = useState2(null);
930
+ const signerContext = useMemo2(() => {
931
+ if (canAutoLock) {
932
+ if (hasCustomSigner) {
933
+ return {
934
+ ownerAddress: dappAddress,
935
+ walletClient: dappWalletClient ?? void 0,
936
+ publicClient: dappPublicClient ?? getPublicClient(sourceChain),
937
+ switchChain: dappSwitchChain
938
+ };
939
+ }
940
+ return {
941
+ ownerAddress: dappWalletClient.account.address,
942
+ walletClient: dappWalletClient,
943
+ publicClient: dappPublicClient ?? getPublicClient(sourceChain),
944
+ switchChain: dappSwitchChain
945
+ };
946
+ }
947
+ if (!isConnectSelectionConfirmed || !selectedConnectAddress) return null;
948
+ if (dappWalletClient?.account && dappWalletClient.account.address.toLowerCase() === selectedConnectAddress.toLowerCase()) {
949
+ return {
950
+ ownerAddress: dappWalletClient.account.address,
951
+ walletClient: dappWalletClient,
952
+ publicClient: dappPublicClient ?? getPublicClient(sourceChain),
953
+ switchChain: dappSwitchChain
954
+ };
955
+ }
956
+ if (reownWallet?.address?.toLowerCase() === selectedConnectAddress.toLowerCase() && reownWallet.walletClient && reownWallet.publicClient) {
957
+ return {
958
+ ownerAddress: reownWallet.address,
959
+ walletClient: reownWallet.walletClient,
960
+ publicClient: reownWallet.publicClient,
961
+ switchChain: reownWallet.switchChain
962
+ };
963
+ }
964
+ return null;
965
+ }, [
966
+ canAutoLock,
967
+ hasCustomSigner,
968
+ isConnectSelectionConfirmed,
969
+ selectedConnectAddress,
970
+ dappWalletClient,
971
+ dappPublicClient,
972
+ dappSwitchChain,
973
+ dappAddress,
974
+ reownWallet,
975
+ sourceChain
976
+ ]);
977
+ const asset = useMemo2(() => {
978
+ const symbol = getTokenSymbol(sourceToken, sourceChain);
979
+ const decimals = getTokenDecimalsByAddress(sourceToken, sourceChain);
980
+ return {
981
+ id: getAssetId({ chainId: sourceChain, token: sourceToken }),
982
+ chainId: sourceChain,
983
+ token: sourceToken,
984
+ symbol,
985
+ name: symbol,
986
+ decimals
987
+ };
988
+ }, [sourceChain, sourceToken]);
989
+ const isSourceNative = sourceToken.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase();
990
+ const stepIndex = step.type === "form" ? 0 : 1;
991
+ const currentBackHandler = void 0;
992
+ useEffect2(() => {
993
+ onStepChange?.(stepIndex, currentBackHandler);
994
+ }, [stepIndex, currentBackHandler, onStepChange]);
995
+ useEffect2(() => {
996
+ onTotalBalanceChange?.(totalBalanceUsd);
997
+ }, [totalBalanceUsd, onTotalBalanceChange]);
998
+ const handleConnected = useCallback2(
999
+ (addr, smartAccount) => {
1000
+ onConnected?.({ address: addr, smartAccount });
1001
+ },
1002
+ [onConnected]
1003
+ );
1004
+ const handleError = useCallback2(
1005
+ (message, code) => {
1006
+ onError?.({ message, code });
1007
+ },
1008
+ [onError]
1009
+ );
1010
+ const resolveSessionOwner = useCallback2(async (eoaAddress) => {
1011
+ const localOwner = loadSessionOwnerFromStorage(eoaAddress);
1012
+ if (localOwner) {
1013
+ return {
1014
+ account: accountFromPrivateKey(localOwner.privateKey),
1015
+ address: localOwner.address
1016
+ };
1017
+ }
1018
+ const created = createSessionOwnerKey();
1019
+ saveSessionOwnerToStorage(eoaAddress, created.privateKey, created.address);
1020
+ return {
1021
+ account: created.account,
1022
+ address: created.address
1023
+ };
1024
+ }, []);
1025
+ const handleFormSubmit = useCallback2(
1026
+ async (recipient, amountValue) => {
1027
+ const ownerAddress2 = signerContext?.ownerAddress;
1028
+ if (!ownerAddress2) {
1029
+ throw new Error("Wallet not connected");
1030
+ }
1031
+ if (!onRelayTransaction && !signerContext?.walletClient) {
1032
+ throw new Error("Wallet not connected");
1033
+ }
1034
+ if (!targetChainObj) {
1035
+ throw new Error("Unsupported target chain");
1036
+ }
1037
+ setIsSubmitting(true);
1038
+ try {
1039
+ const signerAccount = signerContext?.walletClient ? walletClientToAccount(signerContext.walletClient) : createViewOnlyAccount(ownerAddress2);
1040
+ const sessionOwner = await resolveSessionOwner(ownerAddress2);
1041
+ const account = await createSmartAccount(
1042
+ signerAccount,
1043
+ sessionOwner.account,
1044
+ rhinestoneApiKey
1045
+ );
1046
+ const smartAccount = getAccountAddress(account);
1047
+ const checkResult = await service.checkAccount(smartAccount);
1048
+ const targetMatches = checkResult.targetChain === targetChain && checkResult.targetToken?.toLowerCase() === targetToken.toLowerCase();
1049
+ if (!checkResult.isRegistered || forceRegister || !targetMatches) {
1050
+ const initData = getAccountInitData(account);
1051
+ const sessionDetails = await getSessionDetails(
1052
+ account,
1053
+ targetChainObj,
1054
+ signerAddress,
1055
+ sessionOwner.account,
1056
+ targetToken,
1057
+ sessionChainIds
1058
+ );
1059
+ await service.registerAccount({
1060
+ address: smartAccount,
1061
+ accountParams: {
1062
+ factory: initData.factory,
1063
+ factoryData: initData.factoryData,
1064
+ sessionDetails
1065
+ },
1066
+ eoaAddress: ownerAddress2,
1067
+ sessionOwner: sessionOwner.address,
1068
+ target: {
1069
+ chain: targetChain,
1070
+ token: targetToken,
1071
+ recipient
1072
+ }
1073
+ });
1074
+ }
1075
+ handleConnected(ownerAddress2, smartAccount);
1076
+ const amountUnits = parseUnits2(amountValue, asset.decimals);
1077
+ const pc = signerContext?.publicClient ?? getPublicClient(sourceChain);
1078
+ let result;
1079
+ if (onRelayTransaction) {
1080
+ const transferData = isSourceNative ? { to: smartAccount, value: amountUnits, data: "0x" } : {
1081
+ to: sourceToken,
1082
+ value: 0n,
1083
+ data: encodeFunctionData2({
1084
+ abi: erc20Abi3,
1085
+ functionName: "transfer",
1086
+ args: [smartAccount, amountUnits]
1087
+ })
1088
+ };
1089
+ const request = await buildSafeTransaction({
1090
+ publicClient: pc,
1091
+ safeAddress,
1092
+ to: transferData.to,
1093
+ value: transferData.value,
1094
+ data: transferData.data,
1095
+ chainId: sourceChain
1096
+ });
1097
+ result = await onRelayTransaction(request);
1098
+ } else if (isSourceNative) {
1099
+ result = await executeSafeEthTransfer({
1100
+ walletClient: signerContext.walletClient,
1101
+ publicClient: pc,
1102
+ safeAddress,
1103
+ recipient: smartAccount,
1104
+ amount: amountUnits,
1105
+ chainId: sourceChain
1106
+ });
1107
+ } else {
1108
+ result = await executeSafeErc20Transfer({
1109
+ walletClient: signerContext.walletClient,
1110
+ publicClient: pc,
1111
+ safeAddress,
1112
+ tokenAddress: sourceToken,
1113
+ recipient: smartAccount,
1114
+ amount: amountUnits,
1115
+ chainId: sourceChain
1116
+ });
1117
+ }
1118
+ onWithdrawSubmitted?.({
1119
+ txHash: result.txHash,
1120
+ sourceChain,
1121
+ amount: amountUnits.toString(),
1122
+ safeAddress
1123
+ });
1124
+ setStep({
1125
+ type: "processing",
1126
+ smartAccount,
1127
+ txHash: result.txHash,
1128
+ sourceChain,
1129
+ sourceToken,
1130
+ amount: amountUnits.toString()
1131
+ });
1132
+ } catch (err) {
1133
+ const raw = err instanceof Error ? err.message : "Withdraw failed";
1134
+ handleError(formatUserError(raw), "WITHDRAW_FLOW_ERROR");
1135
+ throw err;
1136
+ } finally {
1137
+ setIsSubmitting(false);
1138
+ }
1139
+ },
1140
+ [
1141
+ signerContext,
1142
+ targetChainObj,
1143
+ resolveSessionOwner,
1144
+ signerAddress,
1145
+ sessionChainIds,
1146
+ forceRegister,
1147
+ targetChain,
1148
+ targetToken,
1149
+ service,
1150
+ rhinestoneApiKey,
1151
+ handleConnected,
1152
+ asset.decimals,
1153
+ safeAddress,
1154
+ sourceToken,
1155
+ sourceChain,
1156
+ onWithdrawSubmitted,
1157
+ onRelayTransaction,
1158
+ isSourceNative,
1159
+ handleError
1160
+ ]
1161
+ );
1162
+ const handleWithdrawComplete = useCallback2(
1163
+ (txHash, destinationTxHash) => {
1164
+ onWithdrawComplete?.({ txHash, destinationTxHash });
1165
+ },
1166
+ [onWithdrawComplete]
1167
+ );
1168
+ const handleWithdrawFailed = useCallback2(
1169
+ (txHash, error) => {
1170
+ onWithdrawFailed?.({ txHash, error });
1171
+ },
1172
+ [onWithdrawFailed]
1173
+ );
1174
+ const targetChainOptions = useMemo2(() => {
1175
+ return SOURCE_CHAINS.filter(
1176
+ (chain) => getSupportedTargetTokens(chain.id).length > 0
1177
+ );
1178
+ }, []);
1179
+ const targetTokenOptions = useMemo2(
1180
+ () => getSupportedTargetTokens(targetChain),
1181
+ [targetChain]
1182
+ );
1183
+ useEffect2(() => {
1184
+ if (targetTokenOptions.length === 0) return;
1185
+ const matches = targetTokenOptions.some(
1186
+ (option) => option.address.toLowerCase() === targetToken.toLowerCase()
1187
+ );
1188
+ if (!matches) {
1189
+ setTargetToken(targetTokenOptions[0].address);
1190
+ }
1191
+ }, [targetToken, targetTokenOptions]);
1192
+ const handleTargetChainChange = useCallback2(
1193
+ (chainId) => {
1194
+ setTargetChain(chainId);
1195
+ const options = getSupportedTargetTokens(chainId);
1196
+ if (options.length === 0) {
1197
+ return;
1198
+ }
1199
+ const currentlySupported = options.some(
1200
+ (option) => option.address.toLowerCase() === targetToken.toLowerCase()
1201
+ );
1202
+ setTargetToken(
1203
+ currentlySupported ? targetToken : options[0].address
1204
+ );
1205
+ },
1206
+ [targetToken]
1207
+ );
1208
+ const handleTargetTokenChange = useCallback2((token) => {
1209
+ setTargetToken(token);
1210
+ }, []);
1211
+ const selectedConnectAddressEffective = useMemo2(() => {
1212
+ if (selectedConnectAddress) return selectedConnectAddress;
1213
+ if (walletOptions.length === 1) {
1214
+ return walletOptions[0].address;
1215
+ }
1216
+ return null;
1217
+ }, [selectedConnectAddress, walletOptions]);
1218
+ const walletOptionsKey = useMemo2(
1219
+ () => walletOptions.map((option) => option.address.toLowerCase()).join(","),
1220
+ [walletOptions]
1221
+ );
1222
+ const showConnectStep = !canAutoLock && !isConnectSelectionConfirmed;
1223
+ useEffect2(() => {
1224
+ setIsConnectSelectionConfirmed(false);
1225
+ }, [walletOptionsKey, selectedConnectAddressEffective]);
1226
+ if (showConnectStep) {
1227
+ return /* @__PURE__ */ jsx2("div", { className: "rs-modal-body", children: /* @__PURE__ */ jsx2(
1228
+ ConnectStep,
1229
+ {
1230
+ walletOptions,
1231
+ selectedAddress: selectedConnectAddressEffective,
1232
+ onSelectAddress: setSelectedConnectAddress,
1233
+ onRequestConnect,
1234
+ onConnect,
1235
+ onContinue: () => {
1236
+ if (selectedConnectAddressEffective) {
1237
+ setSelectedConnectAddress(selectedConnectAddressEffective);
1238
+ }
1239
+ setIsConnectSelectionConfirmed(true);
1240
+ },
1241
+ connectButtonLabel
1242
+ }
1243
+ ) });
1244
+ }
1245
+ if (!signerContext) return null;
1246
+ if (!onRelayTransaction && !signerContext.walletClient) return null;
1247
+ const ownerAddress = signerContext.ownerAddress;
1248
+ const formPublicClient = signerContext.publicClient ?? getPublicClient(sourceChain);
1249
+ return /* @__PURE__ */ jsxs2("div", { className: "rs-modal-body", children: [
1250
+ step.type === "form" && /* @__PURE__ */ jsx2(
1251
+ WithdrawFormStep,
1252
+ {
1253
+ walletClient: signerContext.walletClient,
1254
+ publicClient: formPublicClient,
1255
+ address: ownerAddress,
1256
+ safeAddress,
1257
+ asset,
1258
+ defaultRecipient: defaultRecipient ?? ownerAddress,
1259
+ defaultAmount,
1260
+ targetChain,
1261
+ targetToken,
1262
+ targetChains: targetChainOptions,
1263
+ targetTokenOptions,
1264
+ onTargetChainChange: handleTargetChainChange,
1265
+ onTargetTokenChange: handleTargetTokenChange,
1266
+ switchChain: signerContext.switchChain,
1267
+ submitting: isSubmitting,
1268
+ onSubmit: handleFormSubmit,
1269
+ onBalanceUsdChange: setTotalBalanceUsd
1270
+ }
1271
+ ),
1272
+ step.type === "processing" && /* @__PURE__ */ jsx2(
1273
+ ProcessingStep,
1274
+ {
1275
+ smartAccount: step.smartAccount,
1276
+ txHash: step.txHash,
1277
+ sourceChain: step.sourceChain,
1278
+ sourceToken: step.sourceToken,
1279
+ targetChain,
1280
+ targetToken,
1281
+ amount: step.amount,
1282
+ waitForFinalTx,
1283
+ service,
1284
+ flowLabel: "withdraw",
1285
+ onClose,
1286
+ onNewDeposit: () => setStep({ type: "form" }),
1287
+ onDepositComplete: handleWithdrawComplete,
1288
+ onDepositFailed: handleWithdrawFailed,
1289
+ onError: handleError,
1290
+ debug
1291
+ }
1292
+ )
1293
+ ] });
1294
+ }
1295
+
1296
+ // src/WithdrawModal.tsx
1297
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1298
+ var ReownWithdrawInner = lazy(
1299
+ () => import("./WithdrawModalReown-ICP3DH6Q.mjs").then((m) => ({
1300
+ default: m.WithdrawModalReown
1301
+ }))
1302
+ );
1303
+ function WithdrawModal(props) {
1304
+ const needsReown = !!props.reownAppId;
1305
+ if (needsReown) {
1306
+ return /* @__PURE__ */ jsx3(Suspense, { fallback: null, children: /* @__PURE__ */ jsx3(ReownWithdrawInner, { ...props }) });
1307
+ }
1308
+ return /* @__PURE__ */ jsx3(WithdrawModalInner, { ...props });
1309
+ }
1310
+ WithdrawModal.displayName = "WithdrawModal";
1311
+ function WithdrawModalInner({
1312
+ dappWalletClient,
1313
+ dappPublicClient,
1314
+ dappAddress,
1315
+ safeAddress,
1316
+ sourceChain: sourceChainProp,
1317
+ sourceToken,
1318
+ targetChain: targetChainProp,
1319
+ targetToken,
1320
+ recipient,
1321
+ defaultAmount,
1322
+ isOpen,
1323
+ onClose,
1324
+ inline,
1325
+ backendUrl = DEFAULT_BACKEND_URL,
1326
+ rhinestoneApiKey,
1327
+ signerAddress = DEFAULT_SIGNER_ADDRESS,
1328
+ sessionChainIds,
1329
+ forceRegister = false,
1330
+ waitForFinalTx = true,
1331
+ reownWallet,
1332
+ onConnect,
1333
+ onRelayTransaction,
1334
+ onRequestConnect,
1335
+ connectButtonLabel,
1336
+ theme,
1337
+ branding,
1338
+ uiConfig,
1339
+ className,
1340
+ onReady,
1341
+ onConnected,
1342
+ onWithdrawSubmitted,
1343
+ onWithdrawComplete,
1344
+ onWithdrawFailed,
1345
+ onError,
1346
+ debug
1347
+ }) {
1348
+ const modalRef = useRef2(null);
1349
+ const [currentStepIndex, setCurrentStepIndex] = useState3(0);
1350
+ const [totalBalanceUsd, setTotalBalanceUsd] = useState3(null);
1351
+ const backHandlerRef = useRef2(void 0);
1352
+ const targetChain = getChainId(targetChainProp);
1353
+ const sourceChain = getChainId(sourceChainProp);
1354
+ const service = useMemo3(() => createDepositService(backendUrl), [backendUrl]);
1355
+ useEffect3(() => {
1356
+ if (isOpen && modalRef.current) {
1357
+ applyTheme(modalRef.current, theme);
1358
+ }
1359
+ }, [isOpen, theme]);
1360
+ const hasCalledReady = useRef2(false);
1361
+ useEffect3(() => {
1362
+ if (isOpen && !hasCalledReady.current) {
1363
+ hasCalledReady.current = true;
1364
+ onReady?.();
1365
+ }
1366
+ }, [isOpen, onReady]);
1367
+ useEffect3(() => {
1368
+ if (!isOpen) {
1369
+ setCurrentStepIndex(0);
1370
+ }
1371
+ }, [isOpen]);
1372
+ const handleStepChange = useCallback3(
1373
+ (stepIndex, onBack) => {
1374
+ setCurrentStepIndex(stepIndex);
1375
+ backHandlerRef.current = onBack;
1376
+ },
1377
+ []
1378
+ );
1379
+ const handleTotalBalanceChange = useCallback3((balance) => {
1380
+ setTotalBalanceUsd(balance);
1381
+ }, []);
1382
+ const handleBack = useCallback3(() => {
1383
+ backHandlerRef.current?.();
1384
+ }, []);
1385
+ const showLogo = uiConfig?.showLogo ?? false;
1386
+ const showStepper = uiConfig?.showStepper ?? false;
1387
+ const showBackButton = uiConfig?.showBackButton ?? true;
1388
+ const balanceTitle = uiConfig?.balanceTitle;
1389
+ const logoUrl = branding?.logoUrl ?? "https://github.com/rhinestonewtf.png";
1390
+ const title = branding?.title ?? "Withdraw";
1391
+ const canGoBack = currentStepIndex > 0 && currentStepIndex < 3 && backHandlerRef.current;
1392
+ return /* @__PURE__ */ jsx3(
1393
+ Modal,
1394
+ {
1395
+ isOpen,
1396
+ onClose,
1397
+ className,
1398
+ inline,
1399
+ children: /* @__PURE__ */ jsxs3("div", { ref: modalRef, className: "rs-modal", children: [
1400
+ /* @__PURE__ */ jsxs3("div", { className: "rs-modal-header--redesigned", children: [
1401
+ /* @__PURE__ */ jsx3("div", { className: "rs-modal-header-nav-left", children: showBackButton && canGoBack && /* @__PURE__ */ jsx3(
1402
+ "button",
1403
+ {
1404
+ type: "button",
1405
+ className: "rs-modal-header-back",
1406
+ "aria-label": "Go back",
1407
+ onClick: handleBack,
1408
+ children: /* @__PURE__ */ jsx3(
1409
+ "svg",
1410
+ {
1411
+ viewBox: "0 0 24 24",
1412
+ fill: "none",
1413
+ stroke: "currentColor",
1414
+ strokeWidth: "2",
1415
+ children: /* @__PURE__ */ jsx3(
1416
+ "path",
1417
+ {
1418
+ strokeLinecap: "round",
1419
+ strokeLinejoin: "round",
1420
+ d: "M15.75 19.5L8.25 12l7.5-7.5"
1421
+ }
1422
+ )
1423
+ }
1424
+ )
1425
+ }
1426
+ ) }),
1427
+ /* @__PURE__ */ jsxs3("div", { className: "rs-modal-header-nav-center", children: [
1428
+ /* @__PURE__ */ jsxs3("div", { className: "rs-modal-header-title-row", children: [
1429
+ showLogo && logoUrl && /* @__PURE__ */ jsx3(
1430
+ "img",
1431
+ {
1432
+ src: logoUrl,
1433
+ alt: "",
1434
+ className: "rs-modal-logo",
1435
+ onError: (e) => {
1436
+ e.target.style.display = "none";
1437
+ }
1438
+ }
1439
+ ),
1440
+ /* @__PURE__ */ jsx3("span", { className: "rs-modal-header-title", children: title }),
1441
+ showStepper && /* @__PURE__ */ jsx3("div", { className: "rs-modal-progress", style: { marginLeft: 8 }, children: [0, 1].map((i) => /* @__PURE__ */ jsx3(
1442
+ "div",
1443
+ {
1444
+ className: `rs-modal-progress-dot ${i <= currentStepIndex ? "rs-modal-progress-dot--active" : "rs-modal-progress-dot--inactive"}`
1445
+ },
1446
+ i
1447
+ )) })
1448
+ ] }),
1449
+ balanceTitle && totalBalanceUsd !== null && /* @__PURE__ */ jsxs3("div", { className: "rs-modal-header-balance", children: [
1450
+ /* @__PURE__ */ jsx3("span", { className: "rs-modal-header-balance-label", children: balanceTitle }),
1451
+ /* @__PURE__ */ jsx3("span", { className: "rs-modal-header-balance-value", children: currencyFormatter.format(totalBalanceUsd) })
1452
+ ] })
1453
+ ] }),
1454
+ /* @__PURE__ */ jsx3("div", { className: "rs-modal-header-nav-right", children: /* @__PURE__ */ jsx3(
1455
+ "button",
1456
+ {
1457
+ type: "button",
1458
+ onClick: onClose,
1459
+ className: "rs-modal-close",
1460
+ "aria-label": "Close",
1461
+ children: /* @__PURE__ */ jsx3(
1462
+ "svg",
1463
+ {
1464
+ viewBox: "0 0 24 24",
1465
+ fill: "none",
1466
+ stroke: "currentColor",
1467
+ strokeWidth: "2",
1468
+ children: /* @__PURE__ */ jsx3(
1469
+ "path",
1470
+ {
1471
+ strokeLinecap: "round",
1472
+ strokeLinejoin: "round",
1473
+ d: "M6 18L18 6M6 6l12 12"
1474
+ }
1475
+ )
1476
+ }
1477
+ )
1478
+ }
1479
+ ) })
1480
+ ] }),
1481
+ /* @__PURE__ */ jsx3(
1482
+ WithdrawFlow,
1483
+ {
1484
+ dappWalletClient,
1485
+ dappPublicClient,
1486
+ dappAddress,
1487
+ safeAddress,
1488
+ sourceChain,
1489
+ sourceToken,
1490
+ targetChain,
1491
+ targetToken,
1492
+ recipient,
1493
+ amount: defaultAmount,
1494
+ service,
1495
+ rhinestoneApiKey,
1496
+ signerAddress,
1497
+ sessionChainIds,
1498
+ forceRegister,
1499
+ waitForFinalTx,
1500
+ reownWallet,
1501
+ onConnect,
1502
+ onRelayTransaction,
1503
+ onRequestConnect,
1504
+ connectButtonLabel,
1505
+ onStepChange: handleStepChange,
1506
+ onTotalBalanceChange: handleTotalBalanceChange,
1507
+ onClose,
1508
+ onConnected,
1509
+ onWithdrawSubmitted,
1510
+ onWithdrawComplete,
1511
+ onWithdrawFailed,
1512
+ onError,
1513
+ debug
1514
+ }
1515
+ )
1516
+ ] })
1517
+ }
1518
+ );
1519
+ }
1520
+
1521
+ export {
1522
+ WithdrawModal,
1523
+ WithdrawModalInner
1524
+ };