@oasisprotocol/privana-sdk 0.5.7 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  "use client";
2
- import { usePrivanaContext, createHostedAuthPendingStorageKey, clearHostedAuthPendingTransaction, HostedAuthRequiredError, HostedAuthError, createPkceVerifier, createPkceChallenge, createHostedAuthState, persistHostedAuthPendingTransaction, parseHostedAuthCallback, readHostedAuthPendingTransaction, HostedAuthStateMismatchError, buildHostedAuthSession, useSafePrivanaContext, usePrivateReadRequest, formatTokenAmount, submitPendingLock, PostDepositLockError, useDepositVerification, useEnsureCorrectChain, getTransactionReceipt, getBlockNumber, waitForTransactionReceipt, canUseBrowserStorage, clampLockAmount, requireDepositLockOwner, getWalletClient, createSignedLockRequest, requireServiceAddress, signWithdrawMessage, signLockMessage, signTransferMessage, signTransferLockedMessage, signModifyLockMessage, cn, stripHostedAuthCallbackParams, AccountingApiError, getBrowserStorageItem, removeBrowserStorageItem, isSignedLockUsable, setBrowserStorageItem, canUseSharedBrowserStorage, DEFAULT_LOCK_DURATION_SECONDS, parseTokenAmount, Skeleton, shortenAddress, formatCountdown, FiatOnRampForm, getExplorerAddressUrl, getExplorerLabel, formatTimeRemaining, getSharedBrowserStorageItem, removeSharedBrowserStorageItem, setSharedBrowserStorageItem } from './chunk-4VOTSHNC.js';
3
- export { AccountingApiError, Button, DEFAULT_LOCK_DURATION_SECONDS, DEFAULT_ONRAMP_LOCK_BUFFER, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PostDepositLockError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyLockBuffer, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createSignedLockRequest, getAccountingContract, getApiUrl, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, isHostedAuthRefreshActive, isHostedAuthSessionActive, isSignedLockUsable, loadPendingLock, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, requireDepositLockOwner, requireServiceAddress, savePendingLock, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useSiweAuth } from './chunk-4VOTSHNC.js';
2
+ import { usePrivanaContext, createHostedAuthPendingStorageKey, clearHostedAuthPendingTransaction, HostedAuthRequiredError, HostedAuthError, createPkceVerifier, createPkceChallenge, createHostedAuthState, persistHostedAuthPendingTransaction, parseHostedAuthCallback, readHostedAuthPendingTransaction, HostedAuthStateMismatchError, buildHostedAuthSession, useSafePrivanaContext, usePrivateReadRequest, formatTokenAmount, submitPendingLock, PostDepositLockError, useDepositVerification, useEnsureCorrectChain, getTransactionReceipt, getBlockNumber, waitForTransactionReceipt, canUseBrowserStorage, clampLockAmount, requireDepositLockOwner, getWalletClient, createSignedLockRequest, requireServiceAddress, signWithdrawMessage, signLockMessage, signTransferMessage, signTransferLockedMessage, signModifyLockMessage, cn, stripHostedAuthCallbackParams, AccountingApiError, getBrowserStorageItem, removeBrowserStorageItem, isSignedLockUsable, setBrowserStorageItem, resolveProductOnRamp, matchesProductOnRampScope, createProductOnRampOutcomeCallbacks, canUseSharedBrowserStorage, DEFAULT_LOCK_DURATION_SECONDS, isMoonPayProductOnRamp, parseTokenAmount, Skeleton, createProductOnRampFlowSnapshot, shortenAddress, formatCountdown, getExplorerAddressUrl, getExplorerLabel, formatTimeRemaining, getSharedBrowserStorageItem, removeSharedBrowserStorageItem, setSharedBrowserStorageItem, matchesOnRampTransaction, useTransakOnRamp, getTransakMinimumTargetBaseUnits, matchesFrozenOnRampToken, Button, canRetryOnRampVerification, FiatOnRampForm } from './chunk-RDYNQUMV.js';
3
+ export { AccountingApiError, Button, DEFAULT_LOCK_DURATION_SECONDS, DEFAULT_ONRAMP_LOCK_BUFFER, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PostDepositLockError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyLockBuffer, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createSignedLockRequest, getAccountingContract, getApiUrl, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, isHostedAuthRefreshActive, isHostedAuthSessionActive, isSignedLockUsable, loadPendingLock, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, requireDepositLockOwner, requireServiceAddress, savePendingLock, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useSiweAuth } from './chunk-RDYNQUMV.js';
4
4
  import { useState, useRef, useMemo, useCallback, useContext, useEffect, useId } from 'react';
5
5
  import { QueryClientContext, useQuery, useQueryClient, useMutation, keepPreviousData } from '@tanstack/react-query';
6
6
  import { useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction, useBalance as useBalance$1, useReadContract } from 'wagmi';
7
- import { zeroAddress, erc20Abi, formatUnits } from 'viem';
7
+ import { zeroAddress, erc20Abi, formatUnits, parseUnits } from 'viem';
8
8
  import { MoonPayProvider, MoonPayContext } from '@moonpay/moonpay-react';
9
9
  import * as DialogPrimitive from '@radix-ui/react-dialog';
10
- import { XIcon } from 'lucide-react';
10
+ import { XIcon, CircleCheckIcon, Loader2 } from 'lucide-react';
11
11
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
12
  import { toast } from 'sonner';
13
13
  import { QRCodeSVG } from 'qrcode.react';
@@ -788,6 +788,7 @@ function useWithdraw(options = {}) {
788
788
  const isStale = () => generation !== generationRef.current;
789
789
  try {
790
790
  if (!address || !walletClient) throw new Error("Wallet not connected");
791
+ setCurrentStep("preparing");
791
792
  const pendingResponse = await client.getPendingWithdrawals(address);
792
793
  if (isStale()) return void 0;
793
794
  const knownIndices = new Set(pendingResponse.pending_withdrawals.map((w) => w.index));
@@ -2455,15 +2456,356 @@ function TokenSelectorView({ selectedTokenId, onSelect }) {
2455
2456
  enabledTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-sm", children: "No tokens available" })
2456
2457
  ] }) });
2457
2458
  }
2459
+ function assertTransakCheckoutPreconditions({
2460
+ activeIntentId,
2461
+ canOpen,
2462
+ canRecreateSession
2463
+ }) {
2464
+ if (activeIntentId && !canRecreateSession) {
2465
+ throw new Error("Continue signed-intent recovery instead of reopening this checkout");
2466
+ }
2467
+ if (!canOpen) {
2468
+ throw new Error("Card checkout is unavailable; close this purchase and start again");
2469
+ }
2470
+ }
2471
+ function TransakCardWidgetView({
2472
+ token,
2473
+ providerAssetCode,
2474
+ amount,
2475
+ allowance,
2476
+ lockServiceAddress,
2477
+ onCredited,
2478
+ onLockSubmitted,
2479
+ onLockFailed,
2480
+ onLeave,
2481
+ onUnsafeToCloseChange,
2482
+ onActiveFlowChange,
2483
+ useOnRamp = useTransakOnRamp
2484
+ }) {
2485
+ const [actionError, setActionError] = useState(null);
2486
+ const [rowError, setRowError] = useState(null);
2487
+ const [lockSettled, setLockSettled] = useState(false);
2488
+ const [lockError, setLockError] = useState(null);
2489
+ const [ownedTerminal, setOwnedTerminal] = useState(false);
2490
+ const ownedIntentIdRef = useRef(null);
2491
+ const postDepositLock = useMemo(
2492
+ () => allowance ? {
2493
+ serviceAddress: lockServiceAddress,
2494
+ maxAmount: BigInt(allowance.value),
2495
+ lockDuration: allowance.lockDuration
2496
+ } : void 0,
2497
+ [allowance, lockServiceAddress]
2498
+ );
2499
+ const ownsRecord = useCallback(
2500
+ (record) => Boolean(
2501
+ ownedIntentIdRef.current && matchesOnRampTransaction(record, ownedIntentIdRef.current)
2502
+ ),
2503
+ []
2504
+ );
2505
+ const {
2506
+ status,
2507
+ activeIntentId,
2508
+ pending,
2509
+ activeVerificationId,
2510
+ error,
2511
+ finalityProgress,
2512
+ depositAddress,
2513
+ minDepositBaseUnits,
2514
+ selectedToken,
2515
+ isLaunching,
2516
+ isWidgetOpen,
2517
+ canRecreateSession,
2518
+ widget,
2519
+ launch,
2520
+ recreateSession,
2521
+ closeWidget,
2522
+ finishPendingVerification,
2523
+ refreshPending
2524
+ } = useOnRamp({
2525
+ tokenId: token.id,
2526
+ postDepositLock,
2527
+ iframeTitle: "Card checkout",
2528
+ iframeClassName: "h-[680px] w-full border-0",
2529
+ onCredited: (_depositTxHash, record) => {
2530
+ if (!ownsRecord(record)) return;
2531
+ onCredited?.();
2532
+ if (!postDepositLock) {
2533
+ setOwnedTerminal(true);
2534
+ onActiveFlowChange?.(false);
2535
+ }
2536
+ },
2537
+ onLockSubmitted: (_response, record) => {
2538
+ if (!ownsRecord(record)) return;
2539
+ setLockError(null);
2540
+ setLockSettled(true);
2541
+ setOwnedTerminal(true);
2542
+ onActiveFlowChange?.(false);
2543
+ onLockSubmitted?.();
2544
+ },
2545
+ onLockFailed: (nextError, record) => {
2546
+ if (!ownsRecord(record)) return;
2547
+ setLockError(nextError);
2548
+ setOwnedTerminal(true);
2549
+ onActiveFlowChange?.(false);
2550
+ onLockFailed?.(nextError);
2551
+ }
2552
+ });
2553
+ const ownedIntentId = ownedIntentIdRef.current ?? activeIntentId;
2554
+ useEffect(() => {
2555
+ if (activeIntentId && !ownedIntentIdRef.current) {
2556
+ ownedIntentIdRef.current = activeIntentId;
2557
+ }
2558
+ }, [activeIntentId]);
2559
+ let amountBaseUnits;
2560
+ try {
2561
+ amountBaseUnits = parseUnits(amount, token.decimals);
2562
+ } catch {
2563
+ }
2564
+ const minimumTargetBaseUnits = minDepositBaseUnits === void 0 ? void 0 : getTransakMinimumTargetBaseUnits(minDepositBaseUnits);
2565
+ const belowMinimum = amountBaseUnits !== void 0 && minimumTargetBaseUnits !== void 0 && amountBaseUnits < minimumTargetBaseUnits;
2566
+ const minimumUnknown = minimumTargetBaseUnits === void 0;
2567
+ const tokenMismatch = !matchesFrozenOnRampToken(token, selectedToken);
2568
+ const isSettling = status === "awaiting-delivery" || status === "verifying";
2569
+ const lockPending = !!allowance && status === "credited" && !lockSettled && !lockError;
2570
+ const ownedPending = ownedIntentId ? pending.filter((record) => matchesOnRampTransaction(record, ownedIntentId)) : [];
2571
+ const ownedFlowActive = !ownedTerminal && (Boolean(activeIntentId && activeIntentId === ownedIntentId) || ownedPending.length > 0 || lockPending);
2572
+ const canOpen = Boolean(selectedToken && depositAddress && amountBaseUnits && amountBaseUnits > 0n) && !tokenMismatch && !minimumUnknown && !belowMinimum && !isLaunching && !isWidgetOpen && !isSettling && status !== "credited" && (!activeIntentId || canRecreateSession);
2573
+ useEffect(() => {
2574
+ onActiveFlowChange?.(ownedFlowActive);
2575
+ }, [onActiveFlowChange, ownedFlowActive]);
2576
+ const unsafeToClose = isLaunching || lockPending;
2577
+ useEffect(() => {
2578
+ onUnsafeToCloseChange?.(unsafeToClose);
2579
+ return () => onUnsafeToCloseChange?.(false);
2580
+ }, [onUnsafeToCloseChange, unsafeToClose]);
2581
+ const runCheckout = useCallback(async () => {
2582
+ setActionError(null);
2583
+ try {
2584
+ assertTransakCheckoutPreconditions({ activeIntentId, canOpen, canRecreateSession });
2585
+ setLockError(null);
2586
+ setLockSettled(false);
2587
+ setOwnedTerminal(false);
2588
+ if (!activeIntentId) ownedIntentIdRef.current = null;
2589
+ if (activeIntentId) await recreateSession();
2590
+ else await launch({ providerAssetCode, quoteCurrencyAmount: amount });
2591
+ } catch (nextError) {
2592
+ setActionError(
2593
+ nextError instanceof Error ? nextError : new Error("Failed to open card checkout")
2594
+ );
2595
+ }
2596
+ }, [
2597
+ activeIntentId,
2598
+ amount,
2599
+ canOpen,
2600
+ canRecreateSession,
2601
+ launch,
2602
+ providerAssetCode,
2603
+ recreateSession
2604
+ ]);
2605
+ const handleClose = useCallback(async () => {
2606
+ setActionError(null);
2607
+ try {
2608
+ await closeWidget();
2609
+ } catch (nextError) {
2610
+ setActionError(
2611
+ nextError instanceof Error ? nextError : new Error("Failed to close card checkout")
2612
+ );
2613
+ }
2614
+ }, [closeWidget]);
2615
+ const handleLeave = useCallback(() => {
2616
+ setActionError(null);
2617
+ onActiveFlowChange?.(false);
2618
+ onLeave?.();
2619
+ }, [onActiveFlowChange, onLeave]);
2620
+ return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-4 rounded-[10px] p-5", children: [
2621
+ /* @__PURE__ */ jsxs("div", { children: [
2622
+ /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Complete your purchase" }),
2623
+ /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground mt-1 text-sm", children: [
2624
+ "Buy ",
2625
+ token.symbol,
2626
+ " and deposit it directly into your Privana balance."
2627
+ ] })
2628
+ ] }),
2629
+ /* @__PURE__ */ jsxs("div", { className: "border-border bg-card rounded-lg border p-3 text-sm", children: [
2630
+ /* @__PURE__ */ jsxs("p", { className: "text-foreground font-medium", children: [
2631
+ "Target receipt: at least ",
2632
+ amount,
2633
+ " ",
2634
+ token.symbol
2635
+ ] }),
2636
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: "In Transak, adjust the fiat amount until \u201CYou receive\u201D is at least this target." }),
2637
+ allowance && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: "The pre-signed lock is 98% of the target, capped by the service allowance." })
2638
+ ] }),
2639
+ widget ? /* @__PURE__ */ jsxs("div", { className: "border-border bg-card overflow-hidden rounded-xl border", children: [
2640
+ /* @__PURE__ */ jsxs("div", { className: "border-border flex items-center justify-between border-b px-4 py-2 text-sm", children: [
2641
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Card checkout" }),
2642
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleClose(), children: "Close" })
2643
+ ] }),
2644
+ widget
2645
+ ] }) : status === "credited" && !lockPending && !lockError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-8 text-center", children: [
2646
+ /* @__PURE__ */ jsx(CircleCheckIcon, { className: "text-primary size-8", "aria-hidden": true }),
2647
+ /* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium", children: "Purchase credited" }),
2648
+ /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
2649
+ "Your ",
2650
+ token.symbol,
2651
+ " deposit is now available in your balance."
2652
+ ] })
2653
+ ] }) : !activeIntentId || canRecreateSession ? /* @__PURE__ */ jsxs(Button, { type: "button", disabled: !canOpen, onClick: () => void runCheckout(), children: [
2654
+ isLaunching && /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", "aria-hidden": true }),
2655
+ isLaunching ? "Opening checkout\u2026" : activeIntentId ? "Reopen card checkout" : allowance ? "Sign policy and open checkout" : "Open card checkout"
2656
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "This signed purchase is in recovery. Do not open another checkout for it." }),
2657
+ !depositAddress && !error && !actionError && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Preparing your secure deposit address\u2026" }),
2658
+ tokenMismatch && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: "The token configuration changed. Close this purchase and start again." }),
2659
+ !tokenMismatch && depositAddress && minimumUnknown && !error && !actionError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: "The minimum purchase amount is unavailable. Close this purchase and try again." }),
2660
+ belowMinimum && minimumTargetBaseUnits !== void 0 && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
2661
+ "Minimum target is ",
2662
+ formatUnits(minimumTargetBaseUnits, token.decimals),
2663
+ " ",
2664
+ token.symbol,
2665
+ ", including a 5% delivery buffer."
2666
+ ] }),
2667
+ isSettling && pending.length === 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
2668
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
2669
+ "Verifying your purchase\u2026"
2670
+ ] }),
2671
+ lockPending && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
2672
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": true }),
2673
+ "Purchase credited. Locking your funds\u2026"
2674
+ ] }),
2675
+ pending.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
2676
+ /* @__PURE__ */ jsx("p", { className: "text-foreground text-sm font-medium", children: "Validating purchases" }),
2677
+ pending.map((record) => {
2678
+ const isActivelyVerifying = record.transaction_id === activeVerificationId;
2679
+ const progress = parseFinalityProgress(finalityProgress[record.transaction_id]);
2680
+ const showRetry = canRetryOnRampVerification(record, activeVerificationId);
2681
+ return /* @__PURE__ */ jsxs(
2682
+ "div",
2683
+ {
2684
+ className: "border-border flex flex-col gap-2 rounded-md border p-2",
2685
+ children: [
2686
+ /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-1 text-xs", children: [
2687
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin", "aria-hidden": true }),
2688
+ record.on_chain_tx_hash ? progress ?? (isActivelyVerifying ? "Verifying\u2026" : "Ready to verify") : "Waiting for provider delivery\u2026"
2689
+ ] }),
2690
+ rowError?.id === record.transaction_id && /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs", children: rowError.message }),
2691
+ showRetry && /* @__PURE__ */ jsx(
2692
+ Button,
2693
+ {
2694
+ type: "button",
2695
+ variant: "outline",
2696
+ size: "sm",
2697
+ onClick: async () => {
2698
+ setRowError(null);
2699
+ try {
2700
+ await finishPendingVerification(record);
2701
+ } catch (nextError) {
2702
+ setRowError({
2703
+ id: record.transaction_id,
2704
+ message: nextError instanceof Error ? nextError.message : "Verification failed"
2705
+ });
2706
+ }
2707
+ },
2708
+ children: "Retry verification"
2709
+ }
2710
+ ),
2711
+ !record.on_chain_tx_hash && /* @__PURE__ */ jsx(
2712
+ Button,
2713
+ {
2714
+ type: "button",
2715
+ variant: "outline",
2716
+ size: "sm",
2717
+ onClick: () => void refreshPending(),
2718
+ children: "Refresh delivery"
2719
+ }
2720
+ )
2721
+ ]
2722
+ },
2723
+ record.transaction_id
2724
+ );
2725
+ })
2726
+ ] }),
2727
+ onLeave && ownedFlowActive && !unsafeToClose && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2728
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleLeave, children: "Leave checkout" }),
2729
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: "Privana will keep this signed purchase available for exact recovery." })
2730
+ ] }),
2731
+ (actionError ?? error) && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", role: "alert", children: (actionError ?? error)?.message }),
2732
+ lockError && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", role: "alert", children: [
2733
+ "Purchase credited, but locking the funds failed: ",
2734
+ lockError.message
2735
+ ] })
2736
+ ] });
2737
+ }
2738
+ function parseFinalityProgress(message) {
2739
+ if (!message) return null;
2740
+ const match = message.match(/(\d+\/\d+)\s+confirmations/i);
2741
+ return match ? `${match[1]} confirmations` : null;
2742
+ }
2458
2743
  function CreditCardWidgetView({
2459
2744
  token,
2745
+ onRamp,
2460
2746
  amount,
2461
2747
  allowance,
2748
+ lockServiceAddress,
2462
2749
  onCredited,
2463
2750
  onLockSubmitted,
2464
- onLockFailed
2751
+ onLockFailed,
2752
+ onLeave,
2753
+ onUnsafeToCloseChange,
2754
+ onActiveFlowChange
2755
+ }) {
2756
+ if (onRamp.unavailableReason || !token || !onRamp.provider || !onRamp.providerAssetCode) {
2757
+ return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-2 rounded-[10px] p-5", children: [
2758
+ /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Card purchases unavailable" }),
2759
+ /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: onRamp.unavailableReason ?? "The card on-ramp is not configured for this token." })
2760
+ ] });
2761
+ }
2762
+ if (onRamp.provider === "transak") {
2763
+ return /* @__PURE__ */ jsx(
2764
+ TransakCardWidgetView,
2765
+ {
2766
+ token,
2767
+ providerAssetCode: onRamp.providerAssetCode,
2768
+ amount,
2769
+ allowance,
2770
+ lockServiceAddress,
2771
+ onCredited,
2772
+ onLockSubmitted,
2773
+ onLockFailed,
2774
+ onLeave,
2775
+ onUnsafeToCloseChange,
2776
+ onActiveFlowChange
2777
+ }
2778
+ );
2779
+ }
2780
+ return /* @__PURE__ */ jsx(
2781
+ MoonPayCardWidgetView,
2782
+ {
2783
+ token,
2784
+ amount,
2785
+ allowance,
2786
+ lockServiceAddress,
2787
+ onCredited,
2788
+ onLockSubmitted,
2789
+ onLockFailed,
2790
+ onLeaveFlow: onLeave,
2791
+ onUnsafeToCloseChange,
2792
+ onActiveFlowChange
2793
+ }
2794
+ );
2795
+ }
2796
+ function MoonPayCardWidgetView({
2797
+ token,
2798
+ amount,
2799
+ allowance,
2800
+ lockServiceAddress,
2801
+ onCredited,
2802
+ onLockSubmitted,
2803
+ onLockFailed,
2804
+ onLeaveFlow,
2805
+ onUnsafeToCloseChange,
2806
+ onActiveFlowChange
2465
2807
  }) {
2466
- const moonpayCurrencyCode = token?.moonpayCurrencyCode;
2808
+ const moonpayCurrencyCode = token.moonpayCurrencyCode;
2467
2809
  const containerRef = useRef(null);
2468
2810
  const [widgetTheme, setWidgetTheme] = useState();
2469
2811
  useEffect(() => {
@@ -2471,26 +2813,32 @@ function CreditCardWidgetView({
2471
2813
  }, []);
2472
2814
  return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
2473
2815
  /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Complete your purchase" }),
2474
- token && moonpayCurrencyCode ? /* @__PURE__ */ jsx(
2816
+ moonpayCurrencyCode ? /* @__PURE__ */ jsx(
2475
2817
  FiatOnRampForm,
2476
2818
  {
2477
2819
  tokenId: token.id,
2820
+ frozenToken: token,
2478
2821
  currencyCode: moonpayCurrencyCode,
2479
2822
  quoteCurrencyAmount: amount || void 0,
2480
2823
  lockAmount: true,
2481
2824
  variant: "embedded",
2482
2825
  autoStart: true,
2483
2826
  theme: widgetTheme,
2827
+ onlyRouteActiveIntentCallbacks: true,
2828
+ onUnsafeToCloseChange,
2829
+ onActiveFlowChange,
2484
2830
  postDepositLock: allowance ? {
2831
+ serviceAddress: lockServiceAddress,
2485
2832
  maxAmount: BigInt(allowance.value),
2486
2833
  lockDuration: allowance.lockDuration
2487
2834
  } : void 0,
2488
2835
  onCredited,
2489
2836
  onLockSubmitted,
2490
- onLockFailed
2837
+ onLockFailed,
2838
+ onLeaveFlow
2491
2839
  }
2492
2840
  ) : /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
2493
- token?.symbol ?? "This token",
2841
+ token.symbol,
2494
2842
  " isn\u2019t available for card purchases yet."
2495
2843
  ] })
2496
2844
  ] });
@@ -2799,7 +3147,7 @@ function CopyIcon() {
2799
3147
  }
2800
3148
  );
2801
3149
  }
2802
- function CircleCheckIcon() {
3150
+ function CircleCheckIcon2() {
2803
3151
  return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
2804
3152
  /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "9", stroke: "currentColor", strokeWidth: "1.5" }),
2805
3153
  /* @__PURE__ */ jsx(
@@ -2918,7 +3266,7 @@ function PolicyTermRow({
2918
3266
  "mt-0.5 shrink-0",
2919
3267
  kind === "permission" ? "text-emerald-500" : "text-orange-500"
2920
3268
  ),
2921
- children: kind === "permission" ? /* @__PURE__ */ jsx(CircleCheckIcon, {}) : /* @__PURE__ */ jsx(CircleXIcon, {})
3269
+ children: kind === "permission" ? /* @__PURE__ */ jsx(CircleCheckIcon2, {}) : /* @__PURE__ */ jsx(CircleXIcon, {})
2922
3270
  }
2923
3271
  ),
2924
3272
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
@@ -2978,6 +3326,7 @@ function AllowancePolicySection({
2978
3326
  function DepositView({
2979
3327
  source,
2980
3328
  selectedToken,
3329
+ onRamp,
2981
3330
  amount,
2982
3331
  allowance,
2983
3332
  externalMinimum,
@@ -2996,13 +3345,15 @@ function DepositView({
2996
3345
  const isConnectedSource = source === "connected";
2997
3346
  const isExternal = source === "external";
2998
3347
  const isCreditCard = source === "credit-card";
3348
+ const isMoonPayCard = isCreditCard && isMoonPayProductOnRamp(onRamp);
3349
+ const isTransakCard = isCreditCard && onRamp.provider === "transak";
2999
3350
  const isNative = selectedToken?.contract === zeroAddress;
3000
- const { data: nativeBalanceData } = useBalance$1({
3351
+ const { data: nativeBalanceData, isLoading: isNativeBalanceLoading } = useBalance$1({
3001
3352
  address,
3002
3353
  chainId: targetChain?.id,
3003
3354
  query: { enabled: isConnectedSource && !!address && !!selectedToken && isNative }
3004
3355
  });
3005
- const { data: erc20Balance } = useReadContract({
3356
+ const { data: erc20Balance, isLoading: isErc20BalanceLoading } = useReadContract({
3006
3357
  address: selectedToken?.contract,
3007
3358
  abi: erc20Abi,
3008
3359
  functionName: "balanceOf",
@@ -3011,6 +3362,7 @@ function DepositView({
3011
3362
  query: { enabled: isConnectedSource && !!address && !!selectedToken && !isNative }
3012
3363
  });
3013
3364
  const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
3365
+ const isWalletBalanceLoading = isNative ? isNativeBalanceLoading : isErc20BalanceLoading;
3014
3366
  const formattedWalletBalance = walletBalance != null && selectedToken ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
3015
3367
  const {
3016
3368
  minBuyAmount: moonpayMinBuy,
@@ -3019,15 +3371,15 @@ function DepositView({
3019
3371
  } = useMoonpayLimits({
3020
3372
  currencyCode: selectedToken?.moonpayCurrencyCode,
3021
3373
  apiBaseUrl: networkConfig.moonpayApiUrl,
3022
- enabled: isCreditCard
3374
+ enabled: isMoonPayCard
3023
3375
  });
3024
3376
  const hasValidAmount = !!amount && parseFloat(amount) > 0;
3025
- const maxAmountDecimals = isCreditCard ? 2 : selectedToken?.decimals;
3377
+ const maxAmountDecimals = isMoonPayCard ? 2 : selectedToken?.decimals;
3026
3378
  const tooManyDecimals = hasValidAmount && maxAmountDecimals != null && amount.includes(".") && amount.split(".")[1].length > maxAmountDecimals;
3027
3379
  const exceedsBalance = isConnectedSource && hasValidAmount && !tooManyDecimals && !!selectedToken && walletBalance != null && parseTokenAmount(amount, selectedToken.decimals) > walletBalance;
3028
- const belowMoonpayMin = isCreditCard && hasValidAmount && moonpayMinBuy != null && parseFloat(amount) < moonpayMinBuy;
3029
- const moonpayLimitsUnready = isCreditCard && !!selectedToken?.moonpayCurrencyCode && moonpayMinBuy == null;
3030
- const creditCardUnavailable = isCreditCard && !!selectedToken && !selectedToken.moonpayCurrencyCode;
3380
+ const belowMoonpayMin = isMoonPayCard && hasValidAmount && moonpayMinBuy != null && parseFloat(amount) < moonpayMinBuy;
3381
+ const moonpayLimitsUnready = isMoonPayCard && !!onRamp.providerAssetCode && moonpayMinBuy == null;
3382
+ const creditCardUnavailable = isCreditCard && !!onRamp.unavailableReason;
3031
3383
  const externalTokenUnavailable = isExternal && !!selectedToken && isNative;
3032
3384
  const externalMinimumRequired = isExternal && !!allowance && !!selectedToken;
3033
3385
  const externalMinimumLoading = externalMinimumRequired && externalMinimum === void 0;
@@ -3037,13 +3389,13 @@ function DepositView({
3037
3389
  const needsConnect = (isConnectedSource || externalNeedsWallet) && !isConnected;
3038
3390
  const canDeposit = hasValidAmount && !!selectedToken && !tooManyDecimals && !exceedsBalance && !belowMoonpayMin && !moonpayLimitsUnready && !creditCardUnavailable && !externalTokenUnavailable && !externalMinimumLoading && !externalMinimumUnavailable && !belowExternalMinimum && !needsConnect;
3039
3391
  const handleMax = () => {
3040
- const max = formattedWalletBalance.replace(/\s/g, "");
3041
- if (parseFloat(max) > 0) onAmountChange(max);
3392
+ if (selectedToken && walletBalance != null && walletBalance > 0n)
3393
+ onAmountChange(formatUnits(walletBalance, selectedToken.decimals));
3042
3394
  };
3043
3395
  return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
3044
3396
  isCreditCard ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3045
- /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Buy with credit card and deposit" }),
3046
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
3397
+ /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Buy with card and deposit" }),
3398
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: isTransakCard ? "Enter your target deposit amount and continue." : "Enter your deposit amount and proceed to sign a policy." })
3047
3399
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3048
3400
  /* @__PURE__ */ jsxs("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: [
3049
3401
  "Deposit from ",
@@ -3058,7 +3410,7 @@ function DepositView({
3058
3410
  {
3059
3411
  type: "button",
3060
3412
  onClick: onSelectToken,
3061
- disabled: isSubmitting,
3413
+ disabled: isSubmitting || isCreditCard && onRamp.tokenSelectionLocked,
3062
3414
  className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left disabled:cursor-not-allowed disabled:opacity-50",
3063
3415
  children: [
3064
3416
  selectedToken ? /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -3071,7 +3423,7 @@ function DepositView({
3071
3423
  ] })
3072
3424
  ] })
3073
3425
  ] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground flex-1 text-sm", children: "Select token" }),
3074
- /* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
3426
+ (!isCreditCard || !onRamp.tokenSelectionLocked) && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
3075
3427
  ]
3076
3428
  }
3077
3429
  )
@@ -3079,11 +3431,10 @@ function DepositView({
3079
3431
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
3080
3432
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3081
3433
  /* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
3082
- isConnectedSource && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
3083
- "Available ",
3084
- formattedWalletBalance,
3434
+ isConnectedSource && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex items-center gap-1 text-sm", children: [
3435
+ "Available",
3085
3436
  " ",
3086
- selectedToken?.symbol
3437
+ isWalletBalanceLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" }) : `${formattedWalletBalance} ${selectedToken?.symbol ?? ""}`
3087
3438
  ] })
3088
3439
  ] }),
3089
3440
  /* @__PURE__ */ jsxs(
@@ -3114,7 +3465,8 @@ function DepositView({
3114
3465
  {
3115
3466
  type: "button",
3116
3467
  onClick: handleMax,
3117
- className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
3468
+ disabled: isWalletBalanceLoading,
3469
+ className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors disabled:cursor-default disabled:opacity-50",
3118
3470
  children: "MAX"
3119
3471
  }
3120
3472
  ) : isCreditCard ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: selectedToken?.symbol ?? "USD" }) : selectedToken && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: selectedToken.symbol })
@@ -3141,12 +3493,9 @@ function DepositView({
3141
3493
  ] }),
3142
3494
  externalMinimumLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking minimum deposit\u2026" }),
3143
3495
  externalMinimumUnavailable && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load the minimum deposit. Please try again." }),
3144
- isCreditCard && moonpayLimitsError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load purchase limits. Please try again." }),
3145
- isCreditCard && moonpayLimitsLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking purchase limits\u2026" }),
3146
- creditCardUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
3147
- selectedToken?.symbol ?? "This token",
3148
- " isn\u2019t available for card purchases yet."
3149
- ] }),
3496
+ isMoonPayCard && moonpayLimitsError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load purchase limits. Please try again." }),
3497
+ isMoonPayCard && moonpayLimitsLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking purchase limits\u2026" }),
3498
+ creditCardUnavailable && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: onRamp.unavailableReason }),
3150
3499
  externalTokenUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
3151
3500
  selectedToken?.symbol ?? "This token",
3152
3501
  " can\u2019t be used for external deposits. Choose an ERC20 token."
@@ -3173,7 +3522,7 @@ function DepositView({
3173
3522
  if (selectedToken) onSubmit({ source, tokenId: selectedToken.id, amount });
3174
3523
  },
3175
3524
  className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
3176
- children: needsConnect ? "Connect Wallet" : allowance ? "Sign Policy and Deposit" : "Deposit"
3525
+ children: needsConnect ? "Connect Wallet" : isTransakCard ? "Continue" : allowance ? "Sign Policy and Deposit" : "Deposit"
3177
3526
  }
3178
3527
  )
3179
3528
  ] });
@@ -3485,9 +3834,11 @@ function ExternalDepositView({
3485
3834
  ] })
3486
3835
  ] });
3487
3836
  }
3488
- function MoonPayGate({ enabled, children }) {
3489
- const { networkConfig } = usePrivanaContext();
3490
- const apiKey = networkConfig.moonpayApiKey;
3837
+ function MoonPayGate({
3838
+ enabled,
3839
+ apiKey,
3840
+ children
3841
+ }) {
3491
3842
  if (!enabled || !apiKey) return /* @__PURE__ */ jsx(Fragment, { children });
3492
3843
  return /* @__PURE__ */ jsx(MoonPayProvider, { apiKey, children });
3493
3844
  }
@@ -3574,15 +3925,52 @@ function DepositModalContent({
3574
3925
  onCloseBlockedChange,
3575
3926
  onExit
3576
3927
  }) {
3577
- const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2, getTokenById } = usePrivanaContext();
3928
+ const {
3929
+ serviceName,
3930
+ enabledTokens,
3931
+ defaultToken,
3932
+ hostedAuthConfig,
3933
+ serviceAddress,
3934
+ getChainById: getChainById2,
3935
+ getTokenById,
3936
+ networkConfig,
3937
+ onRamp
3938
+ } = usePrivanaContext();
3578
3939
  const { address } = useAccount();
3940
+ const { privateReadQueryScope } = usePrivateReadRequest();
3941
+ const [privateReadApiUrl, accountingChainId, beneficiaryAddress] = privateReadQueryScope;
3579
3942
  const appName = serviceName ?? "Privana";
3580
3943
  const [activeTab, setActiveTab] = useState(defaultTab);
3581
3944
  const [view, setView] = useState("method");
3582
3945
  const [source, setSource] = useState("connected");
3583
3946
  const [selectedTokenId, setSelectedTokenId] = useState(defaultToken?.id ?? "");
3584
3947
  const [amount, setAmount] = useState("");
3585
- const selectedToken = enabledTokens.find((t) => t.id === selectedTokenId) ?? defaultToken;
3948
+ const [cardFlow, setCardFlow] = useState(null);
3949
+ const [cardFlowActive, setCardFlowActive] = useState(false);
3950
+ const [cardUnsafeToClose, setCardUnsafeToClose] = useState(false);
3951
+ const nextCardFlowId = useRef(0);
3952
+ const stateSelectedToken = enabledTokens.find((t) => t.id === selectedTokenId) ?? defaultToken;
3953
+ const cardOnRamp = useMemo(
3954
+ () => resolveProductOnRamp({
3955
+ config: onRamp,
3956
+ enabledTokens,
3957
+ legacyToken: stateSelectedToken,
3958
+ moonpayApiKey: networkConfig.moonpayApiKey
3959
+ }),
3960
+ [enabledTokens, networkConfig.moonpayApiKey, onRamp, stateSelectedToken]
3961
+ );
3962
+ const cardOnRampScope = useMemo(
3963
+ () => ({
3964
+ apiUrl: privateReadApiUrl,
3965
+ accountingChainId,
3966
+ accountingContract: networkConfig.accountingContract,
3967
+ beneficiaryAddress
3968
+ }),
3969
+ [accountingChainId, beneficiaryAddress, networkConfig.accountingContract, privateReadApiUrl]
3970
+ );
3971
+ const mountedCardFlow = cardFlow && matchesProductOnRampScope(cardFlow.scope, cardOnRampScope) ? cardFlow : null;
3972
+ const displayedCardOnRamp = view === "credit-card-widget" && mountedCardFlow ? mountedCardFlow.selection : cardOnRamp;
3973
+ const selectedToken = source === "credit-card" ? displayedCardOnRamp.token : stateSelectedToken;
3586
3974
  const externalDepositAddress = useDepositAddress({
3587
3975
  enabled: source === "external" && (view === "deposit" || view === "external-deposit")
3588
3976
  });
@@ -3596,6 +3984,9 @@ function DepositModalContent({
3596
3984
  setView("method");
3597
3985
  setAmount("");
3598
3986
  setSelectedTokenId("");
3987
+ setCardFlow(null);
3988
+ setCardFlowActive(false);
3989
+ setCardUnsafeToClose(false);
3599
3990
  }
3600
3991
  }, [address, hostedAuthConfig]);
3601
3992
  const openDeposit = (next) => {
@@ -3618,9 +4009,33 @@ function DepositModalContent({
3618
4009
  }
3619
4010
  };
3620
4011
  const finishCardPurchase = () => {
4012
+ setCardFlowActive(false);
4013
+ setCardUnsafeToClose(false);
3621
4014
  setAmount("");
3622
4015
  onDepositSuccess?.();
3623
4016
  };
4017
+ const leaveCardPurchase = useCallback(() => {
4018
+ setCardFlow(null);
4019
+ setCardFlowActive(false);
4020
+ setCardUnsafeToClose(false);
4021
+ setAmount("");
4022
+ setView("deposit");
4023
+ }, []);
4024
+ useEffect(() => {
4025
+ if (cardFlow && !matchesProductOnRampScope(cardFlow.scope, cardOnRampScope)) {
4026
+ leaveCardPurchase();
4027
+ }
4028
+ }, [cardFlow, cardOnRampScope, leaveCardPurchase]);
4029
+ const cardOutcomeCallbacks = mountedCardFlow ? createProductOnRampOutcomeCallbacks({
4030
+ requiresLock: mountedCardFlow.requiresLock,
4031
+ onComplete: finishCardPurchase,
4032
+ onLockFailed: (err) => {
4033
+ setCardFlowActive(false);
4034
+ setCardUnsafeToClose(false);
4035
+ setLockFailure(err);
4036
+ onLockFailed?.(err);
4037
+ }
4038
+ }) : null;
3624
4039
  const {
3625
4040
  txHash,
3626
4041
  isGettingAddress,
@@ -3690,7 +4105,7 @@ function DepositModalContent({
3690
4105
  setView("deposit");
3691
4106
  }
3692
4107
  }, [allowance, externalLock.session, view]);
3693
- const isUnsafeToClose = (isGettingAddress || isSendingTransaction) && !cancelled || externalLock.isSigning || externalLock.isSubmittingLock;
4108
+ const isUnsafeToClose = (isGettingAddress || isSendingTransaction) && !cancelled || externalLock.isSigning || externalLock.isSubmittingLock || cardUnsafeToClose;
3694
4109
  useEffect(() => {
3695
4110
  onCloseBlockedChange?.(isUnsafeToClose);
3696
4111
  }, [isUnsafeToClose, onCloseBlockedChange]);
@@ -3733,6 +4148,28 @@ function DepositModalContent({
3733
4148
  return;
3734
4149
  }
3735
4150
  if (args.source === "credit-card") {
4151
+ if (cardFlow && cardFlowActive) {
4152
+ setView("credit-card-widget");
4153
+ return;
4154
+ }
4155
+ try {
4156
+ setCardFlow(
4157
+ createProductOnRampFlowSnapshot({
4158
+ id: ++nextCardFlowId.current,
4159
+ selection: cardOnRamp,
4160
+ amount: args.amount,
4161
+ allowance,
4162
+ lockServiceAddress: serviceAddress,
4163
+ moonpayApiKey: networkConfig.moonpayApiKey,
4164
+ scope: cardOnRampScope
4165
+ })
4166
+ );
4167
+ } catch (error) {
4168
+ toast.error(error instanceof Error ? error.message : "Card purchase is unavailable");
4169
+ return;
4170
+ }
4171
+ setCardFlowActive(false);
4172
+ setCardUnsafeToClose(false);
3736
4173
  setView("credit-card-widget");
3737
4174
  return;
3738
4175
  }
@@ -3810,6 +4247,12 @@ function DepositModalContent({
3810
4247
  setCancelled(false);
3811
4248
  resetDeposit();
3812
4249
  if (view === "external-deposit") setView("deposit");
4250
+ if (view === "credit-card-widget") {
4251
+ setCardFlow(null);
4252
+ setCardFlowActive(false);
4253
+ setCardUnsafeToClose(false);
4254
+ setView("deposit");
4255
+ }
3813
4256
  };
3814
4257
  const handleExternalLockRetry = () => {
3815
4258
  externalLock.retryAfterCredit().catch((err) => {
@@ -3833,12 +4276,20 @@ function DepositModalContent({
3833
4276
  retryVerification().catch(() => {
3834
4277
  });
3835
4278
  };
3836
- const back = view === "external-deposit" ? { to: "deposit", label: "Deposit from External Wallet" } : view === "credit-card-widget" ? { to: "deposit", label: "Buy with credit card and deposit" } : view === "select-token" ? { to: "deposit", label: "Deposit" } : { to: "method", label: "Deposit Method" };
4279
+ const back = view === "external-deposit" ? { to: "deposit", label: "Deposit from External Wallet" } : view === "credit-card-widget" ? { to: "deposit", label: "Buy with card and deposit" } : view === "select-token" ? { to: "deposit", label: "Deposit" } : { to: "method", label: "Deposit Method" };
3837
4280
  const goBack = () => {
3838
4281
  if (view === "external-deposit" && externalLock.session) {
3839
4282
  toast.error("Cancel the active deposit from the address screen before going back");
3840
4283
  return;
3841
4284
  }
4285
+ if (view === "credit-card-widget" && (cardUnsafeToClose || cardFlowActive)) {
4286
+ toast.error("Finish or recover the active card purchase before going back");
4287
+ return;
4288
+ }
4289
+ if (view === "credit-card-widget") {
4290
+ setCardFlow(null);
4291
+ setCardUnsafeToClose(false);
4292
+ }
3842
4293
  if (back.to === "method") {
3843
4294
  setAmount("");
3844
4295
  setSelectedTokenId("");
@@ -3924,8 +4375,8 @@ function DepositModalContent({
3924
4375
  ] }),
3925
4376
  activeView === "method" && /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
3926
4377
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
3927
- /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: activeTab === "crypto" ? "Choose the deposit method" : "Buy with credit card and deposit" }),
3928
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: activeTab === "crypto" ? "Choose the deposit method." : "Use credit card to buy crypto and deposit" })
4378
+ /* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: activeTab === "crypto" ? "Choose the deposit method" : "Buy with card and deposit" }),
4379
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: activeTab === "crypto" ? "Choose the deposit method." : "Buy crypto by card and deposit it into your account." })
3929
4380
  ] }),
3930
4381
  /* @__PURE__ */ jsx(MethodTabs, { activeTab, onTabChange: setActiveTab }),
3931
4382
  activeTab === "crypto" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -3954,8 +4405,8 @@ function DepositModalContent({
3954
4405
  ] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx(
3955
4406
  MethodOption,
3956
4407
  {
3957
- title: "Moonpay",
3958
- description: "on selected address",
4408
+ title: "Buy with card",
4409
+ description: "Purchase and deposit directly into your account.",
3959
4410
  onClick: () => {
3960
4411
  onSelectCreditCard?.();
3961
4412
  openDeposit("credit-card");
@@ -3963,26 +4414,35 @@ function DepositModalContent({
3963
4414
  }
3964
4415
  ) })
3965
4416
  ] }),
3966
- activeView === "deposit" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: source === "credit-card", children: /* @__PURE__ */ jsx(
3967
- DepositView,
4417
+ activeView === "deposit" && /* @__PURE__ */ jsx(
4418
+ MoonPayGate,
3968
4419
  {
3969
- source,
3970
- selectedToken,
3971
- amount,
3972
- allowance,
3973
- externalMinimum,
3974
- onAmountChange: setAmount,
3975
- onSelectToken: () => setView("select-token"),
3976
- onConnectWallet,
3977
- onSubmit: handleSubmit,
3978
- isSubmitting: isPending || externalLock.isSigning || externalLock.isSubmittingLock
4420
+ enabled: source === "credit-card" && cardOnRamp.provider === "moonpay" && !cardOnRamp.unavailableReason,
4421
+ apiKey: networkConfig.moonpayApiKey,
4422
+ children: /* @__PURE__ */ jsx(
4423
+ DepositView,
4424
+ {
4425
+ source,
4426
+ selectedToken,
4427
+ onRamp: cardOnRamp,
4428
+ amount,
4429
+ allowance,
4430
+ externalMinimum,
4431
+ onAmountChange: setAmount,
4432
+ onSelectToken: () => setView("select-token"),
4433
+ onConnectWallet,
4434
+ onSubmit: handleSubmit,
4435
+ isSubmitting: isPending || externalLock.isSigning || externalLock.isSubmittingLock
4436
+ }
4437
+ )
3979
4438
  }
3980
- ) }),
4439
+ ),
3981
4440
  activeView === "select-token" && /* @__PURE__ */ jsx(
3982
4441
  TokenSelectorView,
3983
4442
  {
3984
4443
  selectedTokenId: selectedToken?.id,
3985
4444
  onSelect: (id) => {
4445
+ if (id !== selectedTokenId) setAmount("");
3986
4446
  setSelectedTokenId(id);
3987
4447
  setView("deposit");
3988
4448
  }
@@ -3999,20 +4459,30 @@ function DepositModalContent({
3999
4459
  lock: externalLock.session ? externalLock : void 0
4000
4460
  }
4001
4461
  ),
4002
- activeView === "credit-card-widget" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: true, children: /* @__PURE__ */ jsx(
4003
- CreditCardWidgetView,
4462
+ activeView === "credit-card-widget" && mountedCardFlow && /* @__PURE__ */ jsx(
4463
+ MoonPayGate,
4004
4464
  {
4005
- token: selectedToken,
4006
- amount,
4007
- allowance,
4008
- onCredited: allowance ? void 0 : finishCardPurchase,
4009
- onLockSubmitted: finishCardPurchase,
4010
- onLockFailed: (err) => {
4011
- setLockFailure(err);
4012
- onLockFailed?.(err);
4013
- }
4465
+ enabled: mountedCardFlow.selection.provider === "moonpay",
4466
+ apiKey: mountedCardFlow.moonpayApiKey,
4467
+ children: /* @__PURE__ */ jsx(
4468
+ CreditCardWidgetView,
4469
+ {
4470
+ token: mountedCardFlow.selection.token,
4471
+ onRamp: mountedCardFlow.selection,
4472
+ amount: mountedCardFlow.amount,
4473
+ allowance: mountedCardFlow.allowance,
4474
+ lockServiceAddress: mountedCardFlow.lockServiceAddress,
4475
+ onUnsafeToCloseChange: setCardUnsafeToClose,
4476
+ onActiveFlowChange: setCardFlowActive,
4477
+ onCredited: cardOutcomeCallbacks?.onCredited,
4478
+ onLockSubmitted: cardOutcomeCallbacks?.onLockSubmitted,
4479
+ onLockFailed: cardOutcomeCallbacks?.onLockFailed,
4480
+ onLeave: leaveCardPurchase
4481
+ },
4482
+ mountedCardFlow.id
4483
+ )
4014
4484
  }
4015
- ) })
4485
+ )
4016
4486
  ] });
4017
4487
  }
4018
4488
  function DepositModal({ open, onClose, ...handlers }) {
@@ -4114,6 +4584,15 @@ function WithdrawView({
4114
4584
  return "pending";
4115
4585
  };
4116
4586
  const withdrawSteps = [
4587
+ {
4588
+ label: "Preparing withdrawal",
4589
+ status: getStepStatus("preparing", [
4590
+ "switching-chain",
4591
+ "signing",
4592
+ "submitting",
4593
+ "processing"
4594
+ ])
4595
+ },
4117
4596
  {
4118
4597
  label: "Switching to signing chain",
4119
4598
  status: getStepStatus("switching-chain", ["signing", "submitting", "processing"])
@@ -4136,8 +4615,8 @@ function WithdrawView({
4136
4615
  const canWithdraw = isConnected && hasValidAmount && !!selectedToken && !tooManyDecimals && !exceedsBalance;
4137
4616
  const handleMax = () => {
4138
4617
  if (!selectedToken) return;
4139
- const max = formattedBalance.replace(/\s/g, "");
4140
- if (parseFloat(max) > 0) onAmountChange(max);
4618
+ const wei = BigInt(balanceWei);
4619
+ if (wei > 0n) onAmountChange(formatUnits(wei, selectedToken.decimals));
4141
4620
  };
4142
4621
  const handleWithdraw = async () => {
4143
4622
  if (!selectedToken || !canWithdraw) return;
@@ -4180,7 +4659,7 @@ function WithdrawView({
4180
4659
  ) });
4181
4660
  }
4182
4661
  if (isPending && !cancelled) {
4183
- const canCancel = currentStep === "idle" || currentStep === "switching-chain" || currentStep === "signing";
4662
+ const canCancel = currentStep === "idle" || currentStep === "preparing" || currentStep === "switching-chain" || currentStep === "signing";
4184
4663
  return /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
4185
4664
  TransactionProgressView,
4186
4665
  {
@@ -4219,11 +4698,10 @@ function WithdrawView({
4219
4698
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
4220
4699
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4221
4700
  /* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
4222
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
4223
- "Available ",
4224
- formattedBalance,
4701
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex items-center gap-1 text-sm", children: [
4702
+ "Available",
4225
4703
  " ",
4226
- selectedToken?.symbol
4704
+ isBalanceLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" }) : `${formattedBalance} ${selectedToken?.symbol ?? ""}`
4227
4705
  ] })
4228
4706
  ] }),
4229
4707
  /* @__PURE__ */ jsxs("div", { className: "border-border bg-input flex items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
@@ -4246,7 +4724,8 @@ function WithdrawView({
4246
4724
  {
4247
4725
  type: "button",
4248
4726
  onClick: handleMax,
4249
- className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
4727
+ disabled: isBalanceLoading,
4728
+ className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors disabled:cursor-default disabled:opacity-50",
4250
4729
  children: "MAX"
4251
4730
  }
4252
4731
  )
@@ -4349,6 +4828,7 @@ function WithdrawModalContent({
4349
4828
  {
4350
4829
  selectedTokenId: selectedToken?.id,
4351
4830
  onSelect: (id) => {
4831
+ if (id !== selectedTokenId) setAmount("");
4352
4832
  setSelectedTokenId(id);
4353
4833
  setView("form");
4354
4834
  }